From 1e5912e208a4ca69cdf5eaaf64e3daeeb251f199 Mon Sep 17 00:00:00 2001 From: Devarshi Shimpi Date: Mon, 17 Aug 2026 01:23:00 +0530 Subject: [PATCH 1/7] feat: rename @codra scope to @codraoss and prepare packages for public npm publishing Add LICENSE/README, tsup build configs, and publishConfig.exports to each publishable package (api, core, db, models, provider-github, schema, ui), wire up changesets for versioning/release, and add check-package-exports/swap-publish-exports scripts plus CI validation (build, publint, exports check). Also hardens CLA check to verify every commit author in a PR, not just the opener. --- .changeset/README.md | 33 + .changeset/config.json | 21 + .github/workflows/ci.yml | 11 + .github/workflows/cla-check.yml | 76 +- .gitignore | 4 + apps/worker/package.json | 11 +- apps/worker/src/api-deps.ts | 26 +- apps/worker/src/env.ts | 4 +- apps/worker/src/index.ts | 8 +- apps/worker/src/ports/cloudflare-kv.ts | 2 +- .../src/ports/cloudflare-orchestrator.ts | 10 +- apps/worker/src/sessions.ts | 2 +- apps/worker/src/workflows/review.ts | 4 +- eslint.config.js | 14 +- package-lock.json | 10159 ++++++++++------ package.json | 25 +- packages/api/LICENSE | 625 + packages/api/README.md | 15 + packages/api/package.json | 51 +- packages/api/src/logger.ts | 8 +- packages/api/src/ports.ts | 22 +- packages/api/src/routes/api/auth.ts | 2 +- packages/api/src/routes/api/jobs.ts | 6 +- packages/api/src/routes/api/models.ts | 2 +- packages/api/src/routes/api/repos.ts | 2 +- packages/api/src/routes/api/settings.ts | 2 +- packages/api/src/routes/webhook.ts | 2 +- packages/api/src/sessions.ts | 2 +- packages/api/tsconfig.json | 8 +- packages/api/tsup.config.json | 9 + packages/core/LICENSE | 625 + packages/core/README.md | 15 + packages/core/package.json | 103 +- packages/core/src/diff/index.ts | 2 +- packages/core/src/finding-gates.ts | 2 +- packages/core/src/model-output/batch.ts | 2 +- packages/core/src/model-output/dedupe.ts | 2 +- packages/core/src/model-output/index.ts | 2 +- packages/core/src/model-output/json-batch.ts | 2 +- packages/core/src/model-output/json.ts | 2 +- packages/core/src/ports/file-reviews.ts | 2 +- packages/core/src/ports/formatter.ts | 2 +- packages/core/src/ports/in-memory.ts | 2 +- packages/core/src/ports/jobs.ts | 2 +- packages/core/src/ports/model.ts | 2 +- packages/core/src/ports/orchestrator.ts | 2 +- packages/core/src/ports/repo-config.ts | 2 +- packages/core/src/ports/settings.ts | 2 +- packages/core/src/prompts/file-review.ts | 2 +- packages/core/src/review/bin-runner.ts | 2 +- packages/core/src/review/diff-cache.ts | 2 +- packages/core/src/review/file-runner.ts | 2 +- packages/core/src/review/finalize.ts | 2 +- packages/core/src/review/gate-pipeline.ts | 2 +- packages/core/src/review/index.ts | 4 +- packages/core/src/review/phase.ts | 2 +- packages/core/src/review/prepare.ts | 2 +- packages/core/src/review/request.ts | 4 +- packages/core/src/review/retry-policy.ts | 4 +- packages/core/src/rules/detect.ts | 4 +- packages/core/src/rules/table.ts | 2 +- packages/core/src/verify.ts | 2 +- packages/core/test/in-memory.ts | 2 +- packages/core/tsconfig.json | 2 +- packages/core/tsup.config.json | 26 + packages/db/LICENSE | 625 + packages/db/README.md | 15 + packages/db/package.json | 63 +- packages/db/src/app-settings.ts | 2 +- packages/db/src/constants.ts | 2 +- packages/db/src/file-reviews-bulk.ts | 4 +- packages/db/src/file-reviews-findings.ts | 4 +- packages/db/src/file-reviews.ts | 2 +- packages/db/src/jobs-mapping.ts | 2 +- packages/db/src/jobs.ts | 4 +- packages/db/src/learning.ts | 2 +- packages/db/src/model-configs.ts | 2 +- packages/db/src/repo-configs.ts | 2 +- .../repositories/file-review-repository.ts | 2 +- .../repositories/instance-id-repository.ts | 2 +- .../db/src/repositories/jobs-repository.ts | 2 +- .../repositories/repo-config-repository.ts | 2 +- .../src/repositories/settings-repository.ts | 2 +- packages/db/src/review-comment-sql.ts | 2 +- packages/db/src/stats.ts | 4 +- packages/db/tsconfig.json | 8 +- packages/db/tsup.config.json | 9 + packages/models/LICENSE | 625 + packages/models/README.md | 15 + packages/models/package.json | 71 +- packages/models/src/catalog.ts | 4 +- .../src/internal/model-chain-progress.ts | 6 +- .../models/src/internal/model-chain-runner.ts | 10 +- .../models/src/internal/model-rate-limits.ts | 4 +- .../models/src/internal/model-review-batch.ts | 10 +- .../models/src/internal/model-review-chain.ts | 6 +- .../models/src/internal/model-review-file.ts | 12 +- packages/models/src/internal/model-support.ts | 4 +- packages/models/src/llm-crypto.ts | 2 +- packages/models/src/providers/anthropic.ts | 4 +- packages/models/src/providers/cloudflare.ts | 4 +- packages/models/src/providers/google.ts | 4 +- packages/models/src/providers/openai.ts | 4 +- packages/models/src/providers/vertex.ts | 4 +- packages/models/src/runner.ts | 12 +- packages/models/src/types.ts | 8 +- .../models/test/model/batch-routing.spec.ts | 4 +- .../test/model/chain-progress-store.spec.ts | 2 +- .../models/test/model/chain-resume.spec.ts | 6 +- packages/models/test/model/cloudflare.spec.ts | 2 +- .../models/test/model/config-cache.spec.ts | 4 +- .../models/test/model/gemini-schema.spec.ts | 4 +- packages/models/test/model/limits.spec.ts | 2 +- .../models/test/model/output-batch.spec.ts | 2 +- packages/models/test/model/output.spec.ts | 6 +- .../test/model/rate-limit-parse.spec.ts | 2 +- .../test/model/service-chunking.spec.ts | 8 +- .../test/model/service-fallbacks.spec.ts | 6 +- .../model/service-grammar-rejection.spec.ts | 6 +- .../test/model/service-requests.spec.ts | 8 +- .../models/test/model/service-retries.spec.ts | 8 +- packages/models/test/url-guard.spec.ts | 2 +- packages/models/tsup.config.json | 18 + packages/provider-github/LICENSE | 625 + packages/provider-github/README.md | 15 + packages/provider-github/package.json | 53 +- packages/provider-github/src/app-auth.ts | 2 +- packages/provider-github/src/client.ts | 2 +- packages/provider-github/src/diff-fetch.ts | 4 +- packages/provider-github/src/http.ts | 2 +- packages/provider-github/src/oauth.ts | 2 +- packages/provider-github/src/review-post.ts | 2 +- packages/provider-github/src/types.ts | 4 +- packages/provider-github/src/webhook.ts | 4 +- packages/provider-github/tsup.config.json | 9 + packages/schema/LICENSE | 625 + packages/schema/README.md | 15 + packages/schema/package.json | 71 +- packages/schema/tsup.config.json | 18 + packages/ui/LICENSE | 625 + packages/ui/README.md | 15 + packages/ui/package.json | 88 +- packages/ui/src/lib/file-tree.ts | 2 +- packages/ui/tsup.config.json | 21 + scripts/check-package-exports.mjs | 69 + scripts/swap-publish-exports.mjs | 27 + .../features/account/detail-rows.tsx | 4 +- .../features/account/details-section.tsx | 4 +- .../features/account/profile-card.tsx | 4 +- .../dashboard/updates-email-prompt.tsx | 4 +- .../features/job-detail/comment-card.tsx | 10 +- .../features/job-detail/context-snippet.tsx | 4 +- .../job-detail/diff-file-panel-utils.ts | 4 +- .../features/job-detail/diff-file-panel.tsx | 10 +- .../features/job-detail/diff-file-tree.tsx | 8 +- .../features/job-detail/file-finding.tsx | 6 +- .../features/job-detail/job-chips.tsx | 4 +- .../features/job-detail/job-diffs.tsx | 6 +- .../features/job-detail/job-findings-list.tsx | 6 +- .../features/job-detail/job-header.tsx | 6 +- .../features/job-detail/job-meta-cards.tsx | 4 +- .../features/job-detail/job-progress.tsx | 2 +- .../job-detail/job-review-overview.tsx | 6 +- .../features/job-detail/job-skeleton.tsx | 2 +- .../features/job-detail/status-badge.tsx | 4 +- .../features/models/model-chain.tsx | 4 +- .../features/repos/repo-model-modal.tsx | 4 +- .../components/features/repos/repo-route.ts | 2 +- .../components/features/repos/repo-row.tsx | 4 +- .../features/reviews/live-review-stepper.tsx | 2 +- .../features/settings/about-section.tsx | 2 +- .../settings/default-models-section.tsx | 4 +- .../features/settings/new-provider-form.tsx | 2 +- .../features/settings/provider-list.tsx | 4 +- .../features/settings/provider-row.tsx | 6 +- .../features/settings/review-section.tsx | 8 +- .../features/settings/settings-support.ts | 4 +- .../features/stats/chart-primitives.tsx | 2 +- .../features/stats/chart-support.ts | 2 +- .../features/stats/metrics-grid-charts.tsx | 4 +- .../features/stats/metrics-grid.tsx | 2 +- .../features/stats/overview-stats.tsx | 6 +- .../components/features/stats/stats-grid.tsx | 4 +- .../features/stats/time-range-select.tsx | 4 +- src/client/components/layout/account-menu.tsx | 6 +- src/client/components/layout/app-shell.tsx | 8 +- src/client/components/layout/page-header.tsx | 2 +- .../components/layout/sidebar-nav-item.tsx | 2 +- src/client/components/shared/jobs-table.tsx | 6 +- .../components/shared/page-header-actions.tsx | 2 +- .../shared/route-error-boundary.tsx | 2 +- src/client/hooks/use-job-detail.ts | 2 +- src/client/hooks/use-provider-settings.ts | 4 +- src/client/hooks/use-review-settings.ts | 4 +- src/client/lib/api.ts | 4 +- src/client/lib/batch-groups.ts | 2 +- src/client/lib/job-format.ts | 2 +- src/client/lib/timezone.ts | 2 +- src/client/main.tsx | 6 +- src/client/pages/account.tsx | 4 +- src/client/pages/dashboard.tsx | 4 +- src/client/pages/job-detail.tsx | 4 +- src/client/pages/job-logs.tsx | 8 +- src/client/pages/jobs.tsx | 4 +- src/client/pages/landing.tsx | 4 +- src/client/pages/login.tsx | 4 +- src/client/pages/not-found.tsx | 2 +- src/client/pages/repos.tsx | 6 +- src/client/pages/settings.tsx | 4 +- src/client/pages/stats.tsx | 6 +- src/server/adapters/file-review-store.ts | 6 +- src/server/adapters/index.ts | 8 +- src/server/adapters/jobs-store.ts | 6 +- src/server/adapters/platform.ts | 2 +- src/server/adapters/services.ts | 12 +- src/server/adapters/settings-store.ts | 6 +- src/server/core/claim-checks.ts | 4 +- src/server/core/config.ts | 6 +- src/server/core/diff/index.ts | 4 +- src/server/core/fingerprint.ts | 4 +- src/server/core/job-recovery.ts | 4 +- src/server/core/logger.ts | 8 +- src/server/core/model-output/index.ts | 4 +- src/server/core/oauth.ts | 2 +- src/server/core/review/index.ts | 10 +- src/server/core/rules/detect.ts | 4 +- src/server/core/rules/table.ts | 4 +- src/server/core/telemetry.ts | 2 +- src/server/core/timeout.ts | 4 +- src/server/core/token-tracker.ts | 4 +- src/server/core/verify.ts | 4 +- src/server/env.d.ts | 2 +- src/server/env.ts | 2 +- src/server/prompts/file-review.ts | 4 +- src/server/prompts/languages.ts | 4 +- src/server/prompts/summary.ts | 4 +- src/server/prompts/verify.ts | 4 +- src/server/services/formatter.ts | 2 +- test/api/auth.spec.ts | 10 +- test/api/jobs.spec.ts | 4 +- test/api/repos.spec.ts | 10 +- test/api/webhook-handling.spec.ts | 2 +- test/comment-feedback.spec.ts | 4 +- test/db/bulk-upsert.spec.ts | 6 +- test/db/stats-trend.spec.ts | 2 +- test/diff.spec.ts | 2 +- test/e2e/accordion-selection.spec.tsx | 2 +- test/e2e/dashboard.spec.tsx | 2 +- test/findings/claim-types.spec.ts | 2 +- test/findings/gold-set.spec.ts | 2 +- test/findings/prompts-batch-review.spec.ts | 4 +- test/findings/prompts-file-review.spec.ts | 2 +- test/findings/review-verify.spec.ts | 2 +- test/findings/rules-detect.spec.ts | 2 +- test/findings/rules-pipeline.spec.ts | 2 +- test/findings/suppression.spec.ts | 10 +- test/helpers.ts | 10 +- test/jsonb-encoding.spec.ts | 10 +- test/mocks/review-harness.ts | 2 +- test/mocks/services.ts | 4 +- test/review/async-batch.spec.ts | 20 +- test/review/batch-flow.spec.ts | 40 +- test/review/chunk-concurrency.spec.ts | 2 +- test/review/comments.spec.ts | 4 +- test/review/flow-chunking.spec.ts | 30 +- test/review/flow-lifecycle.spec.ts | 32 +- test/review/flow-retry.spec.ts | 28 +- test/review/pipeline-regression.spec.ts | 2 +- test/review/quota-deferral.spec.ts | 4 +- test/review/resumable-queue.spec.ts | 6 +- test/review/scheduled-maintenance.spec.ts | 4 +- test/review/subrequest-completion.spec.ts | 6 +- .../workflow-finalize-fresh-instance.spec.ts | 4 +- tsconfig.base.json | 1 + 274 files changed, 12143 insertions(+), 4504 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 packages/api/LICENSE create mode 100644 packages/api/README.md create mode 100644 packages/api/tsup.config.json create mode 100644 packages/core/LICENSE create mode 100644 packages/core/README.md create mode 100644 packages/core/tsup.config.json create mode 100644 packages/db/LICENSE create mode 100644 packages/db/README.md create mode 100644 packages/db/tsup.config.json create mode 100644 packages/models/LICENSE create mode 100644 packages/models/README.md create mode 100644 packages/models/tsup.config.json create mode 100644 packages/provider-github/LICENSE create mode 100644 packages/provider-github/README.md create mode 100644 packages/provider-github/tsup.config.json create mode 100644 packages/schema/LICENSE create mode 100644 packages/schema/README.md create mode 100644 packages/schema/tsup.config.json create mode 100644 packages/ui/LICENSE create mode 100644 packages/ui/README.md create mode 100644 packages/ui/tsup.config.json create mode 100644 scripts/check-package-exports.mjs create mode 100644 scripts/swap-publish-exports.mjs diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000..697e6f70 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,33 @@ +# Changesets + +This folder is managed by [Changesets](https://github.com/changesets/changesets), used here only for +**local, manual** versioning of the public `@codraoss/*` packages. There is no CI-based publishing. + +The seven publishable packages (`schema`, `core`, `db`, `models`, `provider-github`, `api`, `ui`) +are a **fixed group** — they version and release in lockstep (currently `0.9.4`). The worker app +(`@codraoss/worker`) is private and never published. + +## Recording a change + +```bash +npx changeset # pick the bump, write a summary; commit the generated file +``` + +## Cutting a release (run locally, then publish by hand) + +```bash +npm run version:packages # applies pending changesets: bumps all @codraoss/* in lockstep + changelog +npm run release # builds dist and runs `changeset publish` for you +``` + +`npm run release` builds every package to `dist/` and publishes. Publishing is also possible per +package with plain npm — the `prepack` hook rewrites `exports` to the compiled `dist/` paths in the +tarball automatically: + +```bash +npm run build:packages +npm publish -w @codraoss/schema # ...repeat bottom-up: schema → core → db/models/provider-github → api → ui +``` + +You must be logged in to npm (`npm whoami`) and own the `@codraoss` scope. First publish of each +package needs public access, which `publishConfig.access` already sets. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000..eec496e2 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [ + [ + "@codraoss/schema", + "@codraoss/core", + "@codraoss/db", + "@codraoss/models", + "@codraoss/provider-github", + "@codraoss/api", + "@codraoss/ui" + ] + ], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": ["@codraoss/worker"] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a300965..97818689 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,17 @@ jobs: - name: Install dependencies run: npm ci + - name: Build packages + run: npm run build:packages + + - name: Validate package exports + run: | + npm run check:exports + for dir in packages/*/; do + echo "publint $dir" + (cd "$dir" && npx --no-install publint) || exit 1 + done + - name: Static Analysis (Typecheck) run: npm run typecheck && npm run typecheck:all diff --git a/.github/workflows/cla-check.yml b/.github/workflows/cla-check.yml index 0cca46ce..4050c151 100644 --- a/.github/workflows/cla-check.yml +++ b/.github/workflows/cla-check.yml @@ -15,40 +15,82 @@ jobs: contents: read pull-requests: read steps: - - name: Verify contributor CLA signature + - name: Verify CLA signatures for the PR author and every commit author env: CLA_CHECK_SECRET: ${{ secrets.CLA_CHECK_SECRET }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} PR_AUTHOR: ${{ github.event.pull_request.user.login }} run: | node -e " (async () => { - const author = process.env.PR_AUTHOR; const secret = process.env.CLA_CHECK_SECRET; - - if (!author) throw new Error('Missing PR author.'); + const token = process.env.GITHUB_TOKEN; + const repo = process.env.REPO; + const prNumber = process.env.PR_NUMBER; + const prAuthor = process.env.PR_AUTHOR; + if (!secret) throw new Error('Missing CLA_CHECK_SECRET.'); + if (!token) throw new Error('Missing GITHUB_TOKEN.'); + if (!repo || !prNumber || !prAuthor) throw new Error('Missing PR context.'); + + const isBot = (login) => login.endsWith('[bot]'); + const logins = new Set(); + if (!isBot(prAuthor)) logins.add(prAuthor); - console.log('Checking CLA signature for @' + author + '...'); + // Walk every commit in the PR, not just the opener: cherry-picked or + // applied patches carry other people's copyright and need a signature too. + const headers = { + authorization: 'Bearer ' + token, + accept: 'application/vnd.github+json', + 'x-github-api-version': '2022-11-28', + }; + const unmatched = []; + for (let page = 1; page <= 3; page++) { + const url = 'https://api.github.com/repos/' + repo + '/pulls/' + prNumber + '/commits?per_page=100&page=' + page; + const res = await fetch(url, { headers }); + if (!res.ok) throw new Error('GitHub API error ' + res.status + ': ' + (await res.text())); + const commits = await res.json(); + for (const c of commits) { + if (c.author && c.author.login) { + if (c.author.type !== 'Bot' && !isBot(c.author.login)) logins.add(c.author.login); + } else { + unmatched.push(c.sha.slice(0, 7) + ' (' + c.commit.author.name + ' <' + c.commit.author.email + '>)'); + } + } + if (commits.length < 100) break; + } - const response = await fetch('https://codra.run/api/internal/check-cla?user=' + encodeURIComponent(author), { - headers: { - 'x-cla-check-secret': secret, - }, - }); + if (unmatched.length > 0) { + console.error('❌ Some commits are not linked to a GitHub account, so their CLA status cannot be verified:'); + for (const line of unmatched) console.error(' ' + line); + console.error('Each author must add their commit email to their GitHub account, or the commits must be re-authored.'); + process.exit(1); + } - if (!response.ok) { - const body = await response.text(); - throw new Error('API Error ' + response.status + ': ' + body); + const unsigned = []; + for (const login of [...logins].sort()) { + console.log('Checking CLA signature for @' + login + '...'); + const res = await fetch('https://codra.run/api/internal/check-cla?user=' + encodeURIComponent(login), { + headers: { 'x-cla-check-secret': secret }, + }); + if (!res.ok) throw new Error('API Error ' + res.status + ': ' + (await res.text())); + const payload = await res.json(); + if (payload.signed) { + console.log('✅ CLA confirmed for @' + login + '.'); + } else { + unsigned.push(login); + } } - const payload = await response.json(); - if (!payload.signed) { - console.error('❌ @' + author + ' has not signed the CLA.'); + if (unsigned.length > 0) { + for (const login of unsigned) console.error('❌ @' + login + ' has not signed the CLA.'); console.error('Please visit https://codra.run/cla to sign.'); process.exit(1); } - console.log('✅ CLA confirmed for @' + author + '.'); + console.log('✅ CLA confirmed for all ' + logins.size + ' author(s).'); })().catch(err => { console.error(err.message); process.exit(1); diff --git a/.gitignore b/.gitignore index 435ede5f..e22ce89b 100644 --- a/.gitignore +++ b/.gitignore @@ -144,3 +144,7 @@ vite.config.ts.timestamp-* .agent +*.prepack-bak +tsup.config.bundled_*.mjs +*.tgz +.npmrc diff --git a/apps/worker/package.json b/apps/worker/package.json index 3db124b9..fbd91f5e 100644 --- a/apps/worker/package.json +++ b/apps/worker/package.json @@ -1,12 +1,15 @@ { - "name": "@codra/worker", + "name": "@codraoss/worker", "version": "0.9.4", "private": true, "type": "module", "dependencies": { - "@codra/core": "*", - "@codra/db": "*", - "@codra/schema": "*", + "@codraoss/api": "*", + "@codraoss/core": "*", + "@codraoss/db": "*", + "@codraoss/models": "*", + "@codraoss/provider-github": "*", + "@codraoss/schema": "*", "hono": "^4.12.25" }, "devDependencies": { diff --git a/apps/worker/src/api-deps.ts b/apps/worker/src/api-deps.ts index d10b664b..a0154bcf 100644 --- a/apps/worker/src/api-deps.ts +++ b/apps/worker/src/api-deps.ts @@ -1,17 +1,17 @@ -import type { ApiRouterDeps } from '@codra/api'; +import type { ApiRouterDeps } from '@codraoss/api'; import type { AppBindings } from './env'; -import * as dbAccounts from '@codra/db/accounts'; -import * as dbJobs from '@codra/db/jobs'; -import * as dbFileReviews from '@codra/db/file-reviews'; -import * as dbCommentFeedback from '@codra/db/comment-feedback'; -import * as dbModelConfigs from '@codra/db/model-configs'; -import * as dbRepoConfigs from '@codra/db/repo-configs'; -import * as dbAppSettings from '@codra/db/app-settings'; -import * as dbStats from '@codra/db/stats'; -import * as dbWebhookDeliveries from '@codra/db/webhook-deliveries'; +import * as dbAccounts from '@codraoss/db/accounts'; +import * as dbJobs from '@codraoss/db/jobs'; +import * as dbFileReviews from '@codraoss/db/file-reviews'; +import * as dbCommentFeedback from '@codraoss/db/comment-feedback'; +import * as dbModelConfigs from '@codraoss/db/model-configs'; +import * as dbRepoConfigs from '@codraoss/db/repo-configs'; +import * as dbAppSettings from '@codraoss/db/app-settings'; +import * as dbStats from '@codraoss/db/stats'; +import * as dbWebhookDeliveries from '@codraoss/db/webhook-deliveries'; -import { GitHubClient, normalizeGitHubWebhook } from '@codra/provider-github'; +import { GitHubClient, normalizeGitHubWebhook } from '@codraoss/provider-github'; import { getGlobalConfig, updateGlobalConfig, loadRepoConfig, invalidateRepoConfigCache } from '../../../src/server/core/config'; import { getUpdatesEmailPreference, syncUpdatesEmail } from '../../../src/server/core/updates-email'; @@ -26,8 +26,8 @@ import { CloudflareSessionStore } from './sessions'; import { logger } from '../../../src/server/core/logger'; // model sync dependencies -import { listLlmProviderSecrets, upsertDiscoveredModelConfigs, createLlmProvider, updateLlmProvider, getResolvedModelConfig, getLlmProvider } from '@codra/db/model-configs'; -import { encryptLlmApiKey, decryptLlmApiKey, listProviderModels, reviewWithCloudflare, reviewWithGoogle, reviewWithVertex, reviewWithOpenAI, reviewWithAnthropic, ProviderRequestError } from '@codra/models'; +import { listLlmProviderSecrets, upsertDiscoveredModelConfigs, createLlmProvider, updateLlmProvider, getResolvedModelConfig, getLlmProvider } from '@codraoss/db/model-configs'; +import { encryptLlmApiKey, decryptLlmApiKey, listProviderModels, reviewWithCloudflare, reviewWithGoogle, reviewWithVertex, reviewWithOpenAI, reviewWithAnthropic, ProviderRequestError } from '@codraoss/models'; import { buildReviewResponseSchema } from '../../../src/server/prompts/file-review'; function getSecretStore(env: AppBindings) { diff --git a/apps/worker/src/env.ts b/apps/worker/src/env.ts index 03238748..b3160b61 100644 --- a/apps/worker/src/env.ts +++ b/apps/worker/src/env.ts @@ -1,5 +1,5 @@ -import type { ReviewJobMessage } from '@codra/schema'; -import type { DashboardSessionUser, SessionStore } from '@codra/core'; +import type { ReviewJobMessage } from '@codraoss/schema'; +import type { DashboardSessionUser, SessionStore } from '@codraoss/core'; export interface WorkersAiBinding { run(model: string, input: Record, options?: { signal?: AbortSignal }): Promise; diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index 9bd6f940..71a8425c 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -1,12 +1,12 @@ -import { createApiRouter } from '@codra/api'; +import { createApiRouter } from '@codraoss/api'; import { createApiRouterDeps } from './api-deps'; import { ReviewWorkflow } from './workflows/review'; import type { AppBindings } from './env'; -import { reviewJobMessageSchema } from '@codra/schema'; +import { reviewJobMessageSchema } from '@codraoss/schema'; import { logger } from '@server/core/logger'; import { disposeRpc } from '@server/core/rpc'; -import { runWithDb } from '@codra/db/client'; -import { failJob, hasPendingMaintenanceWork, clearSystemActive } from '@codra/db/jobs'; +import { runWithDb } from '@codraoss/db/client'; +import { failJob, hasPendingMaintenanceWork, clearSystemActive } from '@codraoss/db/jobs'; import { runBestEffortJobMaintenance } from '@server/core/job-recovery'; const app = createApiRouter(); diff --git a/apps/worker/src/ports/cloudflare-kv.ts b/apps/worker/src/ports/cloudflare-kv.ts index 0c2af4b4..e8151ae3 100644 --- a/apps/worker/src/ports/cloudflare-kv.ts +++ b/apps/worker/src/ports/cloudflare-kv.ts @@ -1,4 +1,4 @@ -import type { KeyValueStore } from '@codra/core'; +import type { KeyValueStore } from '@codraoss/core'; export class CloudflareKV implements KeyValueStore { constructor(private readonly kv: KVNamespace) {} diff --git a/apps/worker/src/ports/cloudflare-orchestrator.ts b/apps/worker/src/ports/cloudflare-orchestrator.ts index ca8d28ea..ed27767d 100644 --- a/apps/worker/src/ports/cloudflare-orchestrator.ts +++ b/apps/worker/src/ports/cloudflare-orchestrator.ts @@ -1,9 +1,9 @@ -import type { JobOrchestrator } from '@codra/core'; -import type { ReviewJobMessage } from '@codra/schema'; -import { FRESH_INVOCATION_YIELD_SECONDS } from '@codra/core'; +import type { JobOrchestrator } from '@codraoss/core'; +import type { ReviewJobMessage } from '@codraoss/schema'; +import { FRESH_INVOCATION_YIELD_SECONDS } from '@codraoss/core'; import { runReviewJob } from '@server/core/review'; -import { setJobWorkflowInstance } from '@codra/db/jobs'; -import { logger } from '@codra/core/logger'; +import { setJobWorkflowInstance } from '@codraoss/db/jobs'; +import { logger } from '@codraoss/core/logger'; import { runBestEffortJobMaintenance } from '@server/core/job-recovery'; import type { AppBindings } from '../env'; import type { WorkflowStep } from 'cloudflare:workers'; diff --git a/apps/worker/src/sessions.ts b/apps/worker/src/sessions.ts index c6ecc3a7..b50375f7 100644 --- a/apps/worker/src/sessions.ts +++ b/apps/worker/src/sessions.ts @@ -1,4 +1,4 @@ -import type { DashboardSessionUser, SessionStore } from '@codra/core'; +import type { DashboardSessionUser, SessionStore } from '@codraoss/core'; export class CloudflareSessionStore implements SessionStore { constructor(private readonly kv: KVNamespace) {} diff --git a/apps/worker/src/workflows/review.ts b/apps/worker/src/workflows/review.ts index 01d55f99..0a3dc46b 100644 --- a/apps/worker/src/workflows/review.ts +++ b/apps/worker/src/workflows/review.ts @@ -1,7 +1,7 @@ import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers'; import type { AppBindings } from '../env'; -import { type ReviewJobMessage } from '@codra/schema'; -import { runWithDb } from '@codra/db/client'; +import { type ReviewJobMessage } from '@codraoss/schema'; +import { runWithDb } from '@codraoss/db/client'; import { CloudflareOrchestrator } from '../ports/cloudflare-orchestrator'; export class ReviewWorkflow extends WorkflowEntrypoint { diff --git a/eslint.config.js b/eslint.config.js index 0396e8ec..b23885d2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -80,7 +80,7 @@ export default tseslint.config( { target: 'src/client/**/*', from: ['packages/core/**/*', 'src/server/**/*'], - message: 'The review engine and the Worker tree are server-only. Importing either pulls zod/jsonrepair/picomatch into the browser bundle -- exactly what the `vite build` CI step exists to catch. (@codra/schema/review-limits is the sanctioned client-side import.)' + message: 'The review engine and the Worker tree are server-only. Importing either pulls zod/jsonrepair/picomatch into the browser bundle -- exactly what the `vite build` CI step exists to catch. (@codraoss/schema/review-limits is the sanctioned client-side import.)' } ] }], @@ -106,14 +106,14 @@ export default tseslint.config( patterns: [ { group: ['**/db/jobs-*', '@server/db/jobs-*'], message: 'Import from @server/db/jobs, not a sibling. Eight specs vi.mock that specifier; a direct sibling import silently bypasses the mock.' }, { group: ['**/db/file-reviews-*', '@server/db/file-reviews-*'], message: 'Import from @server/db/file-reviews, not a sibling. (No spec mocks this one today; the rule keeps the barrel the single entry point.)' }, - { group: ['**/services/model-review-*', '**/services/model-rate-limits', '**/services/model-chain-runner', '**/services/model-support', '@codra/models-*'], message: 'Import from @codra/models, not a sibling. Four specs vi.mock that specifier.' }, + { group: ['**/services/model-review-*', '**/services/model-rate-limits', '**/services/model-chain-runner', '**/services/model-support', '@codraoss/models-*'], message: 'Import from @codraoss/models, not a sibling. Four specs vi.mock that specifier.' }, { group: ['**/core/github/http', '**/core/github/app-auth', '**/core/github/types', '**/core/github/diff-fetch', '**/core/github/review-post', '**/core/github/labels', '@server/core/github/http', '@server/core/github/app-auth', '@server/core/github/types', '@server/core/github/diff-fetch', '@server/core/github/review-post', '@server/core/github/labels'], message: 'Import from @server/core/github, not a sibling. One spec vi.mocks that specifier. (core/github/oauth is deliberately NOT listed: it is the dashboard OAuth flow, not part of the GitHubClient barrel, and routes/auth.ts imports it directly.)' }, // Covers every sibling in the family, including the three the barrel re-exports publicly // (budget, diff-cache, request) which were previously unprotected. - { group: ['**/core/review/*', '@server/core/review/*', '@codra/core/review/*'], message: 'Import from @server/core/review, not a sibling. One spec vi.mocks that specifier and workflows/review.ts imports only runReviewJob from it.' }, - { group: ['**/core/model-output/*', '@server/core/model-output/*', '@codra/core/model-output/*'], message: 'Import from @codra/core/model-output, not a sibling. (The package exports map already refuses to resolve these; the lint rule gives the error at edit time.)' }, - { group: ['**/core/diff/position', '@server/core/diff/position', '@codra/core/diff/position'], message: 'Import from @codra/core/diff, not a sibling.' }, - { group: ['**/schema-claims', '**/schema-repo-config', '**/schema-enums', '@codra/schema/schema-claims', '@codra/schema/schema-repo-config', '@codra/schema/schema-enums'], message: 'Import from @codra/schema, not a sibling. (@codra/schema/review-limits is exempt: the client imports it directly to keep zod out of the browser bundle.)' }, + { group: ['**/core/review/*', '@server/core/review/*', '@codraoss/core/review/*'], message: 'Import from @server/core/review, not a sibling. One spec vi.mocks that specifier and workflows/review.ts imports only runReviewJob from it.' }, + { group: ['**/core/model-output/*', '@server/core/model-output/*', '@codraoss/core/model-output/*'], message: 'Import from @codraoss/core/model-output, not a sibling. (The package exports map already refuses to resolve these; the lint rule gives the error at edit time.)' }, + { group: ['**/core/diff/position', '@server/core/diff/position', '@codraoss/core/diff/position'], message: 'Import from @codraoss/core/diff, not a sibling.' }, + { group: ['**/schema-claims', '**/schema-repo-config', '**/schema-enums', '@codraoss/schema/schema-claims', '@codraoss/schema/schema-repo-config', '@codraoss/schema/schema-enums'], message: 'Import from @codraoss/schema, not a sibling. (@codraoss/schema/review-limits is exempt: the client imports it directly to keep zod out of the browser bundle.)' }, ], }], }, @@ -138,7 +138,7 @@ export default tseslint.config( 'src/server/db/file-reviews.ts', 'src/server/services/model.ts', 'src/server/core/github/index.ts', - // core/review, core/diff and core/model-output are gone from here: they moved to @codra/core and + // core/review, core/diff and core/model-output are gone from here: they moved to @codraoss/core and // what is left at those paths is a re-export shim with no sibling imports to exempt. ESLint does // not warn about `files` patterns that match nothing, so a stale entry would just rot quietly. 'packages/schema/src/schema.ts', diff --git a/package-lock.json b/package-lock.json index d74001b2..13532691 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,13 @@ ], "dependencies": { "@base-ui/react": "^1.6.0", - "@codra/core": "*", - "@codra/schema": "*", - "@codra/ui": "*", + "@codraoss/api": "*", + "@codraoss/core": "*", + "@codraoss/db": "*", + "@codraoss/models": "*", + "@codraoss/provider-github": "*", + "@codraoss/schema": "*", + "@codraoss/ui": "*", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "hono": "^4.12.25", @@ -39,6 +43,8 @@ "zod": "^4.3.6" }, "devDependencies": { + "@arethetypeswrong/cli": "^0.18.5", + "@changesets/cli": "^3.0.0", "@eslint/js": "^10.0.1", "@tailwindcss/vite": "^4.2.2", "@testing-library/dom": "^10.4.1", @@ -56,7 +62,9 @@ "jsdom": "^29.0.2", "ora": "^9.4.1", "prompts": "^2.4.2", + "publint": "^0.3.23", "tailwindcss": "^4.2.2", + "tsup": "^8.5.1", "typescript": "^6.0.2", "typescript-eslint": "^8.66.0", "vite": "^8.0.8", @@ -65,12 +73,15 @@ } }, "apps/worker": { - "name": "@codra/worker", + "name": "@codraoss/worker", "version": "0.9.4", "dependencies": { - "@codra/core": "*", - "@codra/db": "*", - "@codra/schema": "*", + "@codraoss/api": "*", + "@codraoss/core": "*", + "@codraoss/db": "*", + "@codraoss/models": "*", + "@codraoss/provider-github": "*", + "@codraoss/schema": "*", "hono": "^4.12.25" }, "devDependencies": { @@ -85,6 +96,115 @@ "dev": true, "license": "MIT OR Apache-2.0" }, + "node_modules/@andrewbranch/untar.js": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.4.tgz", + "integrity": "sha512-pVXSwPsLuw8IGLo2Di0EaOfsk+ntVvpkk942J/sHYIkwvtKUakEcPh7HBgZ6tuimgzKSEHgCvO4XgQ05DEbwDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@arethetypeswrong/cli": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.18.5.tgz", + "integrity": "sha512-gM+8vRsQOD/Uc7EnBedUhkG5OCsDWE4uoak5QvomGpMpaky0Eh41p04nIMgrWb8EOmqZUJGc6zz9hsP6E56R7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@arethetypeswrong/core": "0.18.5", + "chalk": "^4.1.2", + "cli-table3": "^0.6.3", + "commander": "^10.0.1", + "marked": "^9.1.2", + "marked-terminal": "^7.1.0", + "semver": "^7.5.4" + }, + "bin": { + "attw": "dist/index.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@arethetypeswrong/cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@arethetypeswrong/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@arethetypeswrong/core": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.18.5.tgz", + "integrity": "sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@andrewbranch/untar.js": "^1.0.3", + "@loaderkit/resolve": "^1.0.2", + "cjs-module-lexer": "^1.2.3", + "fflate": "^0.8.3", + "lru-cache": "^11.0.1", + "semver": "^7.5.4", + "typescript": "5.6.1-rc", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/typescript": { + "version": "5.6.1-rc", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.1-rc.tgz", + "integrity": "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "5.1.11", "dev": true, @@ -461,6 +581,13 @@ } } }, + "node_modules/@braidai/lang": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@braidai/lang/-/lang-1.1.2.tgz", + "integrity": "sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==", + "dev": true, + "license": "ISC" + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "dev": true, @@ -472,6 +599,289 @@ "specificity": "bin/cli.js" } }, + "node_modules/@changesets/apply-release-plan": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-8.0.0.tgz", + "integrity": "sha512-kUd2pbf1w5/AYmBMb0Tt+rkIPCjFJdT0SZMrkOjJT/WV/QbtmvkyB5jkV0oaNPheavprZk+SfUiozUti7TIL2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/config": "^4.0.0", + "@changesets/format": "^0.1.1", + "@changesets/git": "^4.0.0", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "import-meta-resolve": "^4.2.0", + "jsonc-parser": "^3.3.1", + "semver": "^7.8.1" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/assemble-release-plan": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-7.0.0.tgz", + "integrity": "sha512-oEW8BxdA604kGGtDSCiHr5w9Tv4UWe9I2k61IBNZzCOE1kbYaJj4v+lFQNgcEZFkUc2pV/+hASErGDvpJOZCTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^1.0.0", + "@changesets/get-dependents-graph": "^3.0.0", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "semver": "^7.8.1" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/changelog-git": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-1.0.0.tgz", + "integrity": "sha512-3Dst2Ime2Op5nd4XmWJLPIgp11ZFqJqSkVug9izK6TDcIV4YlhPS4ECbEVR+eGI0bk0r1ItogD4j2Oli87bJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/cli": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-3.0.0.tgz", + "integrity": "sha512-V7Gm+GP5OT3mJinMI2YcJD/JyO/a4WChnaMCCzTRhWHgu1zhtsyY7zCjP6n5W6zsgSjJKs+yPmvtREvE0F2g8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/apply-release-plan": "^8.0.0", + "@changesets/assemble-release-plan": "^7.0.0", + "@changesets/changelog-git": "^1.0.0", + "@changesets/config": "^4.0.0", + "@changesets/errors": "^1.0.0", + "@changesets/get-dependents-graph": "^3.0.0", + "@changesets/git": "^4.0.0", + "@changesets/pre": "^3.0.0", + "@changesets/read": "^1.0.0", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "@changesets/write": "^1.0.0", + "@clack/prompts": "^1.7.0", + "@manypkg/get-packages": "^3.1.0", + "@pnpm/deps.graph-sequencer": "^1100.0.1", + "cac": "^7.0.0", + "import-meta-resolve": "^4.2.0", + "launch-editor": "^2.14.1", + "package-manager-detector": "^1.6.0", + "semver": "^7.8.1", + "tinyexec": "^1.3.0" + }, + "bin": { + "changeset": "bin.js" + }, + "engines": { + "node": "^22.11 || ^24 || >=26", + "npm": ">=10.9.0", + "pnpm": ">=10.0.0", + "yarn": ">=4.5.2" + } + }, + "node_modules/@changesets/cli/node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@changesets/config": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-4.0.0.tgz", + "integrity": "sha512-mw95/YrkOuhZZxfnVAA4bSXOFUi+KlhzOBTM8C4x777NhUU6HWIl9Z+K+nME+E4PVsv5NQVQwTfiHihAS1A/ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/get-dependents-graph": "^3.0.0", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "@manypkg/get-packages": "^3.1.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-1.0.0.tgz", + "integrity": "sha512-ElN/mEzn6zmETgjwf5MclCMa9ef59sAR0lfO8VSYIsiRvbC2FbLB/92EoYw10Sl0kGixxHFiJZUSv7dA+YpR8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/format": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@changesets/format/-/format-0.1.2.tgz", + "integrity": "sha512-Caez5XtNXCFS/G5bwyav3wuXL0tMxVd2ZGbaumWbzN08tyzO21asCw7JZhNtVsAZDCvDRUzZN+Iit9SyRITSYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.8.0", + "tinyexec": "^1.3.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/get-dependents-graph": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-3.0.0.tgz", + "integrity": "sha512-ji/t5wFA1zREKXRUePE6Qi+Qu2UgxCeSSGQrphezwvQZrp49B7sJ+8+wvM0tA7zPeSxYKCojDy3WWgrl+s+awg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0", + "semver": "^7.8.1" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/git": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-4.0.0.tgz", + "integrity": "sha512-uIEswpPUgzBBqrC0qg13byNaPorzhf05LE2T+gRizEKCXvMsJC6NPJp5iNDSV/gYj4Viqh09UiOF5E7XWeH5lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^1.0.0", + "@changesets/types": "^7.0.0", + "@manypkg/get-packages": "^3.1.0", + "picomatch": "^4.0.4", + "tinyexec": "^1.3.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-1.0.0.tgz", + "integrity": "sha512-P0iaMb9p9CRYZiTgAllEIF9AUMQHIy1G72tKlcIqJp61icZSsKQNiOPdxAMZG8m/DvZwt/oz5xEbTpike//dWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0", + "yaml": "^2.9.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/pre": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-3.0.0.tgz", + "integrity": "sha512-Zm/6YliV/a2oeWTqHJf6KxLrQwgcK1i/BRDl2m0EKZvbnxV5fG9QRhwJJGshjsZTUTS6dkfURQ2K6aAvgNw/3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^1.0.0", + "@changesets/types": "^7.0.0", + "@manypkg/get-packages": "^3.1.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/read": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-1.0.0.tgz", + "integrity": "sha512-8TdE2PwG6yArPt5Ozej83Z6iHz1G8BDBKvIEKZ455MccR4K3GNbLR2XqjBxa+5yLFnEd3xAOPXYboBg1pt8stQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/git": "^4.0.0", + "@changesets/parse": "^1.0.0", + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/should-skip-package": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-1.0.0.tgz", + "integrity": "sha512-pwqoJmbONn1XgXmZXPEExgAaT+HdZLjALFTDgIm+PnS5KeO2nLtzA2/Q+4aMFY14kFMuXKG30MObhvDzWgzDgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/types": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-7.0.0.tgz", + "integrity": "sha512-c5GoiQyt3pxiXjrWSNoP8/GRf4kG+VnKzovx1OQM8dYYALlSwgedmkPmJ+ZqGxqwg9D3Bkj85Uo4KLd5BN3A0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/write": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-1.0.0.tgz", + "integrity": "sha512-xCK3/4C7Z7muQB/wguE6HcbjtY9iOtaK9orZKE+7xi573cMGD8rLZz7qlo6IvK7s+SIUIKajzj1l+F1QuP97tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/format": "^0.1.1", + "@changesets/types": "^7.0.0", + "human-id": "^4.2.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@clack/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.3.tgz", + "integrity": "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.7.0.tgz", + "integrity": "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "1.4.3", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, "node_modules/@cloudflare/kv-asset-handler": { "version": "0.5.0", "dev": true, @@ -579,38 +989,49 @@ "node": ">=16" } }, - "node_modules/@codra/api": { + "node_modules/@codraoss/api": { "resolved": "packages/api", "link": true }, - "node_modules/@codra/core": { + "node_modules/@codraoss/core": { "resolved": "packages/core", "link": true }, - "node_modules/@codra/db": { + "node_modules/@codraoss/db": { "resolved": "packages/db", "link": true }, - "node_modules/@codra/models": { + "node_modules/@codraoss/models": { "resolved": "packages/models", "link": true }, - "node_modules/@codra/provider-github": { + "node_modules/@codraoss/provider-github": { "resolved": "packages/provider-github", "link": true }, - "node_modules/@codra/schema": { + "node_modules/@codraoss/schema": { "resolved": "packages/schema", "link": true }, - "node_modules/@codra/ui": { + "node_modules/@codraoss/ui": { "resolved": "packages/ui", "link": true }, - "node_modules/@codra/worker": { + "node_modules/@codraoss/worker": { "resolved": "apps/worker", "link": true }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -2092,45 +2513,130 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", - "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "node_modules/@loaderkit/resolve": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.6.tgz", + "integrity": "sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@braidai/lang": "^1.0.0" + } + }, + "node_modules/@manypkg/find-root": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-3.1.0.tgz", + "integrity": "sha512-BcSqCyKhBVZ5YkSzOiheMCV41kqAFptW6xGqYSTjkVTl9XQpr+pqHhwgGCOHQtjDCv7Is6EFyA14Sm5GVbVABA==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.3" + "@manypkg/tools": "^2.1.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + "node": ">=20.0.0" } }, - "node_modules/@oxc-project/types": { - "version": "0.137.0", + "node_modules/@manypkg/get-packages": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-3.1.0.tgz", + "integrity": "sha512-0TbBVyvPrP7xGYBI/cP8UP+yl/z+HtbTttAD7FMAJgn/kXOTwh5/60TsqP9ZYY710forNfyV0N8P/IE/ujGZJg==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "dependencies": { + "@manypkg/find-root": "^3.1.0", + "@manypkg/tools": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@poppinss/colors": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", - "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "node_modules/@manypkg/tools": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@manypkg/tools/-/tools-2.1.2.tgz", + "integrity": "sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==", "dev": true, "license": "MIT", "dependencies": { - "kleur": "^4.1.5" - } + "jju": "^1.4.0", + "tinyglobby": "^0.2.13", + "yaml": "^2.9.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pnpm/deps.graph-sequencer": { + "version": "1100.0.1", + "resolved": "https://registry.npmjs.org/@pnpm/deps.graph-sequencer/-/deps.graph-sequencer-1100.0.1.tgz", + "integrity": "sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.13" + }, + "funding": { + "url": "https://opencollective.com/pnpm" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } }, "node_modules/@poppinss/colors/node_modules/kleur": { "version": "4.1.5", @@ -2174,6 +2680,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@publint/pack": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.6.tgz", + "integrity": "sha512-3uVNyGcVplhPZSLVyeIpL7+cIRn1YCSNHLG/rUIlBQMVH8YuN9++YF+5+UDIIO9RW98dujiUoTltO7RDB5bFJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyexec": "^1.2.4" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.12.0", "license": "MIT", @@ -2484,74 +3006,24 @@ "dev": true, "license": "MIT" }, - "node_modules/@sindresorhus/is": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", - "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@speed-highlight/core": { - "version": "1.2.23", - "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.23.tgz", - "integrity": "sha512-iRoq6i6JDJP6Mt2A5JaPvzw0pgYHH6k92ij+yXiTrB7T2y9N789aWE3EHWj/5ztlJBokcCBja3iYLVdu5wgnkg==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/@standard-schema/utils": { - "version": "0.3.0", - "license": "MIT" - }, - "node_modules/@tailwindcss/node": { - "version": "4.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "5.21.6", - "jiti": "^2.7.0", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.3.2" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.3.2", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-x64": "4.3.2", - "@tailwindcss/oxide-freebsd-x64": "4.3.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-x64-musl": "4.3.2", - "@tailwindcss/oxide-wasm32-wasi": "4.3.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", - "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", "cpu": [ "arm64" ], @@ -2560,15 +3032,12 @@ "optional": true, "os": [ "android" - ], - "engines": { - "node": ">= 20" - } + ] }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", - "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", "cpu": [ "arm64" ], @@ -2577,15 +3046,12 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">= 20" - } + ] }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", - "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", "cpu": [ "x64" ], @@ -2594,15 +3060,26 @@ "optional": true, "os": [ "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" ], - "engines": { - "node": ">= 20" - } + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", - "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", "cpu": [ "x64" ], @@ -2611,32 +3088,46 @@ "optional": true, "os": [ "freebsd" - ], - "engines": { - "node": ">= 20" - } + ] }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", - "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" ], - "engines": { - "node": ">= 20" - } + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", - "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", "cpu": [ "arm64" ], @@ -2648,15 +3139,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 20" - } + ] }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", - "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", "cpu": [ "arm64" ], @@ -2668,17 +3156,14 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 20" - } + ] }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", - "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", "cpu": [ - "x64" + "loong64" ], "dev": true, "libc": [ @@ -2688,17 +3173,14 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 20" - } + ] }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", - "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", "cpu": [ - "x64" + "loong64" ], "dev": true, "libc": [ @@ -2708,3146 +3190,4907 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 20" - } + ] }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", - "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", "cpu": [ - "wasm32" + "ppc64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/core": "^1.11.1", - "@emnapi/runtime": "^1.11.1", - "@emnapi/wasi-threads": "^1.2.2", - "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.2", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } + "os": [ + "linux" + ] }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", - "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", "cpu": [ - "arm64" + "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ - "win32" + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" ], - "engines": { - "node": ">= 20" - } + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.2", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ - "win32" + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" ], - "engines": { - "node": ">= 20" - } + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@tailwindcss/vite": { - "version": "4.3.2", + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.3.2", - "@tailwindcss/oxide": "4.3.2", - "tailwindcss": "4.3.2" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" - } + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@testing-library/react": { - "version": "16.3.2", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@testing-library/dom": "^10.0.0", - "@types/react": "^18.0.0 || ^19.0.0", - "@types/react-dom": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "os": [ + "win32" + ] }, - "node_modules/@types/aria-query": { - "version": "5.0.4", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@types/chai": { - "version": "5.2.3", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "license": "MIT" + "node_modules/@speed-highlight/core": { + "version": "1.2.23", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.23.tgz", + "integrity": "sha512-iRoq6i6JDJP6Mt2A5JaPvzw0pgYHH6k92ij+yXiTrB7T2y9N789aWE3EHWj/5ztlJBokcCBja3iYLVdu5wgnkg==", + "dev": true, + "license": "CC0-1.0" }, - "node_modules/@types/d3-color": { - "version": "3.1.3", + "node_modules/@standard-schema/spec": { + "version": "1.1.0", "license": "MIT" }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", + "node_modules/@standard-schema/utils": { + "version": "0.3.0", "license": "MIT" }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", + "node_modules/@tailwindcss/node": { + "version": "4.3.2", + "dev": true, "license": "MIT", "dependencies": { - "@types/d3-color": "*" + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.2" } }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", + "node_modules/@tailwindcss/oxide": { + "version": "4.3.2", + "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-time": "*" + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-x64": "4.3.2", + "@tailwindcss/oxide-freebsd-x64": "4.3.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-x64-musl": "4.3.2", + "@tailwindcss/oxide-wasm32-wasi": "4.3.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" } }, - "node_modules/@types/d3-shape": { - "version": "3.1.8", + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", + "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-path": "*" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "license": "MIT" - }, - "node_modules/@types/debug": { - "version": "4.1.13", + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", + "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/ms": "*" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", + "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", "license": "MIT", - "dependencies": { - "@types/estree": "*" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@types/hast": { - "version": "3.0.4", + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", + "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/unist": "*" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", + "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", "license": "MIT", - "dependencies": { - "@types/unist": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@types/ms": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.9.4", + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", + "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@types/picomatch": { - "version": "4.0.3", + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", + "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", - "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@types/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", + "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@types/unist": { - "version": "3.0.3", - "license": "MIT" - }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.6", - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", - "integrity": "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==", + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", + "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/type-utils": "8.66.0", - "@typescript-eslint/utils": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.66.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": ">= 20" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", - "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", + "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", - "debug": "^4.4.3" + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": ">=14.0.0" } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", - "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", + "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.66.0", - "@typescript-eslint/types": "^8.66.0", - "debug": "^4.4.3" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "node": ">= 20" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", - "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.2", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 20" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", - "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", + "node_modules/@tailwindcss/vite": { + "version": "4.3.2", "dev": true, "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "dependencies": { + "@tailwindcss/node": "4.3.2", + "@tailwindcss/oxide": "4.3.2", + "tailwindcss": "4.3.2" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz", - "integrity": "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==", + "node_modules/@testing-library/dom": { + "version": "10.4.1", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/utils": "8.66.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", - "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", - "dev": true, - "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", - "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", + "node_modules/@testing-library/react": { + "version": "16.3.2", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.66.0", - "@typescript-eslint/tsconfig-utils": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", - "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "tslib": "^2.4.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", - "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "node_modules/@types/aria-query": { + "version": "5.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.2", - "license": "ISC" + "node_modules/@types/d3-array": { + "version": "3.2.2", + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", - "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "node_modules/@types/d3-color": { + "version": "3.1.3", + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", - "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@types/d3-color": "*" + } }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", - "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@types/d3-time": "*" + } }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", - "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@types/d3-shape": { + "version": "3.1.8", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@types/d3-path": "*" + } }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", - "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.13", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@types/ms": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", - "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", - "cpu": [ - "arm" - ], + "node_modules/@types/deep-eql": { + "version": "4.0.2", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", - "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", - "cpu": [ - "arm" - ], + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/estree": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", - "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], + "node_modules/@types/hast": { + "version": "3.0.4", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/unist": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", - "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", - "cpu": [ - "arm64" - ], + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "libc": [ - "musl" - ], + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/unist": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", - "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", - "cpu": [ - "loong64" - ], + "node_modules/@types/ms": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.9.4", "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } }, - "node_modules/@unrs/resolver-binding-linux-loong64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", - "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", - "cpu": [ - "loong64" - ], + "node_modules/@types/picomatch": { + "version": "4.0.3", "dev": true, - "libc": [ - "musl" - ], + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "csstype": "^3.2.2" + } }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", - "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peerDependencies": { + "@types/react": "^19.2.0" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", - "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", - "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@types/unist": { + "version": "3.0.3", + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", - "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", - "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", - "cpu": [ - "x64" - ], + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", + "integrity": "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==", "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/type-utils": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.66.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", - "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", - "cpu": [ - "x64" - ], + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, - "libc": [ - "musl" - ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">= 4" + } }, - "node_modules/@unrs/resolver-binding-openharmony-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", - "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", - "cpu": [ - "arm64" - ], + "node_modules/@typescript-eslint/parser": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", + "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "dependencies": { + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", - "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", - "cpu": [ - "wasm32" - ], + "node_modules/@typescript-eslint/project-service": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", + "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@typescript-eslint/tsconfig-utils": "^8.66.0", + "@typescript-eslint/types": "^8.66.0", + "debug": "^4.4.3" }, "engines": { - "node": ">=14.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", + "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", + "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz", + "integrity": "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", - "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", - "cpu": [ - "arm64" - ], + "node_modules/@typescript-eslint/types": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", + "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", - "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", - "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@vitejs/plugin-react": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", - "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "^1.0.1" - }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "vite": "^8.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@vitest/expect": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", + "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "@typescript-eslint/project-service": "8.66.0", + "@typescript-eslint/tsconfig-utils": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@vitest/mocker": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "node_modules/@typescript-eslint/utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", + "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.10", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.1.0" + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@vitest/runner": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "node_modules/@ungap/structured-clone": { + "version": "1.3.2", + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.10", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@vitest/snapshot": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "@vitest/utils": "4.1.10", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@vitest/spy": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@vitest/utils": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/acorn": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", - "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "engines": { - "node": ">=8" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/ansi-styles": { - "version": "5.2.0", + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/aria-query": { - "version": "5.3.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/assertion-error": { - "version": "2.0.1", + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "engines": { - "node": ">=12" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/bail": { - "version": "2.0.2", + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", - "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/bidi-js": { - "version": "1.0.3", + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "require-from-string": "^2.0.2" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/blake3-wasm": { - "version": "2.1.5", + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "MIT" + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/browserslist": { - "version": "4.28.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", - "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "libc": [ + "musl" ], "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.44", - "caniuse-lite": "^1.0.30001806", - "electron-to-chromium": "^1.5.393", - "node-releases": "^2.0.51", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/caniuse-lite": { - "version": "1.0.30001806", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", - "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" ], - "license": "CC-BY-4.0" - }, - "node_modules/ccount": { - "version": "2.0.1", + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/chai": { - "version": "6.2.2", + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/chalk": { - "version": "5.6.2", + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/character-entities": { - "version": "2.0.2", + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/character-entities-html4": { - "version": "2.1.0", + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/cli-cursor": { - "version": "5.0.0", + "node_modules/@vitejs/plugin-react": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^5.0.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, - "node_modules/cli-spinners": { - "version": "3.4.0", + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18.20" + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, - "node_modules/cliui": { - "version": "8.0.1", + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" }, - "engines": { - "node": ">=12" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=6" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/color-convert": { - "version": "2.0.1", + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/color-name": { - "version": "1.1.4", + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, - "license": "MIT" - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", "license": "MIT", "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/vitest" } }, - "node_modules/comment-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.7.tgz", - "integrity": "sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==", + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 12.0.0" + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/concurrently": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", - "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", - "dependencies": { - "chalk": "4.1.2", - "rxjs": "7.8.2", - "shell-quote": "1.9.0", - "supports-color": "8.1.1", - "tree-kill": "1.2.2", - "yargs": "17.7.2" - }, "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" + "acorn": "bin/acorn" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + "node": ">=0.4.0" } }, - "node_modules/concurrently/node_modules/ansi-styles": { - "version": "4.3.0", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/concurrently/node_modules/chalk": { - "version": "4.1.2", + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "environment": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", + "node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", + "node_modules/ansi-styles": { + "version": "5.2.0", "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } + "license": "MIT" }, - "node_modules/css-tree": { - "version": "3.2.1", + "node_modules/aria-query": { + "version": "5.3.0", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "dequal": "^2.0.3" } }, - "node_modules/csstype": { - "version": "3.2.3", - "license": "MIT" - }, - "node_modules/d3-array": { - "version": "3.2.4", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, + "node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", "engines": { "node": ">=12" } }, - "node_modules/d3-color": { - "version": "3.1.0", - "license": "ISC", - "engines": { - "node": ">=12" + "node_modules/bail": { + "version": "2.0.2", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/d3-ease": { - "version": "3.0.1", - "license": "BSD-3-Clause", + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": "18 || 20 || >=22" } }, - "node_modules/d3-format": { - "version": "3.1.2", - "license": "ISC", + "node_modules/baseline-browser-mapping": { + "version": "2.11.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", + "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, "engines": { - "node": ">=12" + "node": ">=6.0.0" } }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "license": "ISC", + "node_modules/bidi-js": { + "version": "1.0.3", + "dev": true, + "license": "MIT", "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" + "require-from-string": "^2.0.2" } }, - "node_modules/d3-path": { - "version": "3.1.0", - "license": "ISC", + "node_modules/blake3-wasm": { + "version": "2.1.5", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, "engines": { - "node": ">=12" + "node": "20 || >=22" } }, - "node_modules/d3-scale": { - "version": "4.0.2", - "license": "ISC", + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">=12" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/d3-time": { - "version": "3.1.0", - "license": "ISC", + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-array": "2 - 3" + "load-tsconfig": "^0.2.3" }, "engines": { - "node": ">=12" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" } }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" - }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/d3-timer": { - "version": "3.0.1", - "license": "ISC", - "engines": { - "node": ">=12" + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/data-urls": { - "version": "7.0.0", + "node_modules/chai": { + "version": "6.2.2", "dev": true, "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" - }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=18" } }, - "node_modules/debug": { - "version": "4.4.3", + "node_modules/chalk": { + "version": "5.6.2", + "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/decimal.js": { - "version": "10.6.0", + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, - "license": "MIT" - }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10" + } }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", + "node_modules/character-entities": { + "version": "2.0.2", "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" + "node_modules/character-entities-html4": { + "version": "2.1.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "node_modules/dequal": { - "version": "2.0.3", + "node_modules/character-entities-legacy": { + "version": "3.0.0", "license": "MIT", - "engines": { - "node": ">=6" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/devlop": { - "version": "1.1.0", + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, "license": "MIT", "dependencies": { - "dequal": "^2.0.0" + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "dev": true, "license": "MIT" }, - "node_modules/electron-to-chromium": { - "version": "1.5.400", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.400.tgz", - "integrity": "sha512-96EWDNjM59SYflgeV5Ylsf4EMiq1a25YjCnJH7cxn/AF2H3pILRweaUnoLax0yKHWdpOzY6JKEu45e8irqZIHA==", - "dev": true, - "license": "ISC" + "node_modules/class-variance-authority": { + "version": "0.7.1", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } }, - "node_modules/emoji-regex": { - "version": "8.0.0", + "node_modules/cli-cursor": { + "version": "5.0.0", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/enhanced-resolve": { - "version": "5.21.6", + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" }, "engines": { - "node": ">=10.13.0" + "node": ">=8.0.0", + "npm": ">=5.0.0" } }, - "node_modules/entities": { - "version": "8.0.0", + "node_modules/cli-highlight/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=20.19.0" + "node": ">=8" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/error-stack-parser-es": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", - "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/es-module-lexer": { - "version": "2.2.0", + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "license": "MIT" - }, - "node_modules/es-toolkit": { - "version": "1.49.0", - "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } }, - "node_modules/esbuild": { - "version": "0.28.1", + "node_modules/cli-highlight/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } + "license": "MIT" }, - "node_modules/escalade": { - "version": "3.2.0", + "node_modules/cli-highlight/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", + "node_modules/cli-highlight/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/eslint": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", - "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", + "node_modules/cli-highlight/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", - "workspaces": [ - "packages/*" - ], "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.7.0", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" + "has-flag": "^4.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "node": ">=8" } }, - "node_modules/eslint-import-context": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", - "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", "dev": true, "license": "MIT", "dependencies": { - "get-tsconfig": "^4.10.1", - "stable-hash-x": "^0.2.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-import-context" - }, - "peerDependencies": { - "unrs-resolver": "^1.0.0" - }, - "peerDependenciesMeta": { - "unrs-resolver": { - "optional": true - } + "node": ">=10" } }, - "node_modules/eslint-import-resolver-typescript": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.5.tgz", - "integrity": "sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==", + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "license": "ISC", - "dependencies": { - "debug": "^4.4.1", - "eslint-import-context": "^0.1.8", - "get-tsconfig": "^4.10.1", - "is-bun-module": "^2.0.0", - "stable-hash-x": "^0.2.0", - "tinyglobby": "^0.2.14", - "unrs-resolver": "^1.7.11" - }, "engines": { - "node": "^16.17.0 || >=18.6.0" + "node": ">=10" + } + }, + "node_modules/cli-spinners": { + "version": "3.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20" }, "funding": { - "url": "https://opencollective.com/eslint-import-resolver-typescript" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*", - "eslint-plugin-import-x": "*" - }, - "peerDependenciesMeta": { - "eslint-plugin-import": { - "optional": true - }, - "eslint-plugin-import-x": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-import-x": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.17.1.tgz", - "integrity": "sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==", + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.56.0", - "comment-parser": "^1.4.1", - "debug": "^4.4.1", - "eslint-import-context": "^0.1.9", - "is-glob": "^4.0.3", - "minimatch": "^9.0.3 || ^10.1.2", - "semver": "^7.7.2", - "stable-hash-x": "^0.2.0", - "unrs-resolver": "^1.9.2" + "string-width": "^4.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-import-x" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^8.56.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "eslint-import-resolver-node": "*" + "node": "10.* || >= 12.*" }, - "peerDependenciesMeta": { - "@typescript-eslint/utils": { - "optional": true - }, - "eslint-import-resolver-node": { - "optional": true - } + "optionalDependencies": { + "@colors/colors": "1.5.0" } }, - "node_modules/eslint-plugin-react-hooks": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", - "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + "node": ">=8" } }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "node_modules/cli-table3/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8" } }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "node_modules/cliui": { + "version": "8.0.1", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=12" } }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", "dev": true, "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8" } }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, + "node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", "engines": { - "node": ">=0.10" + "node": ">=6" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/color-convert": { + "version": "2.0.1", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=4.0" + "node": ">=7.0.0" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/color-name": { + "version": "1.1.4", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } + "license": "MIT" }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", + "node_modules/comma-separated-tokens": { + "version": "2.0.3", "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/estree-walker": { - "version": "3.0.3", + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=14" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "license": "MIT" - }, - "node_modules/expect-type": { - "version": "1.4.0", + "node_modules/comment-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.7.tgz", + "integrity": "sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">= 12.0.0" } }, - "node_modules/extend": { - "version": "3.0.2", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", + "node_modules/concurrently": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", + "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12.0.0" + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.9.0", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "node_modules/concurrently/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=16" + "node": ">=8" } }, - "node_modules/flatted": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", - "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "dev": true, - "license": "ISC" - }, - "node_modules/framer-motion": { - "version": "12.42.2", - "license": "MIT", - "dependencies": { - "motion-dom": "^12.42.2", - "motion-utils": "^12.39.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } + "license": "MIT" }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^14.18.0 || >=16.10.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", + "node_modules/convert-source-map": { + "version": "2.0.0", "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } + "license": "MIT" }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "dev": true, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", "engines": { "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/get-tsconfig": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.1.tgz", - "integrity": "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "engines": { + "node": ">= 8" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/css-tree": { + "version": "3.2.1", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=10.13.0" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "dev": true, - "license": "ISC" + "node_modules/csstype": { + "version": "3.2.3", + "license": "MIT" }, - "node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", + "node_modules/d3-array": { + "version": "3.2.4", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.3", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "node_modules/d3-color": { + "version": "3.1.0", + "license": "ISC", + "engines": { + "node": ">=12" } }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "node_modules/d3-ease": { + "version": "3.0.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" } }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "license": "MIT", + "node_modules/d3-format": { + "version": "3.1.2", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "license": "ISC", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "d3-color": "1 - 3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=12" } }, - "node_modules/hast-util-raw/node_modules/entities": { - "version": "6.0.1", - "license": "BSD-2-Clause", + "node_modules/d3-path": { + "version": "3.1.0", + "license": "ISC", "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=12" } }, - "node_modules/hast-util-raw/node_modules/parse5": { - "version": "7.3.0", - "license": "MIT", + "node_modules/d3-scale": { + "version": "4.0.2", + "license": "ISC", "dependencies": { - "entities": "^6.0.0" + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">=12" } }, - "node_modules/hast-util-sanitize": { - "version": "5.0.2", - "license": "MIT", + "node_modules/d3-shape": { + "version": "3.2.0", + "license": "ISC", "dependencies": { - "@types/hast": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "unist-util-position": "^5.0.0" + "d3-path": "^3.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=12" } }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.6", - "license": "MIT", + "node_modules/d3-time": { + "version": "3.1.0", + "license": "ISC", "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" + "d3-array": "2 - 3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=12" } }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.1", - "license": "MIT", + "node_modules/d3-time-format": { + "version": "4.1.0", + "license": "ISC", "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "d3-time": "1 - 3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=12" } }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", + "node_modules/d3-timer": { + "version": "3.0.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "dev": true, "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/hastscript": { - "version": "9.0.1", + "node_modules/debug": { + "version": "4.4.3", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" + "ms": "^2.1.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "node_modules/decimal.js": { + "version": "10.6.0", "dev": true, "license": "MIT" }, - "node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, - "node_modules/hono": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz", - "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } + "node_modules/decimal.js-light": { + "version": "2.5.1", + "license": "MIT" }, - "node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "dev": true, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", "license": "MIT", "dependencies": { - "@exodus/bytes": "^1.6.0" + "character-entities": "^2.0.0" }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/html-url-attributes": { - "version": "3.0.1", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immer": { - "version": "10.2.0", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/imurmurhash": { + "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inline-style-parser": { - "version": "0.2.7", "license": "MIT" }, - "node_modules/internmap": { + "node_modules/dequal": { "version": "2.0.3", - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "node_modules/detect-libc": { + "version": "2.1.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" } }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", + "node_modules/devlop": { + "version": "1.1.0", "license": "MIT", "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" + "dequal": "^2.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-bun-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", - "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "node_modules/dom-accessibility-api": { + "version": "0.5.16", "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.7.1" - } + "license": "MIT" }, - "node_modules/is-decimal": { - "version": "2.0.1", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "node_modules/electron-to-chromium": { + "version": "1.5.400", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.400.tgz", + "integrity": "sha512-96EWDNjM59SYflgeV5Ylsf4EMiq1a25YjCnJH7cxn/AF2H3pILRweaUnoLax0yKHWdpOzY6JKEu45e8irqZIHA==", + "dev": true, + "license": "ISC" }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/emoji-regex": { + "version": "8.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/enhanced-resolve": { + "version": "5.21.6", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" } }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "license": "MIT", + "node_modules/entities": { + "version": "8.0.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/is-interactive": { - "version": "2.0.0", + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-plain-obj": { - "version": "4.1.0", + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", + "node_modules/es-module-lexer": { + "version": "2.2.0", "dev": true, "license": "MIT" }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", + "node_modules/es-toolkit": { + "version": "1.49.0", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.28.1", "dev": true, + "hasInstallScript": true, "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.7.0", + "node_modules/escalade": { + "version": "3.2.0", "dev": true, "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" + "engines": { + "node": ">=6" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/jsdom": { - "version": "29.1.1", + "node_modules/eslint": { + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", + "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { - "@asamuzakjp/css-color": "^5.1.11", - "@asamuzakjp/dom-selector": "^7.1.1", - "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.3", - "@exodus/bytes": "^1.15.0", - "css-tree": "^3.2.1", - "data-urls": "^7.0.0", - "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^6.0.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.3.5", - "parse5": "^8.0.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.1", - "undici": "^7.25.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.1", - "xml-name-validator": "^5.0.0" + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" }, "peerDependencies": { - "canvas": "^3.0.0" + "jiti": "*" }, "peerDependenciesMeta": { - "canvas": { + "jiti": { "optional": true } } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", "dev": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.5.tgz", + "integrity": "sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==", "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" }, "engines": { - "node": ">=6" - } - }, - "node_modules/jsonrepair": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.15.0.tgz", - "integrity": "sha512-wy8OTjwsJwQRnQJkKnMJJ9vcytRdBPAgIF/Hy6+s1dAj42BHMKiyL8JzEieIl3JY7idt8eyHwBWTO8mh/+mtwA==", - "license": "ISC", - "bin": { - "jsonrepair": "bin/cli.js" + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/eslint-plugin-import-x": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.17.1.tgz", + "integrity": "sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==", "dev": true, "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "dev": true, - "license": "MIT", + "@typescript-eslint/types": "^8.56.0", + "comment-parser": "^1.4.1", + "debug": "^4.4.1", + "eslint-import-context": "^0.1.9", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3 || ^10.1.2", + "semver": "^7.7.2", + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" + }, "engines": { - "node": ">=6" - } - }, - "node_modules/lenis": { - "version": "1.3.26", - "resolved": "https://registry.npmjs.org/lenis/-/lenis-1.3.26.tgz", - "integrity": "sha512-s/xTCZCxTFvHbAN1OzuhNaN5YPJH2ail0XAkctKW1b+RUAG4nUL5UHLXwNko1h8aEeT2jspBXegMgPJd8zcuag==", - "license": "MIT", - "workspaces": [ - "packages/*", - "playground", - "playground/*" - ], + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/darkroomengineering" + "url": "https://opencollective.com/eslint-plugin-import-x" }, "peerDependencies": { - "@nuxt/kit": ">=3.0.0", - "react": ">=17.0.0", - "vue": ">=3.0.0" + "@typescript-eslint/utils": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "eslint-import-resolver-node": "*" }, "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - }, - "react": { + "@typescript-eslint/utils": { "optional": true }, - "vue": { + "eslint-import-resolver-node": { "optional": true } } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, - "node_modules/lightningcss": { - "version": "1.32.0", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 12.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "url": "https://opencollective.com/eslint" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], + "license": "Apache-2.0", "engines": { - "node": ">= 12.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/eslint" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, "engines": { - "node": ">= 12.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/eslint" } }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">=4.0" } }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "BSD-2-Clause", "engines": { - "node": ">= 12.0.0" - }, + "node": ">=4.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "license": "MIT", "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], + "node_modules/estree-walker": { + "version": "3.0.3", "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=0.10.0" } }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], + "node_modules/eventemitter3": { + "version": "5.0.4", + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.4.0", "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=12.0.0" } }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], + "node_modules/extend": { + "version": "3.0.2", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "libc": [ - "glibc" + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/framer-motion": { + "version": "12.42.2", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.42.2", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.1.tgz", + "integrity": "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/entities": { + "version": "6.0.1", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/hast-util-raw/node_modules/parse5": { + "version": "7.3.0", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/hast-util-sanitize": { + "version": "5.0.2", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "unist-util-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hono": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz", + "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/human-id": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.2.0.tgz", + "integrity": "sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.2.0", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true, + "license": "MIT" + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "29.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonrepair": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.15.0.tgz", + "integrity": "sha512-wy8OTjwsJwQRnQJkKnMJJ9vcytRdBPAgIF/Hy6+s1dAj42BHMKiyL8JzEieIl3JY7idt8eyHwBWTO8mh/+mtwA==", + "license": "ISC", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/launch-editor": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.4" + } + }, + "node_modules/lenis": { + "version": "1.3.26", + "resolved": "https://registry.npmjs.org/lenis/-/lenis-1.3.26.tgz", + "integrity": "sha512-s/xTCZCxTFvHbAN1OzuhNaN5YPJH2ail0XAkctKW1b+RUAG4nUL5UHLXwNko1h8aEeT2jspBXegMgPJd8zcuag==", + "license": "MIT", + "workspaces": [ + "packages/*", + "playground", + "playground/*" + ], + "funding": { + "type": "github", + "url": "https://github.com/sponsors/darkroomengineering" + }, + "peerDependencies": { + "@nuxt/kit": ">=3.0.0", + "react": ">=17.0.0", + "vue": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "react": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.5.1", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lucide-react": { + "version": "1.22.0", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" + } + }, + "node_modules/marked-terminal/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "dev": true, - "libc": [ - "musl" + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/log-symbols": { - "version": "7.0.1", + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mimic-function": { + "version": "5.0.1", "dev": true, "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0", - "yoctocolors": "^2.1.1" - }, "engines": { "node": ">=18" }, @@ -5855,959 +8098,881 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/longest-streak": { - "version": "3.1.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lru-cache": { - "version": "11.5.1", + "node_modules/miniflare": { + "version": "5.20260801.1-alpha", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260801.1-alpha.tgz", + "integrity": "sha512-BHPVzIDA6mbx7LefxpvkXW7DHx9FKB9GorZatbnrrFTt3CVMU8zuUpbgyCuebwDKcTTOZos43ta8GQ0eMVEpxA==", "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/lucide-react": { - "version": "1.22.0", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "0.35.2", + "undici": "7.29.0", + "workerd": "1.20260801.1", + "ws": "8.21.0", + "youch": "4.1.0-beta.10" + }, + "engines": { + "node": ">=22.0.0" } }, - "node_modules/lz-string": { - "version": "1.5.0", + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/magic-string": { - "version": "0.30.21", + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" } }, - "node_modules/markdown-table": { - "version": "3.0.4", + "node_modules/motion": { + "version": "12.42.2", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "framer-motion": "^12.42.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", + "node_modules/motion-dom": { + "version": "12.42.2", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "motion-utils": "^12.39.0" } }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.3", + "node_modules/motion-utils": { + "version": "12.39.0", + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=4" } }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, "license": "MIT", "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" } }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" + "bin": { + "nanoid": "bin/nanoid.cjs" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/napi-postinstall" } }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=18" } }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", + "node_modules/node-emoji/node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "engines": { + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", + "node_modules/node-releases": { + "version": "2.0.52", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.52.tgz", + "integrity": "sha512-MRlTqhAfoMx/4mhEbPo3Hi02g9LJZaJkka69V6h67Cb1gjrAG0jsTE4CZX1eptNx+VCAwJmfpnDIF4P0Nh1A7A==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=18" } }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", + "node_modules/obug": { + "version": "2.1.3", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=12.20.0" } }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", + "node_modules/onetime": { + "version": "7.0.0", + "dev": true, "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", + "node_modules/ora": { + "version": "9.4.1", + "dev": true, "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" + "chalk": "^5.6.2", + "cli-cursor": "^5.0.0", + "cli-spinners": "^3.2.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.3.2", + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0" + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdn-data": { - "version": "2.27.1", + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", "dev": true, - "license": "CC0-1.0" + "license": "MIT" }, - "node_modules/micromark": { + "node_modules/parse-entities": { "version": "4.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "license": "MIT" }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", + "node_modules/parse5": { + "version": "8.0.1", + "dev": true, "license": "MIT", "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" + "entities": "^8.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "parse5": "^6.0.1" } }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=8" } }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=8" } }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", + "node_modules/path-to-regexp": { + "version": "6.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 6" } }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, "license": "MIT", "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, "funding": [ { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, "funding": [ { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } - ], + } + }, + "node_modules/postgres": { + "version": "3.4.9", + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/pretty-format": { + "version": "27.5.1", + "dev": true, "license": "MIT", "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "dev": true, "license": "MIT", "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/property-information": { + "version": "7.2.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/publint": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.23.tgz", + "integrity": "sha512-5MQipUPcB7MWw84zLUkHrg/H/UBtk3LL+A0GngTTBSsiNJLQurMUaSIRG3edlOrRz4UFe0AOKK9TZdIWviV+jQ==", + "dev": true, "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@publint/pack": "^0.1.6", + "package-manager-detector": "^1.7.0", + "picocolors": "^1.1.1", + "sade": "^1.8.1" + }, + "bin": { + "publint": "src/cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" } }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" } }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/react-is": { + "version": "19.2.7", "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } + "peer": true }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/react-markdown": { + "version": "10.1.0", "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" } }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/react-redux": { + "version": "9.3.0", "license": "MIT", "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" + "redux": { + "optional": true } - ], - "license": "MIT" + } }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/react-router": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", + "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0" + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/react-router-dom": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", + "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", "license": "MIT", "dependencies": { - "micromark-util-types": "^2.0.0" + "react-router": "7.18.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" } }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/recharts": { + "version": "3.9.0", "license": "MIT", + "workspaces": [ + "www" + ], "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.2.0", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/redux": { + "version": "5.0.1", "license": "MIT" }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" + "node_modules/redux-thunk": { + "version": "3.1.0", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "dev": true, + "node_modules/rehype-raw": { + "version": "7.0.0", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/miniflare": { - "version": "5.20260801.1-alpha", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260801.1-alpha.tgz", - "integrity": "sha512-BHPVzIDA6mbx7LefxpvkXW7DHx9FKB9GorZatbnrrFTt3CVMU8zuUpbgyCuebwDKcTTOZos43ta8GQ0eMVEpxA==", - "dev": true, + "node_modules/rehype-sanitize": { + "version": "6.0.0", "license": "MIT", "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "sharp": "0.35.2", - "undici": "7.29.0", - "workerd": "1.20260801.1", - "ws": "8.21.0", - "youch": "4.1.0-beta.10" + "@types/hast": "^3.0.0", + "hast-util-sanitize": "^5.0.0" }, - "engines": { - "node": ">=22.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "license": "BlueOak-1.0.0", + "node_modules/remark-gfm": { + "version": "4.0.1", + "license": "MIT", "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/motion": { - "version": "12.42.2", + "node_modules/remark-parse": { + "version": "11.0.0", "license": "MIT", "dependencies": { - "framer-motion": "^12.42.2", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/motion-dom": { - "version": "12.42.2", + "node_modules/remark-rehype": { + "version": "11.1.2", "license": "MIT", "dependencies": { - "motion-utils": "^12.39.0" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/motion-utils": { - "version": "12.39.0", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "license": "MIT" + "node_modules/remark-stringify": { + "version": "11.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "node_modules/require-directory": { + "version": "2.1.1", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=0.10.0" } }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "node_modules/require-from-string": { + "version": "2.0.2", "dev": true, "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" + "node": ">=0.10.0" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, + "node_modules/reselect": { + "version": "5.2.0", "license": "MIT" }, - "node_modules/node-releases": { - "version": "2.0.52", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.52.tgz", - "integrity": "sha512-MRlTqhAfoMx/4mhEbPo3Hi02g9LJZaJkka69V6h67Cb1gjrAG0jsTE4CZX1eptNx+VCAwJmfpnDIF4P0Nh1A7A==", + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/obug": { - "version": "2.1.3", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], "license": "MIT", - "engines": { - "node": ">=12.20.0" + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/onetime": { - "version": "7.0.0", + "node_modules/restore-cursor": { + "version": "5.1.0", "dev": true, "license": "MIT", "dependencies": { - "mimic-function": "^5.0.0" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { "node": ">=18" @@ -6816,994 +8981,1192 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "node_modules/rolldown": { + "version": "1.1.3", "dev": true, "license": "MIT", "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" + "@oxc-project/types": "=0.137.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">= 0.8.0" + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.3", + "@rolldown/binding-darwin-arm64": "1.1.3", + "@rolldown/binding-darwin-x64": "1.1.3", + "@rolldown/binding-freebsd-x64": "1.1.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", + "@rolldown/binding-linux-arm64-gnu": "1.1.3", + "@rolldown/binding-linux-arm64-musl": "1.1.3", + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", + "@rolldown/binding-linux-s390x-gnu": "1.1.3", + "@rolldown/binding-linux-x64-gnu": "1.1.3", + "@rolldown/binding-linux-x64-musl": "1.1.3", + "@rolldown/binding-openharmony-arm64": "1.1.3", + "@rolldown/binding-wasm32-wasi": "1.1.3", + "@rolldown/binding-win32-arm64-msvc": "1.1.3", + "@rolldown/binding-win32-x64-msvc": "1.1.3" } - }, - "node_modules/ora": { - "version": "9.4.1", + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.6.2", - "cli-cursor": "^5.0.0", - "cli-spinners": "^3.2.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.1.0", - "log-symbols": "^7.0.1", - "stdin-discarder": "^0.3.2", - "string-width": "^8.1.0" + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=20" + "node": ">=18.0.0", + "npm": ">=8.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/rxjs": { + "version": "7.8.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "mri": "^1.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/saxes": { + "version": "6.0.0", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "p-limit": "^3.0.2" + "xmlchars": "^2.2.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=v12.22.7" } }, - "node_modules/parse-entities": { - "version": "4.0.2", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" + "node_modules/scheduler": { + "version": "0.27.0", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=10" } }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, - "node_modules/parse5": { - "version": "8.0.1", + "node_modules/sharp": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz", + "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "entities": "^8.0.0" + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.4" + }, + "engines": { + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.2", + "@img/sharp-darwin-x64": "0.35.2", + "@img/sharp-freebsd-wasm32": "0.35.2", + "@img/sharp-libvips-darwin-arm64": "1.3.1", + "@img/sharp-libvips-darwin-x64": "1.3.1", + "@img/sharp-libvips-linux-arm": "1.3.1", + "@img/sharp-libvips-linux-arm64": "1.3.1", + "@img/sharp-libvips-linux-ppc64": "1.3.1", + "@img/sharp-libvips-linux-riscv64": "1.3.1", + "@img/sharp-libvips-linux-s390x": "1.3.1", + "@img/sharp-libvips-linux-x64": "1.3.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", + "@img/sharp-libvips-linuxmusl-x64": "1.3.1", + "@img/sharp-linux-arm": "0.35.2", + "@img/sharp-linux-arm64": "0.35.2", + "@img/sharp-linux-ppc64": "0.35.2", + "@img/sharp-linux-riscv64": "0.35.2", + "@img/sharp-linux-s390x": "0.35.2", + "@img/sharp-linux-x64": "0.35.2", + "@img/sharp-linuxmusl-arm64": "0.35.2", + "@img/sharp-linuxmusl-x64": "0.35.2", + "@img/sharp-webcontainers-wasm32": "0.35.2", + "@img/sharp-win32-arm64": "0.35.2", + "@img/sharp-win32-ia32": "0.35.2", + "@img/sharp-win32-x64": "0.35.2" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-to-regexp": { - "version": "6.3.0", - "dev": true, - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", + "node_modules/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "node_modules/siginfo": { + "version": "2.0.0", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.17", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } + "license": "ISC" }, - "node_modules/postgres": { - "version": "3.4.9", - "license": "Unlicense", + "node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", "engines": { - "node": ">=12" + "node": ">=14" }, "funding": { - "type": "individual", - "url": "https://github.com/sponsors/porsager" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/sisteransi": { + "version": "1.0.5", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } + "license": "MIT" }, - "node_modules/pretty-format": { - "version": "27.5.1", + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "unicode-emoji-modifier-base": "^1.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=8" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "dev": true, - "license": "MIT" + "node_modules/sonner": { + "version": "2.0.7", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } }, - "node_modules/prompts": { - "version": "2.4.2", + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, - "node_modules/property-information": { - "version": "7.2.0", + "node_modules/space-separated-tokens": { + "version": "2.0.2", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/punycode": { - "version": "2.3.1", + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" - } - }, - "node_modules/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", - "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node": ">=12.0.0" } }, - "node_modules/react-dom": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", - "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.8" - } + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" }, - "node_modules/react-is": { - "version": "19.2.7", - "license": "MIT", - "peer": true + "node_modules/std-env": { + "version": "4.1.0", + "dev": true, + "license": "MIT" }, - "node_modules/react-markdown": { - "version": "10.1.0", + "node_modules/stdin-discarder": { + "version": "0.3.2", + "dev": true, "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "html-url-attributes": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "unified": "^11.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" + "engines": { + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=18", - "react": ">=18" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-redux": { - "version": "9.3.0", + "node_modules/string-width": { + "version": "8.2.1", + "dev": true, "license": "MIT", "dependencies": { - "@types/use-sync-external-store": "^0.0.6", - "use-sync-external-store": "^1.4.0" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, - "peerDependencies": { - "@types/react": "^18.2.25 || ^19", - "react": "^18.0 || ^19", - "redux": "^5.0.0" + "engines": { + "node": ">=20" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "redux": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-router": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", - "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", + "node_modules/stringify-entities": { + "version": "4.0.4", "license": "MIT", "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/react-router-dom": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", - "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", + "node_modules/strip-ansi": { + "version": "7.2.0", + "dev": true, "license": "MIT", "dependencies": { - "react-router": "7.18.2" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=12" }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/recharts": { - "version": "3.9.0", + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, "license": "MIT", - "workspaces": [ - "www" - ], - "dependencies": { - "@reduxjs/toolkit": "^1.9.0 || 2.x.x", - "clsx": "^2.1.1", - "decimal.js-light": "^2.5.1", - "es-toolkit": "^1.39.3", - "eventemitter3": "^5.0.1", - "immer": "^10.1.1", - "react-redux": "8.x.x || 9.x.x", - "reselect": "5.2.0", - "tiny-invariant": "^1.3.3", - "use-sync-external-store": "^1.2.2", - "victory-vendor": "^37.0.2" - }, "engines": { - "node": ">=18" + "node": ">=12" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/redux": { - "version": "5.0.1", - "license": "MIT" - }, - "node_modules/redux-thunk": { - "version": "3.1.0", - "license": "MIT", - "peerDependencies": { - "redux": "^5.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/rehype-raw": { - "version": "7.0.0", + "node_modules/style-to-js": { + "version": "1.1.21", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "style-to-object": "1.0.14" } }, - "node_modules/rehype-sanitize": { - "version": "6.0.0", + "node_modules/style-to-object": { + "version": "1.0.14", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-sanitize": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "inline-style-parser": "0.2.7" } }, - "node_modules/remark-gfm": { - "version": "4.0.1", + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" } }, - "node_modules/remark-parse": { - "version": "11.0.0", + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 6" } }, - "node_modules/remark-rehype": { - "version": "11.1.2", + "node_modules/sugar-high": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sugar-high/-/sugar-high-2.0.0.tgz", + "integrity": "sha512-qZQaa+3vbmKSx00G465bY1LqVUU0RaUkY/7xDV63cQzezD6dF/DJCthztRQqYLGA7HXdObvCEtcCRpvoIrrbCg==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "8.1.1", + "dev": true, "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/remark-stringify": { - "version": "11.0.0", + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, - "node_modules/require-directory": { - "version": "2.1.1", + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/require-from-string": { - "version": "2.0.2", + "node_modules/symbol-tree": { + "version": "3.2.4", "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", "license": "MIT", - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" } }, - "node_modules/reselect": { - "version": "5.2.0", + "node_modules/tailwindcss": { + "version": "4.3.2", + "dev": true, "license": "MIT" }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "node_modules/tapable": { + "version": "2.3.3", "dev": true, "license": "MIT", + "engines": { + "node": ">=6" + }, "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "any-promise": "^1.0.0" } }, - "node_modules/rolldown": { - "version": "1.1.3", + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.137.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" + "thenify": ">= 3.1.0 < 4" }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.3", - "@rolldown/binding-darwin-arm64": "1.1.3", - "@rolldown/binding-darwin-x64": "1.1.3", - "@rolldown/binding-freebsd-x64": "1.1.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", - "@rolldown/binding-linux-arm64-gnu": "1.1.3", - "@rolldown/binding-linux-arm64-musl": "1.1.3", - "@rolldown/binding-linux-ppc64-gnu": "1.1.3", - "@rolldown/binding-linux-s390x-gnu": "1.1.3", - "@rolldown/binding-linux-x64-gnu": "1.1.3", - "@rolldown/binding-linux-x64-musl": "1.1.3", - "@rolldown/binding-openharmony-arm64": "1.1.3", - "@rolldown/binding-wasm32-wasi": "1.1.3", - "@rolldown/binding-win32-arm64-msvc": "1.1.3", - "@rolldown/binding-win32-x64-msvc": "1.1.3" + "node": ">=0.8" } }, - "node_modules/rxjs": { - "version": "7.8.2", + "node_modules/tiny-invariant": { + "version": "1.3.3", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" } }, - "node_modules/saxes": { - "version": "6.0.0", + "node_modules/tinyglobby": { + "version": "0.2.17", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "xmlchars": "^2.2.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=v12.22.7" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/scheduler": { - "version": "0.27.0", - "license": "MIT" + "node_modules/tinyrainbow": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "node_modules/tldts": { + "version": "7.4.5", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.5" }, - "engines": { - "node": ">=10" + "bin": { + "tldts": "bin/cli.js" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "node_modules/tldts-core": { + "version": "7.4.5", + "dev": true, "license": "MIT" }, - "node_modules/sharp": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz", - "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==", + "node_modules/tough-cookie": { + "version": "6.0.1", "dev": true, - "license": "Apache-2.0", + "license": "BSD-3-Clause", "dependencies": { - "@img/colour": "^1.1.0", - "detect-libc": "^2.1.2", - "semver": "^7.8.4" + "tldts": "^7.0.5" }, "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.2", - "@img/sharp-darwin-x64": "0.35.2", - "@img/sharp-freebsd-wasm32": "0.35.2", - "@img/sharp-libvips-darwin-arm64": "1.3.1", - "@img/sharp-libvips-darwin-x64": "1.3.1", - "@img/sharp-libvips-linux-arm": "1.3.1", - "@img/sharp-libvips-linux-arm64": "1.3.1", - "@img/sharp-libvips-linux-ppc64": "1.3.1", - "@img/sharp-libvips-linux-riscv64": "1.3.1", - "@img/sharp-libvips-linux-s390x": "1.3.1", - "@img/sharp-libvips-linux-x64": "1.3.1", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", - "@img/sharp-libvips-linuxmusl-x64": "1.3.1", - "@img/sharp-linux-arm": "0.35.2", - "@img/sharp-linux-arm64": "0.35.2", - "@img/sharp-linux-ppc64": "0.35.2", - "@img/sharp-linux-riscv64": "0.35.2", - "@img/sharp-linux-s390x": "0.35.2", - "@img/sharp-linux-x64": "0.35.2", - "@img/sharp-linuxmusl-arm64": "0.35.2", - "@img/sharp-linuxmusl-x64": "0.35.2", - "@img/sharp-webcontainers-wasm32": "0.35.2", - "@img/sharp-win32-arm64": "0.35.2", - "@img/sharp-win32-ia32": "0.35.2", - "@img/sharp-win32-x64": "0.35.2" + "node": ">=16" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/tr46": { + "version": "6.0.0", "dev": true, "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=8" + "node": ">=20" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/tree-kill": { + "version": "1.2.2", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "bin": { + "tree-kill": "cli.js" } }, - "node_modules/shell-quote": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", - "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", - "dev": true, + "node_modules/trim-lines": { + "version": "3.0.1", "license": "MIT", - "engines": { - "node": ">= 0.4" - }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/siginfo": { - "version": "2.0.0", - "dev": true, - "license": "ISC" + "node_modules/trough": { + "version": "2.2.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "node_modules/signal-exit": { - "version": "4.1.0", + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18.12" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "typescript": ">=4.8.4" } }, - "node_modules/sisteransi": { - "version": "1.0.5", + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", "dev": true, - "license": "MIT" - }, - "node_modules/sonner": { - "version": "2.0.7", "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, "peerDependencies": { - "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } } }, - "node_modules/source-map-js": { - "version": "1.2.1", + "node_modules/tsup/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", + "node_modules/tsup/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/stable-hash-x": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", - "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "node_modules/tsup/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=12.0.0" + "node": ">=18" } }, - "node_modules/stackback": { - "version": "0.0.2", + "node_modules/tsup/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/std-env": { - "version": "4.1.0", + "node_modules/tsup/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/stdin-discarder": { - "version": "0.3.2", + "node_modules/tsup/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/string-width": { - "version": "8.2.1", + "node_modules/tsup/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/stringify-entities": { - "version": "4.0.4", + "node_modules/tsup/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/strip-ansi": { - "version": "7.2.0", + "node_modules/tsup/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.2.2", + "node_modules/tsup/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=18" } }, - "node_modules/style-to-js": { - "version": "1.1.21", + "node_modules/tsup/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", - "dependencies": { - "style-to-object": "1.0.14" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/style-to-object": { - "version": "1.0.14", + "node_modules/tsup/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.7" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/sugar-high": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sugar-high/-/sugar-high-2.0.0.tgz", - "integrity": "sha512-qZQaa+3vbmKSx00G465bY1LqVUU0RaUkY/7xDV63cQzezD6dF/DJCthztRQqYLGA7HXdObvCEtcCRpvoIrrbCg==", - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "8.1.1", + "node_modules/tsup/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=18" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", + "node_modules/tsup/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/tailwind-merge": { - "version": "3.6.0", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/tailwindcss": { - "version": "4.3.2", + "node_modules/tsup/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/tapable": { - "version": "2.3.3", + "node_modules/tsup/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">=18" } }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.2.4", + "node_modules/tsup/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=18" } }, - "node_modules/tinyglobby": { - "version": "0.2.17", + "node_modules/tsup/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "node": ">=18" } }, - "node_modules/tinyrainbow": { - "version": "3.1.0", + "node_modules/tsup/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/tldts": { - "version": "7.4.5", + "node_modules/tsup/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "tldts-core": "^7.4.5" - }, - "bin": { - "tldts": "bin/cli.js" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/tldts-core": { - "version": "7.4.5", + "node_modules/tsup/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/tough-cookie": { - "version": "6.0.1", + "node_modules/tsup/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^7.0.5" - }, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=16" + "node": ">=18" } }, - "node_modules/tr46": { - "version": "6.0.0", + "node_modules/tsup/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=20" + "node": ">=18" } }, - "node_modules/tree-kill": { - "version": "1.2.2", + "node_modules/tsup/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "bin": { - "tree-kill": "cli.js" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/trim-lines": { - "version": "3.0.1", + "node_modules/tsup/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/trough": { - "version": "2.2.0", + "node_modules/tsup/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "node_modules/tsup/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { - "node": ">=18.12" + "node": ">=18" }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "license": "0BSD" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/tsup/node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" }, "node_modules/type-check": { "version": "0.4.0", @@ -7854,6 +10217,13 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", @@ -7877,6 +10247,16 @@ "pathe": "^2.0.3" } }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/unified": { "version": "11.0.5", "license": "MIT", @@ -8038,6 +10418,16 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/vfile": { "version": "6.0.3", "license": "MIT", @@ -8170,21 +10560,6 @@ } } }, - "node_modules/vite/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/vitest": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", @@ -8421,21 +10796,6 @@ } } }, - "node_modules/wrangler/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "dev": true, @@ -8540,6 +10900,22 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "17.7.2", "dev": true, @@ -8667,19 +11043,21 @@ } }, "packages/api": { - "name": "@codra/api", + "name": "@codraoss/api", "version": "0.9.4", + "license": "AGPL-3.0-only", "dependencies": { - "@codra/core": "*", - "@codra/db": "*", - "@codra/models": "*", - "@codra/provider-github": "*", - "@codra/schema": "*", + "@codraoss/core": "^0.9.4", + "@codraoss/db": "^0.9.4", + "@codraoss/models": "^0.9.4", + "@codraoss/provider-github": "^0.9.4", + "@codraoss/schema": "^0.9.4", "hono": "^4.12.25", "zod": "^4.3.6" }, "devDependencies": { - "@types/node": "^22.0.0" + "@types/node": "^22.0.0", + "tsup": "^8.0.0" } }, "packages/api/node_modules/@types/node": { @@ -8700,27 +11078,32 @@ "license": "MIT" }, "packages/core": { - "name": "@codra/core", + "name": "@codraoss/core", "version": "0.9.4", + "license": "AGPL-3.0-only", "dependencies": { - "@codra/schema": "*", + "@codraoss/schema": "^0.9.4", "jsonrepair": "^3.15.0", "picomatch": "^4.0.5", "zod": "^4.3.6" }, "devDependencies": { - "@types/picomatch": "^4.0.3" + "@types/picomatch": "^4.0.3", + "tsup": "^8.0.0" } }, "packages/db": { - "name": "@codra/db", + "name": "@codraoss/db", "version": "0.9.4", + "license": "AGPL-3.0-only", "dependencies": { - "@codra/schema": "*", + "@codraoss/core": "^0.9.4", + "@codraoss/schema": "^0.9.4", "postgres": "^3.4.9" }, "devDependencies": { - "@types/node": "^22.0.0" + "@types/node": "^22.0.0", + "tsup": "^8.0.0" } }, "packages/db/node_modules/@types/node": { @@ -8741,39 +11124,55 @@ "license": "MIT" }, "packages/models": { - "name": "@codra/models", + "name": "@codraoss/models", "version": "0.9.4", + "license": "AGPL-3.0-only", "dependencies": { - "@codra/core": "*", - "@codra/schema": "*" + "@codraoss/core": "^0.9.4", + "@codraoss/schema": "^0.9.4" }, - "devDependencies": {} + "devDependencies": { + "tsup": "^8.0.0" + } }, "packages/provider-github": { - "name": "@codra/provider-github", + "name": "@codraoss/provider-github", "version": "0.9.4", + "license": "AGPL-3.0-only", "dependencies": { - "@codra/core": "*", - "@codra/schema": "*" + "@codraoss/core": "^0.9.4", + "@codraoss/schema": "^0.9.4" + }, + "devDependencies": { + "tsup": "^8.0.0" } }, "packages/schema": { - "name": "@codra/schema", + "name": "@codraoss/schema", "version": "0.9.4", + "license": "AGPL-3.0-only", "dependencies": { "zod": "^4.3.6" + }, + "devDependencies": { + "tsup": "^8.0.0" } }, "packages/ui": { - "name": "@codra/ui", + "name": "@codraoss/ui", "version": "0.9.4", + "license": "AGPL-3.0-only", "dependencies": { "@base-ui/react": "^1.6.0", + "@codraoss/schema": "^0.9.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "sugar-high": "^2.0.0", "tailwind-merge": "^3.5.0" }, + "devDependencies": { + "tsup": "^8.0.0" + }, "peerDependencies": { "lenis": ">=1.0.0", "lucide-react": ">=1.0.0", diff --git a/package.json b/package.json index cf83099f..e3a84df0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ ], "scripts": { "build": "vite build && npm run cf-typegen", + "build:packages": "npm run build -w @codraoss/schema && npm run build -w @codraoss/core && npm run build -w @codraoss/provider-github && npm run build -w @codraoss/db && npm run build -w @codraoss/models && npm run build -w @codraoss/api && npm run build -w @codraoss/ui", "build:all": "npm run build --workspaces --if-present", "cf-typegen": "cd apps/worker && wrangler types ./src/worker-env.d.ts", "deploy": "npm run build && npm run migrate && cd apps/worker && wrangler deploy", @@ -34,9 +35,15 @@ "test:all": "npm run test --workspaces --if-present", "test:watch": "vitest", "typecheck": "tsc --noEmit", - "typecheck:all": "npm run typecheck --workspaces --if-present" + "typecheck:all": "npm run typecheck --workspaces --if-present", + "changeset": "changeset", + "version:packages": "changeset version", + "release": "npm run build:packages && changeset publish", + "check:exports": "node scripts/check-package-exports.mjs" }, "devDependencies": { + "@arethetypeswrong/cli": "^0.18.5", + "@changesets/cli": "^3.0.0", "@eslint/js": "^10.0.1", "@tailwindcss/vite": "^4.2.2", "@testing-library/dom": "^10.4.1", @@ -54,7 +61,9 @@ "jsdom": "^29.0.2", "ora": "^9.4.1", "prompts": "^2.4.2", + "publint": "^0.3.23", "tailwindcss": "^4.2.2", + "tsup": "^8.5.1", "typescript": "^6.0.2", "typescript-eslint": "^8.66.0", "vite": "^8.0.8", @@ -62,9 +71,14 @@ "wrangler": "^4.114.0" }, "dependencies": { - "@codra/core": "*", - "@codra/schema": "*", "@base-ui/react": "^1.6.0", + "@codraoss/api": "*", + "@codraoss/core": "*", + "@codraoss/db": "*", + "@codraoss/models": "*", + "@codraoss/provider-github": "*", + "@codraoss/schema": "*", + "@codraoss/ui": "*", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "hono": "^4.12.25", @@ -84,12 +98,11 @@ "sonner": "^2.0.7", "sugar-high": "^2.0.0", "tailwind-merge": "^3.5.0", - "zod": "^4.3.6", - "@codra/ui": "*" + "zod": "^4.3.6" }, "allowScripts": { "esbuild": true, "unrs-resolver@1.12.2": true, "workerd@1.20260801.1": true } -} \ No newline at end of file +} diff --git a/packages/api/LICENSE b/packages/api/LICENSE new file mode 100644 index 00000000..024299ea --- /dev/null +++ b/packages/api/LICENSE @@ -0,0 +1,625 @@ +GNU Affero General Public License +================================= + +_Version 3, 19 November 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: **(1)** assert copyright on the software, and **(2)** offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +### 0. Definitions +“This License” refers to version 3 of the GNU Affero General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a “modified version” of the +earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based +on the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” +to the extent that it includes a convenient and prominently visible +feature that **(1)** displays an appropriate copyright notice, and **(2)** +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code +The “source code” for a work means the preferred form of the work +for making modifications to it. “Object code” means any non-source +form of a work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other +than the work as a whole, that **(a)** is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and **(b)** serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +“Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +### 2. Basic Permissions +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +### 4. Conveying Verbatim Copies +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified +it, and giving a relevant date. +* **b)** The work must carry prominent notices stating that it is +released under this License and any conditions added under section 7. +This requirement modifies the requirement in section 4 to +“keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. +* **d)** If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +“aggregate” if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either **(1)** a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or **(2)** access to copy the +Corresponding Source from a network server at no charge. +* **c)** Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. +* **d)** Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A “User Product” is either **(1)** a “consumer product”, which means any +tangible personal property which is normally used for personal, family, +or household purposes, or **(2)** anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, “normally used” refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install + +### 6. Conveying Non-Source Forms +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or +authors of the material; or +* **e)** Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +### 8. Termination +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated **(a)** +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and **(b)** permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To “grant” such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either **(1)** cause the Corresponding Source to be so +available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is “discriminatory” if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license **(a)** in connection with copies of the covered work +conveyed by you (or copies made from those copies), or **(b)** primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found. + + Codra: Open source PR review infrastructure for Cloudflare Workers. + Copyright (C) 2026 Devarshi Shimpi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a “Source” link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a “copyright disclaimer” for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<>. diff --git a/packages/api/README.md b/packages/api/README.md new file mode 100644 index 00000000..a44d77eb --- /dev/null +++ b/packages/api/README.md @@ -0,0 +1,15 @@ +# @codraoss/api + +Codra's HTTP surface as a mountable Hono router, wired through ports. + +Part of [Codra](https://codra.run), an open-source code review engine. See the [monorepo](https://github.com/devarshishimpi/codra) for development, and [CONTRIBUTING](https://github.com/devarshishimpi/codra/blob/main/CONTRIBUTING.md) for the dual-licensing / CLA details. + +## Install + +```bash +npm install @codraoss/api +``` + +## License + +[AGPL-3.0-only](./LICENSE) © Devarshi Shimpi diff --git a/packages/api/package.json b/packages/api/package.json index 8795350e..58c3b8cd 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,24 +1,57 @@ { - "name": "@codra/api", + "name": "@codraoss/api", "version": "0.9.4", - "private": true, + "description": "Codra's HTTP surface as a mountable Hono router, wired through ports.", + "author": "Devarshi Shimpi", + "license": "AGPL-3.0-only", + "homepage": "https://codra.run", + "repository": { + "type": "git", + "url": "git+https://github.com/devarshishimpi/codra.git", + "directory": "packages/api" + }, + "bugs": { + "url": "https://github.com/devarshishimpi/codra/issues" + }, "type": "module", + "sideEffects": false, "exports": { ".": "./src/index.ts" }, + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + } + }, "scripts": { - "typecheck": "tsc --noEmit" + "build": "tsup", + "typecheck": "tsc -p tsconfig.json", + "prepack": "node ../../scripts/swap-publish-exports.mjs promote", + "postpack": "node ../../scripts/swap-publish-exports.mjs restore" }, "dependencies": { "hono": "^4.12.25", "zod": "^4.3.6", - "@codra/schema": "*", - "@codra/core": "*", - "@codra/db": "*", - "@codra/provider-github": "*", - "@codra/models": "*" + "@codraoss/schema": "^0.9.4", + "@codraoss/core": "^0.9.4", + "@codraoss/db": "^0.9.4", + "@codraoss/provider-github": "^0.9.4", + "@codraoss/models": "^0.9.4" }, "devDependencies": { - "@types/node": "^22.0.0" + "@types/node": "^22.0.0", + "tsup": "^8.0.0" } } diff --git a/packages/api/src/logger.ts b/packages/api/src/logger.ts index 74a6744e..34a3a12d 100644 --- a/packages/api/src/logger.ts +++ b/packages/api/src/logger.ts @@ -1,10 +1,10 @@ import { AsyncLocalStorage } from 'node:async_hooks'; -import { formatLogRecord, setLoggerSink } from '@codra/core/logger'; +import { formatLogRecord, setLoggerSink } from '@codraoss/core/logger'; -// The request-context half of the logger. Scrubbing and record shaping live in @codra/core/logger; +// The request-context half of the logger. Scrubbing and record shaping live in @codraoss/core/logger; // this file owns everything platform-bound -- AsyncLocalStorage and the console sink -- so that // node:async_hooks never enters the engine package. Importing this module installs it as the sink -// that @codra/core's `logger` facade delegates to (see the bottom of the file). +// that @codraoss/core's `logger` facade delegates to (see the bottom of the file). const storage = new AsyncLocalStorage>(); class Logger { @@ -58,6 +58,6 @@ class Logger { export const logger = new Logger(); -// Wired at import scope so engine code logging through @codra/core's facade lands here, with request +// Wired at import scope so engine code logging through @codraoss/core's facade lands here, with request // context attached, rather than in core's bare console fallback. setLoggerSink(logger); diff --git a/packages/api/src/ports.ts b/packages/api/src/ports.ts index 40aec3d5..72d0f05e 100644 --- a/packages/api/src/ports.ts +++ b/packages/api/src/ports.ts @@ -1,16 +1,16 @@ -import type { DashboardSessionUser, SessionStore, ReviewRuntime } from '@codra/core/ports'; +import type { DashboardSessionUser, SessionStore, ReviewRuntime } from '@codraoss/core/ports'; // Type stubs that represent what the API layer requires. -// By importing types from @codra/db, we avoid a runtime dependency while retaining type safety. -import type * as dbAccounts from '@codra/db/accounts'; -import type * as dbJobs from '@codra/db/jobs'; -import type * as dbFileReviews from '@codra/db/file-reviews'; -import type * as dbCommentFeedback from '@codra/db/comment-feedback'; -import type * as dbModelConfigs from '@codra/db/model-configs'; -import type * as dbRepoConfigs from '@codra/db/repo-configs'; -import type * as dbAppSettings from '@codra/db/app-settings'; -import type * as dbStats from '@codra/db/stats'; -import type * as dbWebhookDeliveries from '@codra/db/webhook-deliveries'; +// By importing types from @codraoss/db, we avoid a runtime dependency while retaining type safety. +import type * as dbAccounts from '@codraoss/db/accounts'; +import type * as dbJobs from '@codraoss/db/jobs'; +import type * as dbFileReviews from '@codraoss/db/file-reviews'; +import type * as dbCommentFeedback from '@codraoss/db/comment-feedback'; +import type * as dbModelConfigs from '@codraoss/db/model-configs'; +import type * as dbRepoConfigs from '@codraoss/db/repo-configs'; +import type * as dbAppSettings from '@codraoss/db/app-settings'; +import type * as dbStats from '@codraoss/db/stats'; +import type * as dbWebhookDeliveries from '@codraoss/db/webhook-deliveries'; export interface RepositoriesPort { accounts: typeof dbAccounts; diff --git a/packages/api/src/routes/api/auth.ts b/packages/api/src/routes/api/auth.ts index 0069ad2f..0c6327aa 100644 --- a/packages/api/src/routes/api/auth.ts +++ b/packages/api/src/routes/api/auth.ts @@ -1,4 +1,4 @@ -import { isSupportedTimeZone } from '@codra/schema/timezone'; +import { isSupportedTimeZone } from '@codraoss/schema/timezone'; import { Hono } from 'hono'; import { z } from 'zod'; import { jsonError } from '../../http'; diff --git a/packages/api/src/routes/api/jobs.ts b/packages/api/src/routes/api/jobs.ts index 31ee97d5..96c850f7 100644 --- a/packages/api/src/routes/api/jobs.ts +++ b/packages/api/src/routes/api/jobs.ts @@ -1,9 +1,9 @@ import { Hono } from 'hono'; import type { Context } from 'hono'; -import { defaultRepoConfig, findingLabelSchema, jobsQuerySchema } from '@codra/schema'; +import { defaultRepoConfig, findingLabelSchema, jobsQuerySchema } from '@codraoss/schema'; import { jsonError } from '../../http'; -import { parseUnifiedDiff } from '@codra/core/diff'; -import { buildFileReviewPrompts } from '@codra/core/prompts/file-review'; +import { parseUnifiedDiff } from '@codraoss/core/diff'; +import { buildFileReviewPrompts } from '@codraoss/core/prompts/file-review'; import type { ApiEnv } from '../../ports'; // Best-effort terminate; .get() throws if the instance is gone and .terminate() if already terminal, both non-fatal. diff --git a/packages/api/src/routes/api/models.ts b/packages/api/src/routes/api/models.ts index a9e3708f..c5727d39 100644 --- a/packages/api/src/routes/api/models.ts +++ b/packages/api/src/routes/api/models.ts @@ -1,7 +1,7 @@ import { Hono } from 'hono'; import { z } from 'zod'; import { jsonError } from '../../http'; -import { llmApiFormats } from '@codra/schema'; +import { llmApiFormats } from '@codraoss/schema'; import type { ApiEnv } from '../../ports'; const apiFormatSchema = z.enum(llmApiFormats); diff --git a/packages/api/src/routes/api/repos.ts b/packages/api/src/routes/api/repos.ts index be66a68f..c10bc2a3 100644 --- a/packages/api/src/routes/api/repos.ts +++ b/packages/api/src/routes/api/repos.ts @@ -2,7 +2,7 @@ import { Hono } from 'hono'; import { z } from 'zod'; import type { ApiEnv } from '../../ports'; import { jsonError } from '../../http'; -import { repoConfigSchema } from '@codra/schema'; +import { repoConfigSchema } from '@codraoss/schema'; const repoConfigPatchSchema = z .strictObject({ diff --git a/packages/api/src/routes/api/settings.ts b/packages/api/src/routes/api/settings.ts index d81f622d..774f95ce 100644 --- a/packages/api/src/routes/api/settings.ts +++ b/packages/api/src/routes/api/settings.ts @@ -2,7 +2,7 @@ import { Hono } from 'hono'; import { z } from 'zod'; import type { ApiEnv } from '../../ports'; import { jsonError } from '../../http'; -import { reviewConcurrencyLevels, reviewMaxCommentsOptions, reviewMaxFilesRange, reviewSettingsSchema } from '@codra/schema'; +import { reviewConcurrencyLevels, reviewMaxCommentsOptions, reviewMaxFilesRange, reviewSettingsSchema } from '@codraoss/schema'; const reviewSettingsPatchSchema = z.strictObject({ concurrencyLevel: z.enum(reviewConcurrencyLevels).optional(), diff --git a/packages/api/src/routes/webhook.ts b/packages/api/src/routes/webhook.ts index 1bca3089..2daad75a 100644 --- a/packages/api/src/routes/webhook.ts +++ b/packages/api/src/routes/webhook.ts @@ -5,7 +5,7 @@ import { type FeedbackWebhookPayload, type GitHubReviewCommentPayload, type GitHubWebhookPayload, -} from '@codra/schema/github'; +} from '@codraoss/schema/github'; import type { ApiEnv } from '../ports'; import { jsonError } from '../http'; diff --git a/packages/api/src/sessions.ts b/packages/api/src/sessions.ts index d622f8ab..ae7fae0b 100644 --- a/packages/api/src/sessions.ts +++ b/packages/api/src/sessions.ts @@ -1,7 +1,7 @@ import { deleteCookie, getCookie, setCookie } from 'hono/cookie'; import type { Context } from 'hono'; import type { ApiEnv } from './ports'; -import type { DashboardSessionUser } from '@codra/core/ports'; +import type { DashboardSessionUser } from '@codraoss/core/ports'; const SESSION_COOKIE_NAME = 'codra_session'; const SESSION_TTL_SECONDS = 60 * 60 * 24 * 7; diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 347926ac..6e673328 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -3,9 +3,11 @@ "compilerOptions": { "module": "ESNext", "moduleResolution": "Bundler", - "outDir": "./dist", - "rootDir": "./src", - "types": ["node"] + "types": ["node"], + "composite": false, + "declaration": false, + "emitDeclarationOnly": false, + "noEmit": true }, "include": ["src/**/*"] } diff --git a/packages/api/tsup.config.json b/packages/api/tsup.config.json new file mode 100644 index 00000000..bc7ed045 --- /dev/null +++ b/packages/api/tsup.config.json @@ -0,0 +1,9 @@ +{ + "entry": ["src/index.ts"], + "format": "esm", + "dts": true, + "splitting": true, + "sourcemap": true, + "clean": true, + "outDir": "dist" +} diff --git a/packages/core/LICENSE b/packages/core/LICENSE new file mode 100644 index 00000000..024299ea --- /dev/null +++ b/packages/core/LICENSE @@ -0,0 +1,625 @@ +GNU Affero General Public License +================================= + +_Version 3, 19 November 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: **(1)** assert copyright on the software, and **(2)** offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +### 0. Definitions +“This License” refers to version 3 of the GNU Affero General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a “modified version” of the +earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based +on the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” +to the extent that it includes a convenient and prominently visible +feature that **(1)** displays an appropriate copyright notice, and **(2)** +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code +The “source code” for a work means the preferred form of the work +for making modifications to it. “Object code” means any non-source +form of a work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other +than the work as a whole, that **(a)** is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and **(b)** serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +“Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +### 2. Basic Permissions +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +### 4. Conveying Verbatim Copies +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified +it, and giving a relevant date. +* **b)** The work must carry prominent notices stating that it is +released under this License and any conditions added under section 7. +This requirement modifies the requirement in section 4 to +“keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. +* **d)** If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +“aggregate” if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either **(1)** a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or **(2)** access to copy the +Corresponding Source from a network server at no charge. +* **c)** Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. +* **d)** Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A “User Product” is either **(1)** a “consumer product”, which means any +tangible personal property which is normally used for personal, family, +or household purposes, or **(2)** anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, “normally used” refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install + +### 6. Conveying Non-Source Forms +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or +authors of the material; or +* **e)** Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +### 8. Termination +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated **(a)** +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and **(b)** permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To “grant” such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either **(1)** cause the Corresponding Source to be so +available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is “discriminatory” if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license **(a)** in connection with copies of the covered work +conveyed by you (or copies made from those copies), or **(b)** primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found. + + Codra: Open source PR review infrastructure for Cloudflare Workers. + Copyright (C) 2026 Devarshi Shimpi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a “Source” link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a “copyright disclaimer” for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<>. diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 00000000..2c46cfab --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,15 @@ +# @codraoss/core + +The Codra review engine: pure, transport- and platform-agnostic code review logic behind ports. + +Part of [Codra](https://codra.run), an open-source code review engine. See the [monorepo](https://github.com/devarshishimpi/codra) for development, and [CONTRIBUTING](https://github.com/devarshishimpi/codra/blob/main/CONTRIBUTING.md) for the dual-licensing / CLA details. + +## Install + +```bash +npm install @codraoss/core +``` + +## License + +[AGPL-3.0-only](./LICENSE) © Devarshi Shimpi diff --git a/packages/core/package.json b/packages/core/package.json index 13cbd73f..e2a4434a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,8 +1,20 @@ { - "name": "@codra/core", + "name": "@codraoss/core", "version": "0.9.4", - "private": true, + "description": "The Codra review engine: pure, transport- and platform-agnostic code review logic behind ports.", + "author": "Devarshi Shimpi", + "license": "AGPL-3.0-only", + "homepage": "https://codra.run", + "repository": { + "type": "git", + "url": "git+https://github.com/devarshishimpi/codra.git", + "directory": "packages/core" + }, + "bugs": { + "url": "https://github.com/devarshishimpi/codra/issues" + }, "type": "module", + "sideEffects": false, "exports": { ".": "./src/index.ts", "./ports": "./src/ports/index.ts", @@ -21,17 +33,98 @@ "./prompts/summary": "./src/prompts/summary.ts", "./prompts/verify": "./src/prompts/verify.ts" }, + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./ports": { + "types": "./dist/ports/index.d.ts", + "import": "./dist/ports/index.js" + }, + "./logger": { + "types": "./dist/logger.d.ts", + "import": "./dist/logger.js" + }, + "./diff": { + "types": "./dist/diff/index.d.ts", + "import": "./dist/diff/index.js" + }, + "./model-output": { + "types": "./dist/model-output/index.d.ts", + "import": "./dist/model-output/index.js" + }, + "./rules/detect": { + "types": "./dist/rules/detect.d.ts", + "import": "./dist/rules/detect.js" + }, + "./rules/table": { + "types": "./dist/rules/table.d.ts", + "import": "./dist/rules/table.js" + }, + "./claim-checks": { + "types": "./dist/claim-checks.d.ts", + "import": "./dist/claim-checks.js" + }, + "./verify": { + "types": "./dist/verify.d.ts", + "import": "./dist/verify.js" + }, + "./fingerprint": { + "types": "./dist/fingerprint.d.ts", + "import": "./dist/fingerprint.js" + }, + "./timeout": { + "types": "./dist/timeout.d.ts", + "import": "./dist/timeout.js" + }, + "./token-tracker": { + "types": "./dist/token-tracker.d.ts", + "import": "./dist/token-tracker.js" + }, + "./prompts/file-review": { + "types": "./dist/prompts/file-review.d.ts", + "import": "./dist/prompts/file-review.js" + }, + "./prompts/languages": { + "types": "./dist/prompts/languages.d.ts", + "import": "./dist/prompts/languages.js" + }, + "./prompts/summary": { + "types": "./dist/prompts/summary.d.ts", + "import": "./dist/prompts/summary.js" + }, + "./prompts/verify": { + "types": "./dist/prompts/verify.d.ts", + "import": "./dist/prompts/verify.js" + } + } + }, "scripts": { + "build": "tsup", "typecheck": "tsc -p tsconfig.json", - "test": "vitest run" + "test": "vitest run", + "prepack": "node ../../scripts/swap-publish-exports.mjs promote", + "postpack": "node ../../scripts/swap-publish-exports.mjs restore" }, "dependencies": { - "@codra/schema": "*", + "@codraoss/schema": "^0.9.4", "jsonrepair": "^3.15.0", "picomatch": "^4.0.5", "zod": "^4.3.6" }, "devDependencies": { - "@types/picomatch": "^4.0.3" + "@types/picomatch": "^4.0.3", + "tsup": "^8.0.0" } } diff --git a/packages/core/src/diff/index.ts b/packages/core/src/diff/index.ts index 2c246bbf..390df731 100644 --- a/packages/core/src/diff/index.ts +++ b/packages/core/src/diff/index.ts @@ -1,5 +1,5 @@ import picomatch from 'picomatch'; -import type { RepoConfig } from '@codra/schema'; +import type { RepoConfig } from '@codraoss/schema'; import { type DiffLineKind, type DiffLine, diff --git a/packages/core/src/finding-gates.ts b/packages/core/src/finding-gates.ts index 19b69ddc..cae87bd9 100644 --- a/packages/core/src/finding-gates.ts +++ b/packages/core/src/finding-gates.ts @@ -1,4 +1,4 @@ -import type { FindingDisposition, ParsedReviewComment, RepoConfig } from '@codra/schema'; +import type { FindingDisposition, ParsedReviewComment, RepoConfig } from '@codraoss/schema'; import type { FileDiff } from './diff'; import type { ReviewModel } from './ports'; import { renderDiffSnippet, parseVerifyResponse, type VerifyCandidate } from './prompts/verify'; diff --git a/packages/core/src/model-output/batch.ts b/packages/core/src/model-output/batch.ts index 83931689..1fa5e459 100644 --- a/packages/core/src/model-output/batch.ts +++ b/packages/core/src/model-output/batch.ts @@ -1,4 +1,4 @@ -import type { ClaimType } from '@codra/schema'; +import type { ClaimType } from '@codraoss/schema'; import type { FileDiff } from '../diff'; import { generatorFindingCap } from '../prompts/file-review'; import { logger } from '../logger'; diff --git a/packages/core/src/model-output/dedupe.ts b/packages/core/src/model-output/dedupe.ts index 0d3e080d..63200aca 100644 --- a/packages/core/src/model-output/dedupe.ts +++ b/packages/core/src/model-output/dedupe.ts @@ -1,4 +1,4 @@ -import type { ParsedReviewComment } from '@codra/schema'; +import type { ParsedReviewComment } from '@codraoss/schema'; import { normalizeFindingTitle } from '../fingerprint'; const SEVERITY_RANK: Record = { P0: 0, P1: 1, P2: 2, P3: 3, nit: 4 }; diff --git a/packages/core/src/model-output/index.ts b/packages/core/src/model-output/index.ts index 9d0ad5a4..6ea28356 100644 --- a/packages/core/src/model-output/index.ts +++ b/packages/core/src/model-output/index.ts @@ -6,7 +6,7 @@ import { type ClaimType, type ParsedReviewComment, reviewSeverities, -} from '@codra/schema'; +} from '@codraoss/schema'; import { renderDiffSnippet } from '../prompts/verify'; import { logger } from '../logger'; import { z } from 'zod'; diff --git a/packages/core/src/model-output/json-batch.ts b/packages/core/src/model-output/json-batch.ts index a02b47c5..013b448d 100644 --- a/packages/core/src/model-output/json-batch.ts +++ b/packages/core/src/model-output/json-batch.ts @@ -1,4 +1,4 @@ -import { batchReviewModelOutputSchema, fileReviewModelOutputSchema } from '@codra/schema'; +import { batchReviewModelOutputSchema, fileReviewModelOutputSchema } from '@codraoss/schema'; import { jsonrepair } from 'jsonrepair'; import { z } from 'zod'; import { logger } from '../logger'; diff --git a/packages/core/src/model-output/json.ts b/packages/core/src/model-output/json.ts index b53dbd31..bec5d0e7 100644 --- a/packages/core/src/model-output/json.ts +++ b/packages/core/src/model-output/json.ts @@ -1,4 +1,4 @@ -import { fileReviewModelOutputSchema } from '@codra/schema'; +import { fileReviewModelOutputSchema } from '@codraoss/schema'; import { jsonrepair } from 'jsonrepair'; import { z } from 'zod'; import { logger } from '../logger'; diff --git a/packages/core/src/ports/file-reviews.ts b/packages/core/src/ports/file-reviews.ts index 742a1e88..4fd4e500 100644 --- a/packages/core/src/ports/file-reviews.ts +++ b/packages/core/src/ports/file-reviews.ts @@ -1,4 +1,4 @@ -import type { ParsedReviewComment } from '@codra/schema'; +import type { ParsedReviewComment } from '@codraoss/schema'; export type FileReviewRow = { diff --git a/packages/core/src/ports/formatter.ts b/packages/core/src/ports/formatter.ts index e8fe4eb1..6831e6a0 100644 --- a/packages/core/src/ports/formatter.ts +++ b/packages/core/src/ports/formatter.ts @@ -1,4 +1,4 @@ -import type { ParsedReviewComment } from '@codra/schema'; +import type { ParsedReviewComment } from '@codraoss/schema'; export interface ReviewFormatter { toReviewEvent(verdict: 'approve' | 'comment'): 'APPROVE' | 'COMMENT'; diff --git a/packages/core/src/ports/in-memory.ts b/packages/core/src/ports/in-memory.ts index c5b1e413..57533d64 100644 --- a/packages/core/src/ports/in-memory.ts +++ b/packages/core/src/ports/in-memory.ts @@ -2,7 +2,7 @@ import type { KeyValueStore } from './kv'; import type { QueueProducer } from './queue'; import type { JobOrchestrator } from './orchestrator'; import type { SessionStore, DashboardSessionUser } from './session-store'; -import type { ReviewJobMessage } from '@codra/schema'; +import type { ReviewJobMessage } from '@codraoss/schema'; export class InMemoryKV implements KeyValueStore { private store = new Map(); diff --git a/packages/core/src/ports/jobs.ts b/packages/core/src/ports/jobs.ts index 8535b52b..f93fb717 100644 --- a/packages/core/src/ports/jobs.ts +++ b/packages/core/src/ports/jobs.ts @@ -1,4 +1,4 @@ -import type { JobSummary, RepoConfig } from '@codra/schema'; +import type { JobSummary, RepoConfig } from '@codraoss/schema'; export type PersistedReviewJob = JobSummary; diff --git a/packages/core/src/ports/model.ts b/packages/core/src/ports/model.ts index 1118e578..8f3dd06c 100644 --- a/packages/core/src/ports/model.ts +++ b/packages/core/src/ports/model.ts @@ -1,4 +1,4 @@ -import type { RepoConfig } from '@codra/schema'; +import type { RepoConfig } from '@codraoss/schema'; import type { FileDiff } from '../diff'; import type { BatchReviewResult, parseFileReviewResponse } from '../model-output'; import type { RejectedExemplar } from '../prompts/file-review'; diff --git a/packages/core/src/ports/orchestrator.ts b/packages/core/src/ports/orchestrator.ts index 0e6d3f04..6b62a3e2 100644 --- a/packages/core/src/ports/orchestrator.ts +++ b/packages/core/src/ports/orchestrator.ts @@ -1,4 +1,4 @@ -import type { ReviewJobMessage } from '@codra/schema'; +import type { ReviewJobMessage } from '@codraoss/schema'; export interface JobOrchestrator { startReviewJob(id: string, params: ReviewJobMessage): Promise; diff --git a/packages/core/src/ports/repo-config.ts b/packages/core/src/ports/repo-config.ts index ca3c02cf..8ba3a895 100644 --- a/packages/core/src/ports/repo-config.ts +++ b/packages/core/src/ports/repo-config.ts @@ -1,4 +1,4 @@ -import type { RepoConfig } from '@codra/schema'; +import type { RepoConfig } from '@codraoss/schema'; export interface RepoConfigStore { getRepoConfigRecord(owner: string, repo: string): Promise<{ diff --git a/packages/core/src/ports/settings.ts b/packages/core/src/ports/settings.ts index ca658c62..8cdac300 100644 --- a/packages/core/src/ports/settings.ts +++ b/packages/core/src/ports/settings.ts @@ -1,4 +1,4 @@ -import type { ClaimType, RepoConfig, ReviewSettings } from '@codra/schema'; +import type { ClaimType, RepoConfig, ReviewSettings } from '@codraoss/schema'; export interface ReviewSettingsReader { getReviewSettings(): Promise; diff --git a/packages/core/src/prompts/file-review.ts b/packages/core/src/prompts/file-review.ts index 10d0057e..fcef81a6 100644 --- a/packages/core/src/prompts/file-review.ts +++ b/packages/core/src/prompts/file-review.ts @@ -1,4 +1,4 @@ -import { claimTypes, type RepoConfig } from '@codra/schema'; +import { claimTypes, type RepoConfig } from '@codraoss/schema'; import type { FileDiff } from '../diff'; import type { ModelResponseSchema } from '../ports/model'; import { getLanguageForFile } from './languages'; diff --git a/packages/core/src/review/bin-runner.ts b/packages/core/src/review/bin-runner.ts index 098530cb..2870f739 100644 --- a/packages/core/src/review/bin-runner.ts +++ b/packages/core/src/review/bin-runner.ts @@ -1,5 +1,5 @@ import { logger } from '../logger'; -import type { RepoConfig } from '@codra/schema'; +import type { RepoConfig } from '@codraoss/schema'; import type { FileDiff } from '../diff'; import { renderFileDiff, type RejectedExemplar } from '../prompts/file-review'; import type { BulkFileReviewInput, PullRequestRecord, ReviewModel, ReviewRuntime } from '../ports'; diff --git a/packages/core/src/review/diff-cache.ts b/packages/core/src/review/diff-cache.ts index 98fd5021..9c996d25 100644 --- a/packages/core/src/review/diff-cache.ts +++ b/packages/core/src/review/diff-cache.ts @@ -1,4 +1,4 @@ -import { reviewMaxFilesRange, type RepoConfig } from '@codra/schema'; +import { reviewMaxFilesRange, type RepoConfig } from '@codraoss/schema'; import { filterReviewableFiles, parseUnifiedDiff, type FileDiff } from '../diff'; import type { ReviewGitProvider, ReviewRuntime } from '../ports'; import { logger } from '../logger'; diff --git a/packages/core/src/review/file-runner.ts b/packages/core/src/review/file-runner.ts index caa6a04a..0d8780a6 100644 --- a/packages/core/src/review/file-runner.ts +++ b/packages/core/src/review/file-runner.ts @@ -1,5 +1,5 @@ import { logger } from '../logger'; -import { type ParsedReviewComment, type RepoConfig } from '@codra/schema'; +import { type ParsedReviewComment, type RepoConfig } from '@codraoss/schema'; import { parseUnifiedDiff, type FileDiff } from '../diff'; import { ruleHitsToComments, scanFileForRuleHits, type RuleScanStats } from '../rules/detect'; import type { RejectedExemplar } from '../prompts/file-review'; diff --git a/packages/core/src/review/finalize.ts b/packages/core/src/review/finalize.ts index a742cfec..a2e6c38c 100644 --- a/packages/core/src/review/finalize.ts +++ b/packages/core/src/review/finalize.ts @@ -1,5 +1,5 @@ import { logger } from '../logger'; -import { defaultRepoConfig, type ParsedReviewComment, type RepoConfig } from '@codra/schema'; +import { defaultRepoConfig, type ParsedReviewComment, type RepoConfig } from '@codraoss/schema'; import { shadowEvaluate } from '../finding-gates'; import { getDiffFiles } from './diff-cache'; import type { ReviewFormatter, ReviewGitProvider, ReviewModel, ReviewRuntime } from '../ports'; diff --git a/packages/core/src/review/gate-pipeline.ts b/packages/core/src/review/gate-pipeline.ts index 09fc7058..97976396 100644 --- a/packages/core/src/review/gate-pipeline.ts +++ b/packages/core/src/review/gate-pipeline.ts @@ -1,6 +1,6 @@ import { dedupeFindings } from '../model-output'; import { verifyFindings } from '../finding-gates'; -import type { FindingDisposition, ParsedReviewComment, RepoConfig } from '@codra/schema'; +import type { FindingDisposition, ParsedReviewComment, RepoConfig } from '@codraoss/schema'; import type { FileDiff } from '../diff'; import type { PersistedReviewJob } from './phase-control'; import type { ReviewModel, ReviewRuntime } from '../ports'; diff --git a/packages/core/src/review/index.ts b/packages/core/src/review/index.ts index b889061a..3ec08bf6 100644 --- a/packages/core/src/review/index.ts +++ b/packages/core/src/review/index.ts @@ -1,6 +1,6 @@ import { logger } from '../logger'; -import { type WebhookPayload, type ChangeRequestWebhookPayload } from '@codra/schema/webhook'; -import { REVIEW_CONCURRENCY_LIMITS, type ReviewJobMessage } from '@codra/schema'; +import { type WebhookPayload, type ChangeRequestWebhookPayload } from '@codraoss/schema/webhook'; +import { REVIEW_CONCURRENCY_LIMITS, type ReviewJobMessage } from '@codraoss/schema'; import type { ReviewGitProvider, ReviewRuntime } from '../ports'; import { extractReviewRequest } from './request'; diff --git a/packages/core/src/review/phase.ts b/packages/core/src/review/phase.ts index 05075f19..3fb90cd3 100644 --- a/packages/core/src/review/phase.ts +++ b/packages/core/src/review/phase.ts @@ -1,5 +1,5 @@ import { logger } from '../logger'; -import { defaultRepoConfig, REVIEW_CONCURRENCY_LIMITS, type ParsedReviewComment, type RepoConfig } from '@codra/schema'; +import { defaultRepoConfig, REVIEW_CONCURRENCY_LIMITS, type ParsedReviewComment, type RepoConfig } from '@codraoss/schema'; import { budgetAwareFileLimit } from './budget'; import { narrowUnit, planReviewUnits } from './pack'; import { reviewAndPersistBin } from './bin-runner'; diff --git a/packages/core/src/review/prepare.ts b/packages/core/src/review/prepare.ts index 89fc933f..c357ec48 100644 --- a/packages/core/src/review/prepare.ts +++ b/packages/core/src/review/prepare.ts @@ -1,5 +1,5 @@ import { logger } from '../logger'; -import { defaultRepoConfig, type RepoConfig } from '@codra/schema'; +import { defaultRepoConfig, type RepoConfig } from '@codraoss/schema'; import type { ReviewGitProvider, ReviewRuntime } from '../ports'; import { getDiffFiles } from './diff-cache'; import type { RejectedExemplar } from '../prompts/file-review'; diff --git a/packages/core/src/review/request.ts b/packages/core/src/review/request.ts index a2a3aaca..275aa24b 100644 --- a/packages/core/src/review/request.ts +++ b/packages/core/src/review/request.ts @@ -3,8 +3,8 @@ import type { WebhookPayload, CommentWebhookPayload, ChangeRequestWebhookPayload, -} from '@codra/schema/webhook'; -import type { RepoConfig } from '@codra/schema'; +} from '@codraoss/schema/webhook'; +import type { RepoConfig } from '@codraoss/schema'; function shouldTriggerFromChangeRequest(action: ChangeRequestWebhookPayload['action'], config: RepoConfig['review']) { return (config.on as string[]).includes(action); diff --git a/packages/core/src/review/retry-policy.ts b/packages/core/src/review/retry-policy.ts index e82d9492..866e69a2 100644 --- a/packages/core/src/review/retry-policy.ts +++ b/packages/core/src/review/retry-policy.ts @@ -1,6 +1,6 @@ import { logger } from '../logger'; -import { normalizeModelId, type RepoConfig } from '@codra/schema'; -import { isSubrequestBudgetMessage, isTimeoutMessage, matchesAnyTransientSubstring } from '@codra/schema/transient-errors'; +import { normalizeModelId, type RepoConfig } from '@codraoss/schema'; +import { isSubrequestBudgetMessage, isTimeoutMessage, matchesAnyTransientSubstring } from '@codraoss/schema/transient-errors'; import type { ReviewRuntime } from '../ports'; import { RETRYABLE_MODEL_FAILURE_RETRY_DELAYS_SECONDS } from '../constants'; diff --git a/packages/core/src/rules/detect.ts b/packages/core/src/rules/detect.ts index d9c525b6..25bedf93 100644 --- a/packages/core/src/rules/detect.ts +++ b/packages/core/src/rules/detect.ts @@ -1,8 +1,8 @@ -import type { ClaimType, ParsedReviewComment } from '@codra/schema'; +import type { ClaimType, ParsedReviewComment } from '@codraoss/schema'; import type { DiffLine, FileDiff } from '../diff'; import { commentSyntaxFor, stripCommentsAndStrings } from '../claim-checks'; import { buildAnchorHash, buildFindingFingerprint, buildFindingFingerprintV2, normalizeDiffText } from '../fingerprint'; -import { CLAIM_TYPE_CATEGORY } from '@codra/schema'; +import { CLAIM_TYPE_CATEGORY } from '@codraoss/schema'; import { RULES, type Rule } from './table'; import { MAX_RULE_SCAN_ADDED_LINES } from '../constants'; diff --git a/packages/core/src/rules/table.ts b/packages/core/src/rules/table.ts index 45ba7b37..52c770ab 100644 --- a/packages/core/src/rules/table.ts +++ b/packages/core/src/rules/table.ts @@ -1,4 +1,4 @@ -import type { ClaimType, reviewSeverities } from '@codra/schema'; +import type { ClaimType, reviewSeverities } from '@codraoss/schema'; type ReviewSeverity = typeof reviewSeverities[number]; diff --git a/packages/core/src/verify.ts b/packages/core/src/verify.ts index 4a97e869..1351f673 100644 --- a/packages/core/src/verify.ts +++ b/packages/core/src/verify.ts @@ -1,4 +1,4 @@ -import { hexToBytes } from '@codra/schema/hex'; +import { hexToBytes } from '@codraoss/schema/hex'; const encoder = new TextEncoder(); diff --git a/packages/core/test/in-memory.ts b/packages/core/test/in-memory.ts index 12a20e4b..abaab71e 100644 --- a/packages/core/test/in-memory.ts +++ b/packages/core/test/in-memory.ts @@ -1,5 +1,5 @@ -import { defaultRepoConfig, reviewSettingsSchema, type ParsedReviewComment, type RepoConfig, type ReviewSettings } from '@codra/schema'; +import { defaultRepoConfig, reviewSettingsSchema, type ParsedReviewComment, type RepoConfig, type ReviewSettings } from '@codraoss/schema'; import type { BulkFileReviewInput, FileReviewRow, diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 608f0e1c..afff666a 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -9,7 +9,7 @@ "types": ["node"], // No emit, and therefore no project reference to ../schema. Packages here have no build step: - // @codra/schema is consumed as raw TS source through its `exports` map, exactly as the root + // @codraoss/schema is consumed as raw TS source through its `exports` map, exactly as the root // program consumes it. Keeping the base's composite/declaration settings would instead demand // that schema be built to dist/ first (TS6305) -- a build nothing in this repo performs. // This project exists to typecheck the package against a NARROWER lib/types than the root diff --git a/packages/core/tsup.config.json b/packages/core/tsup.config.json new file mode 100644 index 00000000..c5d47f10 --- /dev/null +++ b/packages/core/tsup.config.json @@ -0,0 +1,26 @@ +{ + "entry": [ + "src/index.ts", + "src/ports/index.ts", + "src/logger.ts", + "src/diff/index.ts", + "src/model-output/index.ts", + "src/rules/detect.ts", + "src/rules/table.ts", + "src/claim-checks.ts", + "src/verify.ts", + "src/fingerprint.ts", + "src/timeout.ts", + "src/token-tracker.ts", + "src/prompts/file-review.ts", + "src/prompts/languages.ts", + "src/prompts/summary.ts", + "src/prompts/verify.ts" + ], + "format": "esm", + "dts": true, + "splitting": true, + "sourcemap": true, + "clean": true, + "outDir": "dist" +} diff --git a/packages/db/LICENSE b/packages/db/LICENSE new file mode 100644 index 00000000..024299ea --- /dev/null +++ b/packages/db/LICENSE @@ -0,0 +1,625 @@ +GNU Affero General Public License +================================= + +_Version 3, 19 November 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: **(1)** assert copyright on the software, and **(2)** offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +### 0. Definitions +“This License” refers to version 3 of the GNU Affero General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a “modified version” of the +earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based +on the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” +to the extent that it includes a convenient and prominently visible +feature that **(1)** displays an appropriate copyright notice, and **(2)** +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code +The “source code” for a work means the preferred form of the work +for making modifications to it. “Object code” means any non-source +form of a work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other +than the work as a whole, that **(a)** is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and **(b)** serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +“Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +### 2. Basic Permissions +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +### 4. Conveying Verbatim Copies +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified +it, and giving a relevant date. +* **b)** The work must carry prominent notices stating that it is +released under this License and any conditions added under section 7. +This requirement modifies the requirement in section 4 to +“keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. +* **d)** If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +“aggregate” if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either **(1)** a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or **(2)** access to copy the +Corresponding Source from a network server at no charge. +* **c)** Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. +* **d)** Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A “User Product” is either **(1)** a “consumer product”, which means any +tangible personal property which is normally used for personal, family, +or household purposes, or **(2)** anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, “normally used” refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install + +### 6. Conveying Non-Source Forms +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or +authors of the material; or +* **e)** Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +### 8. Termination +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated **(a)** +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and **(b)** permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To “grant” such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either **(1)** cause the Corresponding Source to be so +available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is “discriminatory” if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license **(a)** in connection with copies of the covered work +conveyed by you (or copies made from those copies), or **(b)** primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found. + + Codra: Open source PR review infrastructure for Cloudflare Workers. + Copyright (C) 2026 Devarshi Shimpi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a “Source” link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a “copyright disclaimer” for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<>. diff --git a/packages/db/README.md b/packages/db/README.md new file mode 100644 index 00000000..d567e446 --- /dev/null +++ b/packages/db/README.md @@ -0,0 +1,15 @@ +# @codraoss/db + +Codra's Postgres persistence layer behind repository interfaces. + +Part of [Codra](https://codra.run), an open-source code review engine. See the [monorepo](https://github.com/devarshishimpi/codra) for development, and [CONTRIBUTING](https://github.com/devarshishimpi/codra/blob/main/CONTRIBUTING.md) for the dual-licensing / CLA details. + +## Install + +```bash +npm install @codraoss/db +``` + +## License + +[AGPL-3.0-only](./LICENSE) © Devarshi Shimpi diff --git a/packages/db/package.json b/packages/db/package.json index e9eefc46..eb08cff8 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,27 +1,76 @@ { - "name": "@codra/db", + "name": "@codraoss/db", "version": "0.9.4", - "private": true, + "description": "Codra's Postgres persistence layer behind repository interfaces.", + "author": "Devarshi Shimpi", + "license": "AGPL-3.0-only", + "homepage": "https://codra.run", + "repository": { + "type": "git", + "url": "git+https://github.com/devarshishimpi/codra.git", + "directory": "packages/db" + }, + "bugs": { + "url": "https://github.com/devarshishimpi/codra/issues" + }, "type": "module", + "sideEffects": false, "exports": { - ".": "./src/index.ts", "./client": "./src/client.ts", "./repositories": "./src/repositories/index.ts", "./env": "./src/env.ts", "./jobs": "./src/jobs.ts", "./file-reviews": "./src/file-reviews.ts", - "./test/fakes": "./test/fakes/index.ts", "./*": "./src/*.ts" }, + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public", + "exports": { + "./client": { + "types": "./dist/client.d.ts", + "import": "./dist/client.js" + }, + "./repositories": { + "types": "./dist/repositories/index.d.ts", + "import": "./dist/repositories/index.js" + }, + "./env": { + "types": "./dist/env.d.ts", + "import": "./dist/env.js" + }, + "./jobs": { + "types": "./dist/jobs.d.ts", + "import": "./dist/jobs.js" + }, + "./file-reviews": { + "types": "./dist/file-reviews.d.ts", + "import": "./dist/file-reviews.js" + }, + "./*": { + "types": "./dist/*.d.ts", + "import": "./dist/*.js" + } + } + }, "scripts": { + "build": "tsup", "typecheck": "tsc -p tsconfig.json", - "test": "vitest run" + "test": "vitest run", + "prepack": "node ../../scripts/swap-publish-exports.mjs promote", + "postpack": "node ../../scripts/swap-publish-exports.mjs restore" }, "dependencies": { - "@codra/schema": "*", + "@codraoss/schema": "^0.9.4", + "@codraoss/core": "^0.9.4", "postgres": "^3.4.9" }, "devDependencies": { - "@types/node": "^22.0.0" + "@types/node": "^22.0.0", + "tsup": "^8.0.0" } } diff --git a/packages/db/src/app-settings.ts b/packages/db/src/app-settings.ts index 7ba51096..eda495ea 100644 --- a/packages/db/src/app-settings.ts +++ b/packages/db/src/app-settings.ts @@ -7,7 +7,7 @@ import { CONCURRENCY_LEVELS, MAX_COMMENTS_OPTIONS } from './constants'; -import { reviewMaxFilesRange, reviewSettingsSchema, type ReviewSettings } from '@codra/schema'; +import { reviewMaxFilesRange, reviewSettingsSchema, type ReviewSettings } from '@codraoss/schema'; diff --git a/packages/db/src/constants.ts b/packages/db/src/constants.ts index 7e048ba5..34454b76 100644 --- a/packages/db/src/constants.ts +++ b/packages/db/src/constants.ts @@ -1,4 +1,4 @@ -import { reviewConcurrencyLevels, reviewMaxCommentsOptions } from '@codra/schema'; +import { reviewConcurrencyLevels, reviewMaxCommentsOptions } from '@codraoss/schema'; // accounts.ts export const ACCOUNT_COLUMNS = 'id, github_user_id, github_username, account_name, account_email, timezone'; diff --git a/packages/db/src/file-reviews-bulk.ts b/packages/db/src/file-reviews-bulk.ts index a6e8c0a1..84ff4d86 100644 --- a/packages/db/src/file-reviews-bulk.ts +++ b/packages/db/src/file-reviews-bulk.ts @@ -1,5 +1,5 @@ import type { DbEnv } from './env'; -import type { BulkFileReviewInput } from '@codra/core/ports'; +import type { BulkFileReviewInput } from '@codraoss/core/ports'; import { queryRows, queryTransaction } from './client'; import { REVIEW_COMMENT_INSERT_CASTS, @@ -63,7 +63,7 @@ export async function bulkInheritFileReviews( }); } -export type { BulkFileReviewInput } from '@codra/core/ports'; +export type { BulkFileReviewInput } from '@codraoss/core/ports'; // One transaction: per-file upserts would spend the saved model calls back on DB subrequests. `diff_input` is not written (migration 003 nulls it). export async function bulkUpsertFileReviews( diff --git a/packages/db/src/file-reviews-findings.ts b/packages/db/src/file-reviews-findings.ts index b7743387..1cec7149 100644 --- a/packages/db/src/file-reviews-findings.ts +++ b/packages/db/src/file-reviews-findings.ts @@ -1,8 +1,8 @@ import type { DbEnv } from './env'; -import type { SuppressedFinding } from '@codra/core/ports'; +import type { SuppressedFinding } from '@codraoss/core/ports'; import { queryRows } from './client'; -export type { SuppressedFinding } from '@codra/core/ports'; +export type { SuppressedFinding } from '@codraoss/core/ports'; // Findings already posted on an EARLIER commit with the anchored line unchanged, or rejected by a human anywhere in this repository. // `j.commit_sha <> me.commit_sha` is load-bearing: retries and mention-triggered re-reviews reuse the SAME head commit. diff --git a/packages/db/src/file-reviews.ts b/packages/db/src/file-reviews.ts index b61c4b77..a89b8440 100644 --- a/packages/db/src/file-reviews.ts +++ b/packages/db/src/file-reviews.ts @@ -1,5 +1,5 @@ import type { DbEnv } from './env'; -import type { ParsedReviewComment } from '@codra/schema'; +import type { ParsedReviewComment } from '@codraoss/schema'; import { parseJsonColumn, queryRows, queryTransaction } from './client'; import { diff --git a/packages/db/src/jobs-mapping.ts b/packages/db/src/jobs-mapping.ts index dc16b28d..5b8f9399 100644 --- a/packages/db/src/jobs-mapping.ts +++ b/packages/db/src/jobs-mapping.ts @@ -1,5 +1,5 @@ import { parseJsonColumn } from './client'; -import { defaultRepoConfig, jobSummarySchema, repoConfigSchema, type RepoConfig } from '@codra/schema'; +import { defaultRepoConfig, jobSummarySchema, repoConfigSchema, type RepoConfig } from '@codraoss/schema'; // Import from db/jobs.ts, not from here: eight specs vi.mock the '@server/db/jobs' specifier, and a direct sibling import silently bypasses them. // Deliberately imports NONE of the other jobs-* siblings, so it stays the leaf they can all depend on. diff --git a/packages/db/src/jobs.ts b/packages/db/src/jobs.ts index 0c540e3f..78582f36 100644 --- a/packages/db/src/jobs.ts +++ b/packages/db/src/jobs.ts @@ -1,7 +1,7 @@ import type { DbEnv } from './env'; -import { hexToBytes } from '@codra/schema/hex'; +import { hexToBytes } from '@codraoss/schema/hex'; import { parseJsonColumn, queryRows } from './client'; -import { defaultRepoConfig, jobDetailSchema, repoConfigSchema, type RepoConfig } from '@codra/schema'; +import { defaultRepoConfig, jobDetailSchema, repoConfigSchema, type RepoConfig } from '@codraoss/schema'; import { getOrCreateRepository } from './repositories'; import { reviewCommentJsonObject } from './review-comment-sql'; import { type JobRow, bytesToHex, mapJob } from './jobs-mapping'; diff --git a/packages/db/src/learning.ts b/packages/db/src/learning.ts index ebd346cd..f314985e 100644 --- a/packages/db/src/learning.ts +++ b/packages/db/src/learning.ts @@ -1,6 +1,6 @@ import type { DbEnv } from './env'; import { queryRows } from './client'; -import type { ClaimType } from '@codra/schema'; +import type { ClaimType } from '@codraoss/schema'; // Reads over findings a human has already judged: report only over the LABELLED subset, always with n. The absence of a label is not a signal. diff --git a/packages/db/src/model-configs.ts b/packages/db/src/model-configs.ts index 918d0fc1..05ecfaae 100644 --- a/packages/db/src/model-configs.ts +++ b/packages/db/src/model-configs.ts @@ -11,7 +11,7 @@ import { type ModelConfig, type ResolvedModelConfig, type LlmProviderSecret, -} from '@codra/schema'; +} from '@codraoss/schema'; export type { ResolvedModelConfig, LlmProviderSecret }; diff --git a/packages/db/src/repo-configs.ts b/packages/db/src/repo-configs.ts index 7764a54b..a6ce3291 100644 --- a/packages/db/src/repo-configs.ts +++ b/packages/db/src/repo-configs.ts @@ -1,7 +1,7 @@ import type { DbEnv } from './env'; import { parseJsonColumn, queryRows } from './client'; -import { defaultRepoConfig, normalizeRepoConfig, repoConfigRecordSchema, repoConfigSchema, type RepoConfig } from '@codra/schema'; +import { defaultRepoConfig, normalizeRepoConfig, repoConfigRecordSchema, repoConfigSchema, type RepoConfig } from '@codraoss/schema'; import { getOrCreateRepository } from './repositories'; type RepoConfigRow = { diff --git a/packages/db/src/repositories/file-review-repository.ts b/packages/db/src/repositories/file-review-repository.ts index d3c78dbd..79052088 100644 --- a/packages/db/src/repositories/file-review-repository.ts +++ b/packages/db/src/repositories/file-review-repository.ts @@ -1,4 +1,4 @@ -import type { FileReviewStore } from '@codra/core/ports'; +import type { FileReviewStore } from '@codraoss/core/ports'; import type { DbEnv } from '../env'; import { bulkInheritFileReviews, diff --git a/packages/db/src/repositories/instance-id-repository.ts b/packages/db/src/repositories/instance-id-repository.ts index 9460ff21..d2cd8e1b 100644 --- a/packages/db/src/repositories/instance-id-repository.ts +++ b/packages/db/src/repositories/instance-id-repository.ts @@ -1,4 +1,4 @@ -import type { InstanceIdStore } from '@codra/core/ports'; +import type { InstanceIdStore } from '@codraoss/core/ports'; import type { DbEnv } from '../env'; import { queryRows } from '../client'; import { INSTANCE_ID_KEY } from '../constants'; diff --git a/packages/db/src/repositories/jobs-repository.ts b/packages/db/src/repositories/jobs-repository.ts index bb50f4b5..e18e91b1 100644 --- a/packages/db/src/repositories/jobs-repository.ts +++ b/packages/db/src/repositories/jobs-repository.ts @@ -1,4 +1,4 @@ -import type { JobLeaseClaim as CoreJobLeaseClaim, JobRow as CoreJobRow, JobStore, PersistedReviewJob } from '@codra/core/ports'; +import type { JobLeaseClaim as CoreJobLeaseClaim, JobRow as CoreJobRow, JobStore, PersistedReviewJob } from '@codraoss/core/ports'; import type { DbEnv } from '../env'; import { claimJobLease, diff --git a/packages/db/src/repositories/repo-config-repository.ts b/packages/db/src/repositories/repo-config-repository.ts index 73adc242..2fb0e3d0 100644 --- a/packages/db/src/repositories/repo-config-repository.ts +++ b/packages/db/src/repositories/repo-config-repository.ts @@ -1,4 +1,4 @@ -import type { RepoConfigStore } from '@codra/core/ports'; +import type { RepoConfigStore } from '@codraoss/core/ports'; import type { DbEnv } from '../env'; import { getRepoConfigRecord, syncRepoConfig } from '../repo-configs'; diff --git a/packages/db/src/repositories/settings-repository.ts b/packages/db/src/repositories/settings-repository.ts index 2e5e29c2..9d908aec 100644 --- a/packages/db/src/repositories/settings-repository.ts +++ b/packages/db/src/repositories/settings-repository.ts @@ -1,4 +1,4 @@ -import type { LearningStore, ModelConfigReader, ReviewSettingsReader, WebhookDeliveryReader } from '@codra/core/ports'; +import type { LearningStore, ModelConfigReader, ReviewSettingsReader, WebhookDeliveryReader } from '@codraoss/core/ports'; import type { DbEnv } from '../env'; import { getReviewSettings } from '../app-settings'; import { getResolvedModelConfig } from '../model-configs'; diff --git a/packages/db/src/review-comment-sql.ts b/packages/db/src/review-comment-sql.ts index 270c701c..5b6ce6d3 100644 --- a/packages/db/src/review-comment-sql.ts +++ b/packages/db/src/review-comment-sql.ts @@ -1,4 +1,4 @@ -import type { ParsedReviewComment } from '@codra/schema'; +import type { ParsedReviewComment } from '@codraoss/schema'; // Shared review_comments field list. Update bulkInheritFileReviews if changed. diff --git a/packages/db/src/stats.ts b/packages/db/src/stats.ts index 25423c69..33795224 100644 --- a/packages/db/src/stats.ts +++ b/packages/db/src/stats.ts @@ -1,7 +1,7 @@ import type { DbEnv } from './env'; -import { isSupportedTimeZone } from '@codra/schema/timezone'; +import { isSupportedTimeZone } from '@codraoss/schema/timezone'; import { queryRows } from './client'; -import { statsSchema, jobStatuses, reviewTriggers, reviewSeverities, reviewCategories } from '@codra/schema'; +import { statsSchema, jobStatuses, reviewTriggers, reviewSeverities, reviewCategories } from '@codraoss/schema'; import { getModelUsageStats } from './file-reviews'; // Guard the zone before it reaches SQL, so an unknown name can't error the query. diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json index f30b5c17..2dfe7a61 100644 --- a/packages/db/tsconfig.json +++ b/packages/db/tsconfig.json @@ -3,10 +3,12 @@ "compilerOptions": { "module": "ESNext", "moduleResolution": "Bundler", - "outDir": "./dist", - "rootDir": "./src", "target": "ES2022", - "types": ["node"] + "types": ["node"], + "composite": false, + "declaration": false, + "emitDeclarationOnly": false, + "noEmit": true }, "include": [ "src/**/*", diff --git a/packages/db/tsup.config.json b/packages/db/tsup.config.json new file mode 100644 index 00000000..e0c19c65 --- /dev/null +++ b/packages/db/tsup.config.json @@ -0,0 +1,9 @@ +{ + "entry": ["src/**/*.ts"], + "format": "esm", + "dts": true, + "splitting": true, + "sourcemap": true, + "clean": true, + "outDir": "dist" +} diff --git a/packages/models/LICENSE b/packages/models/LICENSE new file mode 100644 index 00000000..024299ea --- /dev/null +++ b/packages/models/LICENSE @@ -0,0 +1,625 @@ +GNU Affero General Public License +================================= + +_Version 3, 19 November 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: **(1)** assert copyright on the software, and **(2)** offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +### 0. Definitions +“This License” refers to version 3 of the GNU Affero General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a “modified version” of the +earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based +on the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” +to the extent that it includes a convenient and prominently visible +feature that **(1)** displays an appropriate copyright notice, and **(2)** +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code +The “source code” for a work means the preferred form of the work +for making modifications to it. “Object code” means any non-source +form of a work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other +than the work as a whole, that **(a)** is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and **(b)** serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +“Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +### 2. Basic Permissions +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +### 4. Conveying Verbatim Copies +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified +it, and giving a relevant date. +* **b)** The work must carry prominent notices stating that it is +released under this License and any conditions added under section 7. +This requirement modifies the requirement in section 4 to +“keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. +* **d)** If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +“aggregate” if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either **(1)** a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or **(2)** access to copy the +Corresponding Source from a network server at no charge. +* **c)** Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. +* **d)** Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A “User Product” is either **(1)** a “consumer product”, which means any +tangible personal property which is normally used for personal, family, +or household purposes, or **(2)** anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, “normally used” refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install + +### 6. Conveying Non-Source Forms +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or +authors of the material; or +* **e)** Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +### 8. Termination +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated **(a)** +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and **(b)** permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To “grant” such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either **(1)** cause the Corresponding Source to be so +available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is “discriminatory” if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license **(a)** in connection with copies of the covered work +conveyed by you (or copies made from those copies), or **(b)** primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found. + + Codra: Open source PR review infrastructure for Cloudflare Workers. + Copyright (C) 2026 Devarshi Shimpi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a “Source” link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a “copyright disclaimer” for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<>. diff --git a/packages/models/README.md b/packages/models/README.md new file mode 100644 index 00000000..de57fe41 --- /dev/null +++ b/packages/models/README.md @@ -0,0 +1,15 @@ +# @codraoss/models + +LLM provider adapters and model catalog for the Codra review engine. + +Part of [Codra](https://codra.run), an open-source code review engine. See the [monorepo](https://github.com/devarshishimpi/codra) for development, and [CONTRIBUTING](https://github.com/devarshishimpi/codra/blob/main/CONTRIBUTING.md) for the dual-licensing / CLA details. + +## Install + +```bash +npm install @codraoss/models +``` + +## License + +[AGPL-3.0-only](./LICENSE) © Devarshi Shimpi diff --git a/packages/models/package.json b/packages/models/package.json index 7c6baed9..750d6279 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -1,8 +1,20 @@ { - "name": "@codra/models", + "name": "@codraoss/models", "version": "0.9.4", - "private": true, + "description": "LLM provider adapters and model catalog for the Codra review engine.", + "author": "Devarshi Shimpi", + "license": "AGPL-3.0-only", + "homepage": "https://codra.run", + "repository": { + "type": "git", + "url": "git+https://github.com/devarshishimpi/codra.git", + "directory": "packages/models" + }, + "bugs": { + "url": "https://github.com/devarshishimpi/codra/issues" + }, "type": "module", + "sideEffects": false, "exports": { ".": "./src/index.ts", "./types": "./src/types.ts", @@ -13,14 +25,63 @@ "./anthropic": "./src/providers/anthropic.ts", "./openai": "./src/providers/openai.ts" }, + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./types": { + "types": "./dist/types.d.ts", + "import": "./dist/types.js" + }, + "./runner": { + "types": "./dist/runner.d.ts", + "import": "./dist/runner.js" + }, + "./cloudflare": { + "types": "./dist/providers/cloudflare.d.ts", + "import": "./dist/providers/cloudflare.js" + }, + "./google": { + "types": "./dist/providers/google.d.ts", + "import": "./dist/providers/google.js" + }, + "./vertex": { + "types": "./dist/providers/vertex.d.ts", + "import": "./dist/providers/vertex.js" + }, + "./anthropic": { + "types": "./dist/providers/anthropic.d.ts", + "import": "./dist/providers/anthropic.js" + }, + "./openai": { + "types": "./dist/providers/openai.d.ts", + "import": "./dist/providers/openai.js" + } + } + }, "scripts": { + "build": "tsup", "typecheck": "tsc -p tsconfig.json", - "test": "vitest run" + "test": "vitest run", + "prepack": "node ../../scripts/swap-publish-exports.mjs promote", + "postpack": "node ../../scripts/swap-publish-exports.mjs restore" }, "dependencies": { - "@codra/schema": "*", - "@codra/core": "*" + "@codraoss/schema": "^0.9.4", + "@codraoss/core": "^0.9.4" }, "devDependencies": { + "tsup": "^8.0.0" } } diff --git a/packages/models/src/catalog.ts b/packages/models/src/catalog.ts index 38e12874..459f852e 100644 --- a/packages/models/src/catalog.ts +++ b/packages/models/src/catalog.ts @@ -1,5 +1,5 @@ -import type { LlmApiFormat } from '@codra/schema'; -import { withTimeout } from '@codra/core/timeout'; +import type { LlmApiFormat } from '@codraoss/schema'; +import { withTimeout } from '@codraoss/core/timeout'; import { assertPublicBaseUrl } from './url-guard'; const MODEL_LIST_TIMEOUT_MS = 8_000; diff --git a/packages/models/src/internal/model-chain-progress.ts b/packages/models/src/internal/model-chain-progress.ts index 94cd62ae..c51fb072 100644 --- a/packages/models/src/internal/model-chain-progress.ts +++ b/packages/models/src/internal/model-chain-progress.ts @@ -1,6 +1,6 @@ -import { logger } from '@codra/core/logger'; -import type { KvStore } from '@codra/core/ports'; -import type { TokenTracker } from '@codra/core/token-tracker'; +import { logger } from '@codraoss/core/logger'; +import type { KvStore } from '@codraoss/core/ports'; +import type { TokenTracker } from '@codraoss/core/token-tracker'; import { isPlausibleTokenBucket } from './model-support'; // Stores where each label got to in its model chain so deferred reviews resume properly. diff --git a/packages/models/src/internal/model-chain-runner.ts b/packages/models/src/internal/model-chain-runner.ts index f16ffa73..8d4fcb89 100644 --- a/packages/models/src/internal/model-chain-runner.ts +++ b/packages/models/src/internal/model-chain-runner.ts @@ -1,10 +1,10 @@ -import { buildSummaryPrompt, SUMMARY_SYSTEM_PROMPT } from '@codra/core/prompts/summary'; -import { buildVerifyPrompt, VERIFY_RESPONSE_SCHEMA, VERIFY_SYSTEM_PROMPT, type VerifyCandidate } from '@codra/core/prompts/verify'; +import { buildSummaryPrompt, SUMMARY_SYSTEM_PROMPT } from '@codraoss/core/prompts/summary'; +import { buildVerifyPrompt, VERIFY_RESPONSE_SCHEMA, VERIFY_SYSTEM_PROMPT, type VerifyCandidate } from '@codraoss/core/prompts/verify'; import { adaptiveModelTimeoutMs, clampTimeoutToChainBudget, MODEL_FALLBACK_CHAIN_BUDGET_MS } from '../limits'; import { isCloudflareAllocationError, isTransientModelFailure, RetryableModelError } from './model-support'; -import { logger } from '@codra/core/logger'; -import type { RepoConfig, ResolvedModelConfig } from '@codra/schema'; -import type { TokenTracker } from '@codra/core/token-tracker'; +import { logger } from '@codraoss/core/logger'; +import type { RepoConfig, ResolvedModelConfig } from '@codraoss/schema'; +import type { TokenTracker } from '@codraoss/core/token-tracker'; import type { ModelInput, ModelResponse } from '../types'; // Import from services/model.ts, not here -- four specs vi.mock that specifier. diff --git a/packages/models/src/internal/model-rate-limits.ts b/packages/models/src/internal/model-rate-limits.ts index ac1c7735..e4434f51 100644 --- a/packages/models/src/internal/model-rate-limits.ts +++ b/packages/models/src/internal/model-rate-limits.ts @@ -1,6 +1,6 @@ -import { logger } from '@codra/core/logger'; +import { logger } from '@codraoss/core/logger'; import { ModelCallGate } from '../limits'; -import type { ResolvedModelConfig } from '@codra/schema'; +import type { ResolvedModelConfig } from '@codraoss/schema'; import { MAX_METERED_QUEUE_DEPTH, PROMPT_FIT_SAFETY_FACTOR, parseRateLimitFromError } from './model-support'; // Narrow port onto whatever survives an invocation (today: the job's chain-progress KV value), so diff --git a/packages/models/src/internal/model-review-batch.ts b/packages/models/src/internal/model-review-batch.ts index 3a92d7dd..15430167 100644 --- a/packages/models/src/internal/model-review-batch.ts +++ b/packages/models/src/internal/model-review-batch.ts @@ -1,9 +1,9 @@ import { submitCloudflareBatch, pollCloudflareBatch } from '../providers/cloudflare'; -import { buildFileReviewPrompts, buildReviewResponseSchema } from '@codra/core/prompts/file-review'; -import { parseFileReviewResponse } from '@codra/core/model-output'; -import { truncateFileDiff } from '@codra/core/diff'; -import { logger } from '@codra/core/logger'; -import type { RepoConfig, ResolvedModelConfig } from '@codra/schema'; +import { buildFileReviewPrompts, buildReviewResponseSchema } from '@codraoss/core/prompts/file-review'; +import { parseFileReviewResponse } from '@codraoss/core/model-output'; +import { truncateFileDiff } from '@codraoss/core/diff'; +import { logger } from '@codraoss/core/logger'; +import type { RepoConfig, ResolvedModelConfig } from '@codraoss/schema'; import type { ModelResponse } from '../types'; import { COMPACT_REVIEW_PROMPT_LINE_CAP, type ModelReviewContext } from './model-review-file'; diff --git a/packages/models/src/internal/model-review-chain.ts b/packages/models/src/internal/model-review-chain.ts index 01733be0..dae2560e 100644 --- a/packages/models/src/internal/model-review-chain.ts +++ b/packages/models/src/internal/model-review-chain.ts @@ -1,6 +1,6 @@ -import { logger } from '@codra/core/logger'; -import { isSubrequestBudgetMessage, isTimeoutMessage } from '@codra/schema/transient-errors'; -import type { RepoConfig, ResolvedModelConfig } from '@codra/schema'; +import { logger } from '@codraoss/core/logger'; +import { isSubrequestBudgetMessage, isTimeoutMessage } from '@codraoss/schema/transient-errors'; +import type { RepoConfig, ResolvedModelConfig } from '@codraoss/schema'; import type { CloudflareAiBinding } from '../providers/cloudflare'; import type { ModelResponseSchema } from '../types'; import { clampTimeoutToChainBudget, MODEL_FALLBACK_CHAIN_BUDGET_MS, SUBREQUEST_HEADROOM_FOR_MODEL_CALL } from '../limits'; diff --git a/packages/models/src/internal/model-review-file.ts b/packages/models/src/internal/model-review-file.ts index 690f5540..7756abd1 100644 --- a/packages/models/src/internal/model-review-file.ts +++ b/packages/models/src/internal/model-review-file.ts @@ -4,16 +4,16 @@ import { buildFileReviewPrompts, buildReviewResponseSchema, type RejectedExemplar, -} from '@codra/core/prompts/file-review'; -import { isNonAnswerReview, parseBatchReviewResponse, parseFileReviewResponse, type BatchReviewResult } from '@codra/core/model-output'; +} from '@codraoss/core/prompts/file-review'; +import { isNonAnswerReview, parseBatchReviewResponse, parseFileReviewResponse, type BatchReviewResult } from '@codraoss/core/model-output'; import { UnparseableModelResponseError } from '../types'; -import { chunkFileDiff, type FileDiff } from '@codra/core/diff'; +import { chunkFileDiff, type FileDiff } from '@codraoss/core/diff'; import { adaptiveModelTimeoutMs, reviewOutputBudgetTokens } from '../limits'; -import { generatorFindingCap } from '@codra/core/prompts/file-review'; +import { generatorFindingCap } from '@codraoss/core/prompts/file-review'; import { mergeCounts } from './model-support'; import { type ModelReviewContext, runModelChain } from './model-review-chain'; -import { logger } from '@codra/core/logger'; -import type { RepoConfig } from '@codra/schema'; +import { logger } from '@codraoss/core/logger'; +import type { RepoConfig } from '@codraoss/schema'; import type { ModelResponse } from '../types'; // Import from the services/model barrel, not here (four specs vi.mock it). diff --git a/packages/models/src/internal/model-support.ts b/packages/models/src/internal/model-support.ts index 9ab9e7b4..2a34018c 100644 --- a/packages/models/src/internal/model-support.ts +++ b/packages/models/src/internal/model-support.ts @@ -1,5 +1,5 @@ -import { normalizeModelId } from '@codra/schema'; -import { isTimeoutMessage, matchesAnyTransientSubstring } from '@codra/schema/transient-errors'; +import { normalizeModelId } from '@codraoss/schema'; +import { isTimeoutMessage, matchesAnyTransientSubstring } from '@codraoss/schema/transient-errors'; import { UnparseableModelResponseError } from '../types'; // Model service pure helpers: aliases, prompt sizes, rate limits, errors. diff --git a/packages/models/src/llm-crypto.ts b/packages/models/src/llm-crypto.ts index ca01e8ed..131fc32b 100644 --- a/packages/models/src/llm-crypto.ts +++ b/packages/models/src/llm-crypto.ts @@ -1,4 +1,4 @@ -import type { SecretStore } from '@codra/core/ports'; +import type { SecretStore } from '@codraoss/core/ports'; const KEY_VERSION = 'v1'; const encoder = new TextEncoder(); diff --git a/packages/models/src/providers/anthropic.ts b/packages/models/src/providers/anthropic.ts index 679a8c70..e4fb8159 100644 --- a/packages/models/src/providers/anthropic.ts +++ b/packages/models/src/providers/anthropic.ts @@ -1,5 +1,5 @@ -import { logger } from '@codra/core/logger'; -import { withTimeout } from '@codra/core/timeout'; +import { logger } from '@codraoss/core/logger'; +import { withTimeout } from '@codraoss/core/timeout'; import { ProviderRequestError, providerErrorMessage, jsonOnlyPrompts, type ModelResponse } from '../types'; import { assertPublicBaseUrl } from '../url-guard'; import { MODEL_TIMEOUT_MAX_MS, resolveOutputTokenCeiling } from '../limits'; diff --git a/packages/models/src/providers/cloudflare.ts b/packages/models/src/providers/cloudflare.ts index f91884f2..8381267b 100644 --- a/packages/models/src/providers/cloudflare.ts +++ b/packages/models/src/providers/cloudflare.ts @@ -1,6 +1,6 @@ -import { logger } from '@codra/core/logger'; +import { logger } from '@codraoss/core/logger'; -import { TimeoutError } from '@codra/core/timeout'; +import { TimeoutError } from '@codraoss/core/timeout'; import { ProviderRequestError, UnparseableModelResponseError, jsonOnlyPrompts, type ModelInput, type ModelResponse } from '../types'; import { MODEL_TIMEOUT_MAX_MS, OUTPUT_TOKENS_FLOOR, resolveOutputTokenCeiling } from '../limits'; diff --git a/packages/models/src/providers/google.ts b/packages/models/src/providers/google.ts index a59cc9b2..7fffb0d4 100644 --- a/packages/models/src/providers/google.ts +++ b/packages/models/src/providers/google.ts @@ -1,5 +1,5 @@ -import { logger } from '@codra/core/logger'; -import { withTimeout } from '@codra/core/timeout'; +import { logger } from '@codraoss/core/logger'; +import { withTimeout } from '@codraoss/core/timeout'; import { ProviderRequestError, UnparseableModelResponseError, providerErrorMessage, jsonOnlyPrompts, type ModelInput, type ModelResponse } from '../types'; import { toGeminiResponseJsonSchema } from '../gemini-schema'; import { assertPublicBaseUrl } from '../url-guard'; diff --git a/packages/models/src/providers/openai.ts b/packages/models/src/providers/openai.ts index 866c386e..4c5e1a85 100644 --- a/packages/models/src/providers/openai.ts +++ b/packages/models/src/providers/openai.ts @@ -1,5 +1,5 @@ -import { logger } from '@codra/core/logger'; -import { withTimeout } from '@codra/core/timeout'; +import { logger } from '@codraoss/core/logger'; +import { withTimeout } from '@codraoss/core/timeout'; import { ProviderRequestError, providerErrorMessage, jsonOnlyPrompts, type ModelResponse } from '../types'; import { assertPublicBaseUrl } from '../url-guard'; import { MODEL_TIMEOUT_MAX_MS, resolveOutputTokenCeiling } from '../limits'; diff --git a/packages/models/src/providers/vertex.ts b/packages/models/src/providers/vertex.ts index 8a9fb201..dd6a5084 100644 --- a/packages/models/src/providers/vertex.ts +++ b/packages/models/src/providers/vertex.ts @@ -1,5 +1,5 @@ -import { logger } from '@codra/core/logger'; -import { withTimeout } from '@codra/core/timeout'; +import { logger } from '@codraoss/core/logger'; +import { withTimeout } from '@codraoss/core/timeout'; import { ProviderRequestError, UnparseableModelResponseError, providerErrorMessage, jsonOnlyPrompts, type ModelResponse } from '../types'; import { assertPublicBaseUrl } from '../url-guard'; import { MODEL_TIMEOUT_MAX_MS, OUTPUT_TOKENS_FLOOR, resolveOutputTokenCeiling } from '../limits'; diff --git a/packages/models/src/runner.ts b/packages/models/src/runner.ts index d5d25ab4..dcdda936 100644 --- a/packages/models/src/runner.ts +++ b/packages/models/src/runner.ts @@ -1,15 +1,15 @@ -import type { KvStore, SecretStore } from '@codra/core/ports'; +import type { KvStore, SecretStore } from '@codraoss/core/ports'; import type { CloudflareAiBinding } from './providers/cloudflare'; import { reviewWithGoogle } from './providers/google'; import { reviewWithVertex } from './providers/vertex'; import { reviewWithCloudflare } from './providers/cloudflare'; import { reviewWithOpenAI } from './providers/openai'; import { reviewWithAnthropic } from './providers/anthropic'; -import type { VerifyCandidate } from '@codra/core/prompts/verify'; -import type { RepoConfig, ResolvedModelConfig } from '@codra/schema'; -import type { TokenTracker } from '@codra/core/token-tracker'; +import type { VerifyCandidate } from '@codraoss/core/prompts/verify'; +import type { RepoConfig, ResolvedModelConfig } from '@codraoss/schema'; +import type { TokenTracker } from '@codraoss/core/token-tracker'; import type { ModelInput, ModelResponse } from './types'; -import { logger } from '@codra/core/logger'; +import { logger } from '@codraoss/core/logger'; import { decryptLlmApiKey } from './llm-crypto'; import { isSchemaDroppedError, @@ -24,7 +24,7 @@ import { type ModelReviewContext, reviewFile, reviewFiles } from './internal/mod export type { BatchReviewOutcome } from './internal/model-review-file'; import { pollReviewBatch, submitReviewBatch } from './internal/model-review-batch'; -// Re-exported: core/review.ts and two specs import these from '@codra/models'. +// Re-exported: core/review.ts and two specs import these from '@codraoss/models'. export { RetryableModelError, isRetryableModelError, nextChainIndexOf } from './internal/model-support'; // Re-exported so the batch-prompt budget test asserts against these constants, not a copy. diff --git a/packages/models/src/types.ts b/packages/models/src/types.ts index 12da12ac..907560d3 100644 --- a/packages/models/src/types.ts +++ b/packages/models/src/types.ts @@ -1,8 +1,8 @@ -// Both live in @codra/core/ports now: prompts/file-review.ts builds a ModelResponseSchema, and it is +// Both live in @codraoss/core/ports now: prompts/file-review.ts builds a ModelResponseSchema, and it is // the only reason a pure prompt module ever imported from models/. Re-exported here so the ~20 -// existing `@codra/models/types` importers are unaffected, and so there is exactly one definition. -import type { ModelResponseSchema } from '@codra/core/ports'; -export type { ModelResponse, ModelResponseSchema } from '@codra/core/ports'; +// existing `@codraoss/models/types` importers are unaffected, and so there is exactly one definition. +import type { ModelResponseSchema } from '@codraoss/core/ports'; +export type { ModelResponse, ModelResponseSchema } from '@codraoss/core/ports'; // `responseSchema` is per-call on purpose: file review, verification, and summary each need a different output shape. export type ModelInput = { diff --git a/packages/models/test/model/batch-routing.spec.ts b/packages/models/test/model/batch-routing.spec.ts index 0617fcb1..0d541c52 100644 --- a/packages/models/test/model/batch-routing.spec.ts +++ b/packages/models/test/model/batch-routing.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { parseBatchReviewResponse } from '@codra/core/model-output'; -import type { FileDiff } from '@codra/core/diff'; +import { parseBatchReviewResponse } from '@codraoss/core/model-output'; +import type { FileDiff } from '@codraoss/core/diff'; function file(path: string, contents: string[], previousPath: string | null = null): FileDiff { return { diff --git a/packages/models/test/model/chain-progress-store.spec.ts b/packages/models/test/model/chain-progress-store.spec.ts index 40edd4b8..20937625 100644 --- a/packages/models/test/model/chain-progress-store.spec.ts +++ b/packages/models/test/model/chain-progress-store.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { ModelChainProgressStore } from '@codra/models'; +import { ModelChainProgressStore } from '@codraoss/models'; // KV double tracks overlapping puts (KV lacks ordering; late puts with less state can revert progress). function makeKV() { diff --git a/packages/models/test/model/chain-resume.spec.ts b/packages/models/test/model/chain-resume.spec.ts index 20ea01d2..84328332 100644 --- a/packages/models/test/model/chain-resume.spec.ts +++ b/packages/models/test/model/chain-resume.spec.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { nextChainIndexOf, ModelRunner } from '@codra/models'; -import { defaultRepoConfig } from '@codra/schema'; -import { TokenTracker } from '@codra/core/token-tracker'; +import { nextChainIndexOf, ModelRunner } from '@codraoss/models'; +import { defaultRepoConfig } from '@codraoss/schema'; +import { TokenTracker } from '@codraoss/core/token-tracker'; import { createTestEnv, saveTestProviderApiKey, createTestModelRunner } from '../../../../test/helpers'; // One invocation only affords ~55s of model calls, so a chain whose head is slow never reaches its diff --git a/packages/models/test/model/cloudflare.spec.ts b/packages/models/test/model/cloudflare.spec.ts index ecb39aa5..21db037e 100644 --- a/packages/models/test/model/cloudflare.spec.ts +++ b/packages/models/test/model/cloudflare.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from 'vitest'; -import { reviewWithCloudflare, submitCloudflareBatch, pollCloudflareBatch } from '@codra/models/cloudflare'; +import { reviewWithCloudflare, submitCloudflareBatch, pollCloudflareBatch } from '@codraoss/models/cloudflare'; // Regression: some Workers AI models (e.g. @cf/qwen/qwen2.5-coder-32b-instruct honoring // response_format) return `response` as an already-parsed JSON object/array rather than a string. diff --git a/packages/models/test/model/config-cache.spec.ts b/packages/models/test/model/config-cache.spec.ts index 04060050..51f1d4ba 100644 --- a/packages/models/test/model/config-cache.spec.ts +++ b/packages/models/test/model/config-cache.spec.ts @@ -1,11 +1,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { createTestEnv, createTestModelRunner } from '../../../../test/helpers'; -// Isolated in its own file: mocking @codra/db/model-configs module-wide would break the +// Isolated in its own file: mocking @codraoss/db/model-configs module-wide would break the // other model-service tests that resolve configs against the real test DB. const getResolvedModelConfigMock = vi.hoisted(() => vi.fn()); -vi.mock('@codra/db/model-configs', async (importOriginal) => { +vi.mock('@codraoss/db/model-configs', async (importOriginal) => { const mod = await importOriginal(); return { ...mod, getResolvedModelConfig: getResolvedModelConfigMock }; }); diff --git a/packages/models/test/model/gemini-schema.spec.ts b/packages/models/test/model/gemini-schema.spec.ts index 9bafdc9e..b6a6d693 100644 --- a/packages/models/test/model/gemini-schema.spec.ts +++ b/packages/models/test/model/gemini-schema.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { toGeminiResponseJsonSchema } from '../../src/gemini-schema'; -import { buildBatchReviewResponseSchema, buildReviewResponseSchema } from '@codra/core/prompts/file-review'; -import { VERIFY_RESPONSE_SCHEMA } from '@codra/core/prompts/verify'; +import { buildBatchReviewResponseSchema, buildReviewResponseSchema } from '@codraoss/core/prompts/file-review'; +import { VERIFY_RESPONSE_SCHEMA } from '@codraoss/core/prompts/verify'; // Transformations asserted on the pure function; the adapter specs only check a grammar reaches the // wire. Every failure mode here is silent -- a mangled grammar still returns 200. diff --git a/packages/models/test/model/limits.spec.ts b/packages/models/test/model/limits.spec.ts index 3b3a860d..15231967 100644 --- a/packages/models/test/model/limits.spec.ts +++ b/packages/models/test/model/limits.spec.ts @@ -11,7 +11,7 @@ import { resolveOutputTokenCeiling, reviewOutputBudgetTokens, } from '../../src/limits'; -import { generatorFindingCap } from '@codra/core/prompts/file-review'; +import { generatorFindingCap } from '@codraoss/core/prompts/file-review'; // The whole point of these: a bin that overruns `maxOutputTokens` comes back as a repaired JSON prefix // with its tail files silently empty, which is indistinguishable from "those files are clean". diff --git a/packages/models/test/model/output-batch.spec.ts b/packages/models/test/model/output-batch.spec.ts index 524d92e1..88d79cd4 100644 --- a/packages/models/test/model/output-batch.spec.ts +++ b/packages/models/test/model/output-batch.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { parseRawBatchPayload } from '@codra/core/model-output'; +import { parseRawBatchPayload } from '@codraoss/core/model-output'; function nested(paths: string[]) { return { diff --git a/packages/models/test/model/output.spec.ts b/packages/models/test/model/output.spec.ts index f0687391..e143bc39 100644 --- a/packages/models/test/model/output.spec.ts +++ b/packages/models/test/model/output.spec.ts @@ -1,6 +1,6 @@ -import { parseFileReviewResponse, dedupeFindings } from '@codra/core/model-output'; -import type { FileDiff } from '@codra/core/diff'; -import type { ParsedReviewComment } from '@codra/schema'; +import { parseFileReviewResponse, dedupeFindings } from '@codraoss/core/model-output'; +import type { FileDiff } from '@codraoss/core/diff'; +import type { ParsedReviewComment } from '@codraoss/schema'; describe('Model Output Parsing Deep Dive', () => { const mockFile: FileDiff = { diff --git a/packages/models/test/model/rate-limit-parse.spec.ts b/packages/models/test/model/rate-limit-parse.spec.ts index ad184af2..7fe41613 100644 --- a/packages/models/test/model/rate-limit-parse.spec.ts +++ b/packages/models/test/model/rate-limit-parse.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { isPlausibleTokenBucket, parseRateLimitFromError } from '@codra/models'; +import { isPlausibleTokenBucket, parseRateLimitFromError } from '@codraoss/models'; // Verbatim from production: a free-tier 429 whose only stated quota counts REQUESTS, not tokens. const REQUESTS_QUOTA_429 = [ diff --git a/packages/models/test/model/service-chunking.spec.ts b/packages/models/test/model/service-chunking.spec.ts index 4631afc6..fb1a21f6 100644 --- a/packages/models/test/model/service-chunking.spec.ts +++ b/packages/models/test/model/service-chunking.spec.ts @@ -1,15 +1,15 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { ModelRunner } from '@codra/models'; +import { ModelRunner } from '@codraoss/models'; import { createTestEnv, saveTestProviderApiKey, createTestModelRunner } from '../../../../test/helpers'; -import { defaultRepoConfig } from '@codra/schema'; -import { TokenTracker } from '@codra/core/token-tracker'; +import { defaultRepoConfig } from '@codraoss/schema'; +import { TokenTracker } from '@codraoss/core/token-tracker'; import { geminiThinkingBudgetTokens, reviewOutputBudgetTokens } from '../../src/limits'; -import { generatorFindingCap } from '@codra/core/prompts/file-review'; +import { generatorFindingCap } from '@codraoss/core/prompts/file-review'; describe('ModelRunner: diff chunking', () => { afterEach(() => { diff --git a/packages/models/test/model/service-fallbacks.spec.ts b/packages/models/test/model/service-fallbacks.spec.ts index c365c189..369015a2 100644 --- a/packages/models/test/model/service-fallbacks.spec.ts +++ b/packages/models/test/model/service-fallbacks.spec.ts @@ -1,10 +1,10 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { isRetryableModelError } from '@codra/models'; +import { isRetryableModelError } from '@codraoss/models'; import { createTestEnv, saveTestProviderApiKey, createTestModelRunner } from '../../../../test/helpers'; -import { defaultRepoConfig } from '@codra/schema'; -import { TokenTracker } from '@codra/core/token-tracker'; +import { defaultRepoConfig } from '@codraoss/schema'; +import { TokenTracker } from '@codraoss/core/token-tracker'; // Walking the model chain: fallback, the two subrequest-budget breakers, and marking a provider // unavailable. The inline retry ladder lives in service-retries.spec.ts. diff --git a/packages/models/test/model/service-grammar-rejection.spec.ts b/packages/models/test/model/service-grammar-rejection.spec.ts index 3113fd08..2d0d8562 100644 --- a/packages/models/test/model/service-grammar-rejection.spec.ts +++ b/packages/models/test/model/service-grammar-rejection.spec.ts @@ -1,9 +1,9 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { reviewWithGoogle } from '@codra/models/google'; -import { buildReviewResponseSchema } from '@codra/core/prompts/file-review'; +import { reviewWithGoogle } from '@codraoss/models/google'; +import { buildReviewResponseSchema } from '@codraoss/core/prompts/file-review'; import { createTestEnv, saveTestProviderApiKey, createTestModelRunner } from '../../../../test/helpers'; -import { defaultRepoConfig } from '@codra/schema'; +import { defaultRepoConfig } from '@codraoss/schema'; // Split out of service-retries.spec.ts: a 400 matches no transient pattern, so grammar rejection is // its own ladder rung -- drop responseJsonSchema, retry once, latch it off -- not part of the diff --git a/packages/models/test/model/service-requests.spec.ts b/packages/models/test/model/service-requests.spec.ts index b8728877..82729ba5 100644 --- a/packages/models/test/model/service-requests.spec.ts +++ b/packages/models/test/model/service-requests.spec.ts @@ -1,10 +1,10 @@ import { afterEach, describe, expect, it } from 'vitest'; -import { reviewWithCloudflare } from '@codra/models/cloudflare'; -import { reviewWithGoogle } from '@codra/models/google'; +import { reviewWithCloudflare } from '@codraoss/models/cloudflare'; +import { reviewWithGoogle } from '@codraoss/models/google'; -import { buildBatchReviewResponseSchema, buildReviewResponseSchema } from '@codra/core/prompts/file-review'; -import { VERIFY_RESPONSE_SCHEMA } from '@codra/core/prompts/verify'; +import { buildBatchReviewResponseSchema, buildReviewResponseSchema } from '@codraoss/core/prompts/file-review'; +import { VERIFY_RESPONSE_SCHEMA } from '@codraoss/core/prompts/verify'; import { createTestEnv, saveTestProviderApiKey, createTestModelRunner } from '../../../../test/helpers'; diff --git a/packages/models/test/model/service-retries.spec.ts b/packages/models/test/model/service-retries.spec.ts index 64acd4e3..d01f2f07 100644 --- a/packages/models/test/model/service-retries.spec.ts +++ b/packages/models/test/model/service-retries.spec.ts @@ -1,10 +1,10 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { isRetryableModelError } from '@codra/models'; -import { reviewWithCloudflare } from '@codra/models/cloudflare'; -import { reviewWithGoogle } from '@codra/models/google'; +import { isRetryableModelError } from '@codraoss/models'; +import { reviewWithCloudflare } from '@codraoss/models/cloudflare'; +import { reviewWithGoogle } from '@codraoss/models/google'; import { MODEL_TIMEOUT_MAX_MS } from '../../src/limits'; import { createTestEnv, saveTestProviderApiKey, createTestModelRunner } from '../../../../test/helpers'; -import { defaultRepoConfig } from '@codra/schema'; +import { defaultRepoConfig } from '@codraoss/schema'; // The retry ladder: inline retries, Retry-After, and which exhausted runs report as retryable. describe('ModelRunner: transient failures and the retry ladder', () => { diff --git a/packages/models/test/url-guard.spec.ts b/packages/models/test/url-guard.spec.ts index e369c411..e0e42af0 100644 --- a/packages/models/test/url-guard.spec.ts +++ b/packages/models/test/url-guard.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { assertPublicBaseUrl, isPrivateHost, isValidPublicUrl } from '../src/url-guard'; -import { ProviderRequestError } from '@codra/models/types'; +import { ProviderRequestError } from '../src/types'; // A provider's base URL comes from the dashboard and is then fetched server-side, so an unguarded // adapter turns that form into an SSRF primitive. diff --git a/packages/models/tsup.config.json b/packages/models/tsup.config.json new file mode 100644 index 00000000..67531a67 --- /dev/null +++ b/packages/models/tsup.config.json @@ -0,0 +1,18 @@ +{ + "entry": [ + "src/index.ts", + "src/types.ts", + "src/runner.ts", + "src/providers/cloudflare.ts", + "src/providers/google.ts", + "src/providers/vertex.ts", + "src/providers/anthropic.ts", + "src/providers/openai.ts" + ], + "format": "esm", + "dts": true, + "splitting": true, + "sourcemap": true, + "clean": true, + "outDir": "dist" +} diff --git a/packages/provider-github/LICENSE b/packages/provider-github/LICENSE new file mode 100644 index 00000000..024299ea --- /dev/null +++ b/packages/provider-github/LICENSE @@ -0,0 +1,625 @@ +GNU Affero General Public License +================================= + +_Version 3, 19 November 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: **(1)** assert copyright on the software, and **(2)** offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +### 0. Definitions +“This License” refers to version 3 of the GNU Affero General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a “modified version” of the +earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based +on the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” +to the extent that it includes a convenient and prominently visible +feature that **(1)** displays an appropriate copyright notice, and **(2)** +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code +The “source code” for a work means the preferred form of the work +for making modifications to it. “Object code” means any non-source +form of a work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other +than the work as a whole, that **(a)** is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and **(b)** serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +“Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +### 2. Basic Permissions +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +### 4. Conveying Verbatim Copies +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified +it, and giving a relevant date. +* **b)** The work must carry prominent notices stating that it is +released under this License and any conditions added under section 7. +This requirement modifies the requirement in section 4 to +“keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. +* **d)** If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +“aggregate” if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either **(1)** a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or **(2)** access to copy the +Corresponding Source from a network server at no charge. +* **c)** Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. +* **d)** Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A “User Product” is either **(1)** a “consumer product”, which means any +tangible personal property which is normally used for personal, family, +or household purposes, or **(2)** anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, “normally used” refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install + +### 6. Conveying Non-Source Forms +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or +authors of the material; or +* **e)** Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +### 8. Termination +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated **(a)** +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and **(b)** permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To “grant” such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either **(1)** cause the Corresponding Source to be so +available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is “discriminatory” if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license **(a)** in connection with copies of the covered work +conveyed by you (or copies made from those copies), or **(b)** primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found. + + Codra: Open source PR review infrastructure for Cloudflare Workers. + Copyright (C) 2026 Devarshi Shimpi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a “Source” link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a “copyright disclaimer” for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<>. diff --git a/packages/provider-github/README.md b/packages/provider-github/README.md new file mode 100644 index 00000000..66c75fc9 --- /dev/null +++ b/packages/provider-github/README.md @@ -0,0 +1,15 @@ +# @codraoss/provider-github + +GitHub git-provider adapter for Codra: App auth, webhooks, OAuth, and review posting. + +Part of [Codra](https://codra.run), an open-source code review engine. See the [monorepo](https://github.com/devarshishimpi/codra) for development, and [CONTRIBUTING](https://github.com/devarshishimpi/codra/blob/main/CONTRIBUTING.md) for the dual-licensing / CLA details. + +## Install + +```bash +npm install @codraoss/provider-github +``` + +## License + +[AGPL-3.0-only](./LICENSE) © Devarshi Shimpi diff --git a/packages/provider-github/package.json b/packages/provider-github/package.json index 831124fb..460e0b2a 100644 --- a/packages/provider-github/package.json +++ b/packages/provider-github/package.json @@ -1,19 +1,62 @@ { - "name": "@codra/provider-github", + "name": "@codraoss/provider-github", "version": "0.9.4", - "private": true, + "description": "GitHub git-provider adapter for Codra: App auth, webhooks, OAuth, and review posting.", + "author": "Devarshi Shimpi", + "license": "AGPL-3.0-only", + "homepage": "https://codra.run", + "repository": { + "type": "git", + "url": "git+https://github.com/devarshishimpi/codra.git", + "directory": "packages/provider-github" + }, + "bugs": { + "url": "https://github.com/devarshishimpi/codra/issues" + }, "type": "module", + "sideEffects": false, "exports": { ".": "./src/index.ts", "./oauth": "./src/oauth.ts", "./webhook": "./src/webhook.ts" }, + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./oauth": { + "types": "./dist/oauth.d.ts", + "import": "./dist/oauth.js" + }, + "./webhook": { + "types": "./dist/webhook.d.ts", + "import": "./dist/webhook.js" + } + } + }, "scripts": { + "build": "tsup", "typecheck": "tsc -p tsconfig.json", - "test": "vitest run" + "test": "vitest run", + "prepack": "node ../../scripts/swap-publish-exports.mjs promote", + "postpack": "node ../../scripts/swap-publish-exports.mjs restore" }, "dependencies": { - "@codra/core": "*", - "@codra/schema": "*" + "@codraoss/core": "^0.9.4", + "@codraoss/schema": "^0.9.4" + }, + "devDependencies": { + "tsup": "^8.0.0" } } diff --git a/packages/provider-github/src/app-auth.ts b/packages/provider-github/src/app-auth.ts index 046fa2e7..afa78e49 100644 --- a/packages/provider-github/src/app-auth.ts +++ b/packages/provider-github/src/app-auth.ts @@ -1,5 +1,5 @@ import type { AppBindingsConfig } from './service'; -import { withTimeout } from '@codra/core/timeout'; +import { withTimeout } from '@codraoss/core/timeout'; import { assertResponseOk, installationCacheKey, withRetry } from './http'; import type { GitHubAppRecord, GitHubInstallation, InstallationTokenCacheRecord } from './types'; import { GITHUB_TIMEOUT_MS, GITHUB_APP_INSTALL_URL_CACHE_KEY } from './constants'; diff --git a/packages/provider-github/src/client.ts b/packages/provider-github/src/client.ts index 27d0dce8..30bf83da 100644 --- a/packages/provider-github/src/client.ts +++ b/packages/provider-github/src/client.ts @@ -1,5 +1,5 @@ import type { AppBindingsConfig } from './service'; -import { withTimeout } from '@codra/core/timeout'; +import { withTimeout } from '@codraoss/core/timeout'; import { GitHubError, assertResponseOk, diff --git a/packages/provider-github/src/diff-fetch.ts b/packages/provider-github/src/diff-fetch.ts index d8806c47..bf66502f 100644 --- a/packages/provider-github/src/diff-fetch.ts +++ b/packages/provider-github/src/diff-fetch.ts @@ -1,5 +1,5 @@ -import { logger } from '@codra/core/logger'; -import { buildUnifiedDiffFromFiles, type DiffFileEntry } from '@codra/core/diff'; +import { logger } from '@codraoss/core/logger'; +import { buildUnifiedDiffFromFiles, type DiffFileEntry } from '@codraoss/core/diff'; import { type GitHubRequestContext, isDiffTooLargeError, repoApiPath, withRetry } from './http'; import { DIFF_FILES_PER_PAGE, MAX_DIFF_FILE_PAGES } from './constants'; diff --git a/packages/provider-github/src/http.ts b/packages/provider-github/src/http.ts index a31f185d..91d39d2d 100644 --- a/packages/provider-github/src/http.ts +++ b/packages/provider-github/src/http.ts @@ -1,4 +1,4 @@ -import { logger } from '@codra/core/logger'; +import { logger } from '@codraoss/core/logger'; export class GitHubError extends Error { constructor( diff --git a/packages/provider-github/src/oauth.ts b/packages/provider-github/src/oauth.ts index 3bab3808..81eef86d 100644 --- a/packages/provider-github/src/oauth.ts +++ b/packages/provider-github/src/oauth.ts @@ -1,4 +1,4 @@ -import type { DashboardSessionUser, IdentityProvider, AuthorizationResult } from '@codra/core'; +import type { DashboardSessionUser, IdentityProvider, AuthorizationResult } from '@codraoss/core'; import type { AppBindingsConfig } from './service'; export type GitHubOAuthProfile = { diff --git a/packages/provider-github/src/review-post.ts b/packages/provider-github/src/review-post.ts index 929b0b2a..edf2757a 100644 --- a/packages/provider-github/src/review-post.ts +++ b/packages/provider-github/src/review-post.ts @@ -1,4 +1,4 @@ -import { logger } from '@codra/core/logger'; +import { logger } from '@codraoss/core/logger'; import { assertResponseOk, type GitHubRequestContext, repoApiPath, withRetry } from './http'; import type { ReviewComment } from './types'; diff --git a/packages/provider-github/src/types.ts b/packages/provider-github/src/types.ts index fbcc3b01..e770bed0 100644 --- a/packages/provider-github/src/types.ts +++ b/packages/provider-github/src/types.ts @@ -1,6 +1,6 @@ -// Both of these are part of the git-provider PORT contract, so @codra/core/ports owns them and +// Both of these are part of the git-provider PORT contract, so @codraoss/core/ports owns them and // this module re-exports: one definition, and the engine does not depend on this file. -export type { ReviewComment, PullRequestRecord } from '@codra/core/ports'; +export type { ReviewComment, PullRequestRecord } from '@codraoss/core/ports'; // Response shapes from the GitHub REST API, narrowed to the fields this app reads. // Import these from @server/core/github, not from here: specs mock that barrel by replacing the whole GitHubClient class. diff --git a/packages/provider-github/src/webhook.ts b/packages/provider-github/src/webhook.ts index ead51a08..c4163c3b 100644 --- a/packages/provider-github/src/webhook.ts +++ b/packages/provider-github/src/webhook.ts @@ -1,5 +1,5 @@ -import type { WebhookPayload, WebhookEventName } from '@codra/schema/webhook'; -import type { PullRequestWebhookPayload, IssueCommentWebhookPayload } from '@codra/schema/github'; +import type { WebhookPayload, WebhookEventName } from '@codraoss/schema/webhook'; +import type { PullRequestWebhookPayload, IssueCommentWebhookPayload } from '@codraoss/schema/github'; export function normalizeGitHubWebhook( eventName: string, diff --git a/packages/provider-github/tsup.config.json b/packages/provider-github/tsup.config.json new file mode 100644 index 00000000..3c001c21 --- /dev/null +++ b/packages/provider-github/tsup.config.json @@ -0,0 +1,9 @@ +{ + "entry": ["src/index.ts", "src/oauth.ts", "src/webhook.ts"], + "format": "esm", + "dts": true, + "splitting": true, + "sourcemap": true, + "clean": true, + "outDir": "dist" +} diff --git a/packages/schema/LICENSE b/packages/schema/LICENSE new file mode 100644 index 00000000..024299ea --- /dev/null +++ b/packages/schema/LICENSE @@ -0,0 +1,625 @@ +GNU Affero General Public License +================================= + +_Version 3, 19 November 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: **(1)** assert copyright on the software, and **(2)** offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +### 0. Definitions +“This License” refers to version 3 of the GNU Affero General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a “modified version” of the +earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based +on the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” +to the extent that it includes a convenient and prominently visible +feature that **(1)** displays an appropriate copyright notice, and **(2)** +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code +The “source code” for a work means the preferred form of the work +for making modifications to it. “Object code” means any non-source +form of a work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other +than the work as a whole, that **(a)** is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and **(b)** serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +“Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +### 2. Basic Permissions +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +### 4. Conveying Verbatim Copies +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified +it, and giving a relevant date. +* **b)** The work must carry prominent notices stating that it is +released under this License and any conditions added under section 7. +This requirement modifies the requirement in section 4 to +“keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. +* **d)** If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +“aggregate” if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either **(1)** a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or **(2)** access to copy the +Corresponding Source from a network server at no charge. +* **c)** Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. +* **d)** Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A “User Product” is either **(1)** a “consumer product”, which means any +tangible personal property which is normally used for personal, family, +or household purposes, or **(2)** anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, “normally used” refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install + +### 6. Conveying Non-Source Forms +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or +authors of the material; or +* **e)** Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +### 8. Termination +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated **(a)** +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and **(b)** permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To “grant” such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either **(1)** cause the Corresponding Source to be so +available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is “discriminatory” if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license **(a)** in connection with copies of the covered work +conveyed by you (or copies made from those copies), or **(b)** primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found. + + Codra: Open source PR review infrastructure for Cloudflare Workers. + Copyright (C) 2026 Devarshi Shimpi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a “Source” link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a “copyright disclaimer” for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<>. diff --git a/packages/schema/README.md b/packages/schema/README.md new file mode 100644 index 00000000..bbe8e484 --- /dev/null +++ b/packages/schema/README.md @@ -0,0 +1,15 @@ +# @codraoss/schema + +Shared types and Zod contracts for Codra, the open-source code review engine. + +Part of [Codra](https://codra.run), an open-source code review engine. See the [monorepo](https://github.com/devarshishimpi/codra) for development, and [CONTRIBUTING](https://github.com/devarshishimpi/codra/blob/main/CONTRIBUTING.md) for the dual-licensing / CLA details. + +## Install + +```bash +npm install @codraoss/schema +``` + +## License + +[AGPL-3.0-only](./LICENSE) © Devarshi Shimpi diff --git a/packages/schema/package.json b/packages/schema/package.json index 48364079..423cc147 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -1,12 +1,23 @@ { - "name": "@codra/schema", + "name": "@codraoss/schema", "version": "0.9.4", - "private": true, + "description": "Shared types and Zod contracts for Codra, the open-source code review engine.", + "author": "Devarshi Shimpi", + "license": "AGPL-3.0-only", + "homepage": "https://codra.run", + "repository": { + "type": "git", + "url": "git+https://github.com/devarshishimpi/codra.git", + "directory": "packages/schema" + }, + "bugs": { + "url": "https://github.com/devarshishimpi/codra/issues" + }, "type": "module", + "sideEffects": false, "exports": { ".": "./src/index.ts", "./api": "./src/api.ts", - "./config": "./src/config.ts", "./github": "./src/github.ts", "./hex": "./src/hex.ts", "./review-limits": "./src/review-limits.ts", @@ -14,7 +25,61 @@ "./transient-errors": "./src/transient-errors.ts", "./webhook": "./src/webhook.ts" }, + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./api": { + "types": "./dist/api.d.ts", + "import": "./dist/api.js" + }, + "./github": { + "types": "./dist/github.d.ts", + "import": "./dist/github.js" + }, + "./hex": { + "types": "./dist/hex.d.ts", + "import": "./dist/hex.js" + }, + "./review-limits": { + "types": "./dist/review-limits.d.ts", + "import": "./dist/review-limits.js" + }, + "./timezone": { + "types": "./dist/timezone.d.ts", + "import": "./dist/timezone.js" + }, + "./transient-errors": { + "types": "./dist/transient-errors.d.ts", + "import": "./dist/transient-errors.js" + }, + "./webhook": { + "types": "./dist/webhook.d.ts", + "import": "./dist/webhook.js" + } + } + }, + "scripts": { + "build": "tsup", + "typecheck": "tsc -p tsconfig.json", + "prepack": "node ../../scripts/swap-publish-exports.mjs promote", + "postpack": "node ../../scripts/swap-publish-exports.mjs restore" + }, "dependencies": { "zod": "^4.3.6" + }, + "devDependencies": { + "tsup": "^8.0.0" } } diff --git a/packages/schema/tsup.config.json b/packages/schema/tsup.config.json new file mode 100644 index 00000000..2c1aab49 --- /dev/null +++ b/packages/schema/tsup.config.json @@ -0,0 +1,18 @@ +{ + "entry": [ + "src/index.ts", + "src/api.ts", + "src/github.ts", + "src/hex.ts", + "src/review-limits.ts", + "src/timezone.ts", + "src/transient-errors.ts", + "src/webhook.ts" + ], + "format": "esm", + "dts": true, + "splitting": true, + "sourcemap": true, + "clean": true, + "outDir": "dist" +} diff --git a/packages/ui/LICENSE b/packages/ui/LICENSE new file mode 100644 index 00000000..024299ea --- /dev/null +++ b/packages/ui/LICENSE @@ -0,0 +1,625 @@ +GNU Affero General Public License +================================= + +_Version 3, 19 November 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: **(1)** assert copyright on the software, and **(2)** offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +### 0. Definitions +“This License” refers to version 3 of the GNU Affero General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a “modified version” of the +earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based +on the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” +to the extent that it includes a convenient and prominently visible +feature that **(1)** displays an appropriate copyright notice, and **(2)** +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code +The “source code” for a work means the preferred form of the work +for making modifications to it. “Object code” means any non-source +form of a work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other +than the work as a whole, that **(a)** is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and **(b)** serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +“Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +### 2. Basic Permissions +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +### 4. Conveying Verbatim Copies +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified +it, and giving a relevant date. +* **b)** The work must carry prominent notices stating that it is +released under this License and any conditions added under section 7. +This requirement modifies the requirement in section 4 to +“keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. +* **d)** If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +“aggregate” if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either **(1)** a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or **(2)** access to copy the +Corresponding Source from a network server at no charge. +* **c)** Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. +* **d)** Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A “User Product” is either **(1)** a “consumer product”, which means any +tangible personal property which is normally used for personal, family, +or household purposes, or **(2)** anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, “normally used” refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install + +### 6. Conveying Non-Source Forms +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or +authors of the material; or +* **e)** Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +### 8. Termination +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated **(a)** +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and **(b)** permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To “grant” such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either **(1)** cause the Corresponding Source to be so +available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is “discriminatory” if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license **(a)** in connection with copies of the covered work +conveyed by you (or copies made from those copies), or **(b)** primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found. + + Codra: Open source PR review infrastructure for Cloudflare Workers. + Copyright (C) 2026 Devarshi Shimpi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a “Source” link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a “copyright disclaimer” for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<>. diff --git a/packages/ui/README.md b/packages/ui/README.md new file mode 100644 index 00000000..86fd257e --- /dev/null +++ b/packages/ui/README.md @@ -0,0 +1,15 @@ +# @codraoss/ui + +Codra's reusable React design-system primitives and hooks. + +Part of [Codra](https://codra.run), an open-source code review engine. See the [monorepo](https://github.com/devarshishimpi/codra) for development, and [CONTRIBUTING](https://github.com/devarshishimpi/codra/blob/main/CONTRIBUTING.md) for the dual-licensing / CLA details. + +## Install + +```bash +npm install @codraoss/ui +``` + +## License + +[AGPL-3.0-only](./LICENSE) © Devarshi Shimpi diff --git a/packages/ui/package.json b/packages/ui/package.json index 83765435..eb111254 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,8 +1,20 @@ { - "name": "@codra/ui", + "name": "@codraoss/ui", "version": "0.9.4", - "private": true, + "description": "Codra's reusable React design-system primitives and hooks.", + "author": "Devarshi Shimpi", + "license": "AGPL-3.0-only", + "homepage": "https://codra.run", + "repository": { + "type": "git", + "url": "git+https://github.com/devarshishimpi/codra.git", + "directory": "packages/ui" + }, + "bugs": { + "url": "https://github.com/devarshishimpi/codra/issues" + }, "type": "module", + "sideEffects": false, "exports": { ".": "./src/index.ts", "./theme": "./src/lib/theme.tsx", @@ -14,8 +26,70 @@ "./prompt-diff": "./src/lib/prompt-diff.ts", "./markdown-plugins": "./src/lib/markdown-plugins.ts", "./motion": "./src/components/motion/index.ts", - "./hooks": "./src/hooks/index.ts", - "./styles": "./src/styles/tokens.css" + "./hooks": "./src/hooks/index.ts" + }, + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./theme": { + "types": "./dist/lib/theme.d.ts", + "import": "./dist/lib/theme.js" + }, + "./utils": { + "types": "./dist/lib/utils.d.ts", + "import": "./dist/lib/utils.js" + }, + "./ease": { + "types": "./dist/lib/ease.d.ts", + "import": "./dist/lib/ease.js" + }, + "./highlight": { + "types": "./dist/lib/highlight.d.ts", + "import": "./dist/lib/highlight.js" + }, + "./selection": { + "types": "./dist/lib/selection.d.ts", + "import": "./dist/lib/selection.js" + }, + "./file-tree": { + "types": "./dist/lib/file-tree.d.ts", + "import": "./dist/lib/file-tree.js" + }, + "./prompt-diff": { + "types": "./dist/lib/prompt-diff.d.ts", + "import": "./dist/lib/prompt-diff.js" + }, + "./markdown-plugins": { + "types": "./dist/lib/markdown-plugins.d.ts", + "import": "./dist/lib/markdown-plugins.js" + }, + "./motion": { + "types": "./dist/components/motion/index.d.ts", + "import": "./dist/components/motion/index.js" + }, + "./hooks": { + "types": "./dist/hooks/index.d.ts", + "import": "./dist/hooks/index.js" + } + } + }, + "scripts": { + "build": "tsup", + "typecheck": "tsc -p tsconfig.json", + "prepack": "node ../../scripts/swap-publish-exports.mjs promote", + "postpack": "node ../../scripts/swap-publish-exports.mjs restore" }, "peerDependencies": { "react": "^19.0.0", @@ -27,10 +101,14 @@ "sonner": ">=2.0.0" }, "dependencies": { + "@codraoss/schema": "^0.9.4", "@base-ui/react": "^1.6.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "tailwind-merge": "^3.5.0", "sugar-high": "^2.0.0" + }, + "devDependencies": { + "tsup": "^8.0.0" } -} \ No newline at end of file +} diff --git a/packages/ui/src/lib/file-tree.ts b/packages/ui/src/lib/file-tree.ts index 9f6bd8b8..35ca8cea 100644 --- a/packages/ui/src/lib/file-tree.ts +++ b/packages/ui/src/lib/file-tree.ts @@ -1,4 +1,4 @@ -import type { FileReviewRecord } from '@codra/schema'; +import type { FileReviewRecord } from '@codraoss/schema'; /** Builds the collapsed directory tree the diff viewer's file list renders. */ diff --git a/packages/ui/tsup.config.json b/packages/ui/tsup.config.json new file mode 100644 index 00000000..af385a76 --- /dev/null +++ b/packages/ui/tsup.config.json @@ -0,0 +1,21 @@ +{ + "entry": [ + "src/index.ts", + "src/lib/theme.tsx", + "src/lib/utils.ts", + "src/lib/ease.ts", + "src/lib/highlight.tsx", + "src/lib/selection.ts", + "src/lib/file-tree.ts", + "src/lib/prompt-diff.ts", + "src/lib/markdown-plugins.ts", + "src/components/motion/index.ts", + "src/hooks/index.ts" + ], + "format": "esm", + "dts": true, + "splitting": true, + "sourcemap": true, + "clean": true, + "outDir": "dist" +} diff --git a/scripts/check-package-exports.mjs b/scripts/check-package-exports.mjs new file mode 100644 index 00000000..d8c39edd --- /dev/null +++ b/scripts/check-package-exports.mjs @@ -0,0 +1,69 @@ +import { readFileSync, existsSync, readdirSync } from 'node:fs'; +import { join, dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const packagesDir = join(root, 'packages'); + +const errors = []; + +for (const name of readdirSync(packagesDir)) { + const pkgDir = join(packagesDir, name); + const pkgJsonPath = join(pkgDir, 'package.json'); + if (!existsSync(pkgJsonPath)) continue; + + const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8')); + const id = pkg.name ?? name; + if (pkg.private) continue; + + const exp = pkg.exports; + const pubExp = pkg.publishConfig?.exports; + + if (!exp) { + errors.push(`${id}: missing top-level "exports".`); + continue; + } + if (!pubExp) { + errors.push(`${id}: missing "publishConfig.exports" (the compiled/dist surface).`); + continue; + } + + const srcKeys = Object.keys(exp).sort(); + const distKeys = Object.keys(pubExp).sort(); + if (JSON.stringify(srcKeys) !== JSON.stringify(distKeys)) { + const onlySrc = srcKeys.filter((k) => !distKeys.includes(k)); + const onlyDist = distKeys.filter((k) => !srcKeys.includes(k)); + errors.push( + `${id}: exports keys drift between source and publishConfig.` + + (onlySrc.length ? ` Only in exports: ${onlySrc.join(', ')}.` : '') + + (onlyDist.length ? ` Only in publishConfig.exports: ${onlyDist.join(', ')}.` : ''), + ); + } + + for (const [key, target] of Object.entries(exp)) { + if (typeof target !== 'string') { + errors.push(`${id}: source export "${key}" must be a string path to a .ts/.tsx source file.`); + continue; + } + if (target.includes('*')) continue; + if (!existsSync(join(pkgDir, target))) { + errors.push(`${id}: export "${key}" points at "${target}" which does not exist.`); + } + } + + if (!Array.isArray(pkg.files) || !pkg.files.includes('dist')) { + errors.push(`${id}: "files" must include "dist" so the compiled output is published.`); + } + if (pkg.publishConfig?.access !== 'public') { + errors.push(`${id}: "publishConfig.access" must be "public".`); + } +} + +if (errors.length) { + console.error('Package export check failed:\n'); + for (const e of errors) console.error(' - ' + e); + console.error('\nFix the exports maps in the offending packages/*/package.json.'); + process.exit(1); +} + +console.log('Package export check passed: all publishable @codraoss/* packages have consistent, resolvable exports.'); diff --git a/scripts/swap-publish-exports.mjs b/scripts/swap-publish-exports.mjs new file mode 100644 index 00000000..958363eb --- /dev/null +++ b/scripts/swap-publish-exports.mjs @@ -0,0 +1,27 @@ +import { readFileSync, writeFileSync, existsSync, copyFileSync, rmSync } from 'node:fs'; + +const mode = process.argv[2]; +const PKG = 'package.json'; +const BAK = 'package.json.prepack-bak'; +const PROMOTABLE = ['main', 'module', 'types', 'exports', 'bin', 'browser']; + +if (mode === 'promote') { + copyFileSync(PKG, BAK); + const pkg = JSON.parse(readFileSync(PKG, 'utf8')); + const fields = pkg.publishConfig ?? {}; + for (const field of PROMOTABLE) { + if (fields[field] !== undefined) { + pkg[field] = fields[field]; + } + } + + writeFileSync(PKG, JSON.stringify(pkg, null, 2) + '\n'); +} else if (mode === 'restore') { + if (existsSync(BAK)) { + copyFileSync(BAK, PKG); + rmSync(BAK); + } +} else { + console.error('Usage: swap-publish-exports.mjs '); + process.exit(1); +} diff --git a/src/client/components/features/account/detail-rows.tsx b/src/client/components/features/account/detail-rows.tsx index 5baf372c..b8976a00 100644 --- a/src/client/components/features/account/detail-rows.tsx +++ b/src/client/components/features/account/detail-rows.tsx @@ -1,6 +1,6 @@ -import { LayerCard, Skeleton, Text } from '@codra/ui'; +import { LayerCard, Skeleton, Text } from '@codraoss/ui'; import { useState } from 'react'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; export function DetailGroup({ caption, children }: { caption: string; children: React.ReactNode }) { return ( diff --git a/src/client/components/features/account/details-section.tsx b/src/client/components/features/account/details-section.tsx index df3edce1..2acde290 100644 --- a/src/client/components/features/account/details-section.tsx +++ b/src/client/components/features/account/details-section.tsx @@ -1,4 +1,4 @@ -import { SectionCard, Select, Skeleton, Text } from '@codra/ui'; +import { SectionCard, Select, Skeleton, Text } from '@codraoss/ui'; import { useMemo } from 'react'; import { Mail } from 'lucide-react'; import { @@ -9,7 +9,7 @@ import { resolvedTimeZone, timeZoneOffsetLabel, } from '@client/lib/timezone'; -import type { AccountSettings, AuthSessionUser } from '@codra/schema/api'; +import type { AccountSettings, AuthSessionUser } from '@codraoss/schema/api'; import { DetailGroup, RevealOnClick, DetailRow } from './detail-rows'; diff --git a/src/client/components/features/account/profile-card.tsx b/src/client/components/features/account/profile-card.tsx index 606808e0..80692241 100644 --- a/src/client/components/features/account/profile-card.tsx +++ b/src/client/components/features/account/profile-card.tsx @@ -1,9 +1,9 @@ -import { Badge, Button, GithubMark, Input, LinkButton, Skeleton } from '@codra/ui'; +import { Badge, Button, GithubMark, Input, LinkButton, Skeleton } from '@codraoss/ui'; import { useState } from 'react'; import { toast } from 'sonner'; import { api } from '@client/lib/api'; import { ExternalLink, Pencil, Check, X } from 'lucide-react'; -import type { AccountSettings, AuthSessionUser } from '@codra/schema/api'; +import type { AccountSettings, AuthSessionUser } from '@codraoss/schema/api'; export function ProfileCard({ user, diff --git a/src/client/components/features/dashboard/updates-email-prompt.tsx b/src/client/components/features/dashboard/updates-email-prompt.tsx index 4fd03128..73195dce 100644 --- a/src/client/components/features/dashboard/updates-email-prompt.tsx +++ b/src/client/components/features/dashboard/updates-email-prompt.tsx @@ -1,9 +1,9 @@ -import { Button, Input } from '@codra/ui'; +import { Button, Input } from '@codraoss/ui'; import { useEffect, useState, type FormEvent } from 'react'; import { toast } from 'sonner'; import { Check, Mail } from 'lucide-react'; import { api } from '@client/lib/api'; -import type { UpdatesEmailResponse } from '@codra/schema/api'; +import type { UpdatesEmailResponse } from '@codraoss/schema/api'; export function UpdatesEmailPrompt() { const [status, setStatus] = useState(null); diff --git a/src/client/components/features/job-detail/comment-card.tsx b/src/client/components/features/job-detail/comment-card.tsx index c168e076..ddeacc65 100644 --- a/src/client/components/features/job-detail/comment-card.tsx +++ b/src/client/components/features/job-detail/comment-card.tsx @@ -1,16 +1,16 @@ -import { CopyButton } from '@codra/ui'; +import { CopyButton } from '@codraoss/ui'; import { useState, type ComponentPropsWithoutRef } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { FileText, ThumbsDown, ThumbsUp } from 'lucide-react'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; import { api } from '@client/lib/api'; -import { preventToggleOnTextSelection } from '@codra/ui/selection'; -import type { ParsedReviewComment } from '@codra/schema'; +import { preventToggleOnTextSelection } from '@codraoss/ui/selection'; +import type { ParsedReviewComment } from '@codraoss/schema'; import { severityConfig } from './constants'; import { ContextSnippet } from './context-snippet'; -import { safeRehypePlugins } from '@codra/ui/markdown-plugins'; +import { safeRehypePlugins } from '@codraoss/ui/markdown-plugins'; /** Plain-English reason a finding never reached the pull request. */ const DISPOSITION_LABEL: Record = { severity: 'Below the severity threshold for this repository', diff --git a/src/client/components/features/job-detail/context-snippet.tsx b/src/client/components/features/job-detail/context-snippet.tsx index eacc0445..d60b9b33 100644 --- a/src/client/components/features/job-detail/context-snippet.tsx +++ b/src/client/components/features/job-detail/context-snippet.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { highlightLine, langForPath } from '@codra/ui/highlight'; -import { cn } from '@codra/ui/utils'; +import { highlightLine, langForPath } from '@codraoss/ui/highlight'; +import { cn } from '@codraoss/ui/utils'; import { ROW_TONES } from './diff-file-panel-utils'; /** diff --git a/src/client/components/features/job-detail/diff-file-panel-utils.ts b/src/client/components/features/job-detail/diff-file-panel-utils.ts index 4482777e..e4ecc840 100644 --- a/src/client/components/features/job-detail/diff-file-panel-utils.ts +++ b/src/client/components/features/job-detail/diff-file-panel-utils.ts @@ -1,6 +1,6 @@ import type { CSSProperties } from 'react'; -import type { DiffRow } from '@codra/ui/prompt-diff'; -import type { ParsedReviewComment } from '@codra/schema'; +import type { DiffRow } from '@codraoss/ui/prompt-diff'; +import type { ParsedReviewComment } from '@codraoss/schema'; export const LARGE_DIFF_ROWS = 300; diff --git a/src/client/components/features/job-detail/diff-file-panel.tsx b/src/client/components/features/job-detail/diff-file-panel.tsx index d19b7c94..361b4f96 100644 --- a/src/client/components/features/job-detail/diff-file-panel.tsx +++ b/src/client/components/features/job-detail/diff-file-panel.tsx @@ -1,11 +1,11 @@ -import { Badge } from '@codra/ui'; +import { Badge } from '@codraoss/ui'; import { useMemo, useState } from 'react'; import { Check, ChevronDown } from 'lucide-react'; import { StatusBadge } from './status-badge'; -import { parsePromptDiff, diffStats, type DiffRow } from '@codra/ui/prompt-diff'; -import { highlightLine, langForPath } from '@codra/ui/highlight'; -import { cn } from '@codra/ui/utils'; -import type { FileReviewRecord, ParsedReviewComment } from '@codra/schema'; +import { parsePromptDiff, diffStats, type DiffRow } from '@codraoss/ui/prompt-diff'; +import { highlightLine, langForPath } from '@codraoss/ui/highlight'; +import { cn } from '@codraoss/ui/utils'; +import type { FileReviewRecord, ParsedReviewComment } from '@codraoss/schema'; import { CommentCard } from './comment-card'; import { LARGE_DIFF_ROWS, diff --git a/src/client/components/features/job-detail/diff-file-tree.tsx b/src/client/components/features/job-detail/diff-file-tree.tsx index b37c4777..c3b4d80e 100644 --- a/src/client/components/features/job-detail/diff-file-tree.tsx +++ b/src/client/components/features/job-detail/diff-file-tree.tsx @@ -1,8 +1,8 @@ import { Check, FileText, Folder, FolderOpen } from 'lucide-react'; -import { type TreeNode } from '@codra/ui/file-tree'; -import { diffStats } from '@codra/ui/prompt-diff'; -import { cn } from '@codra/ui/utils'; -import type { FileReviewRecord } from '@codra/schema'; +import { type TreeNode } from '@codraoss/ui/file-tree'; +import { diffStats } from '@codraoss/ui/prompt-diff'; +import { cn } from '@codraoss/ui/utils'; +import type { FileReviewRecord } from '@codraoss/schema'; export interface TreeProps { nodes: TreeNode[]; diff --git a/src/client/components/features/job-detail/file-finding.tsx b/src/client/components/features/job-detail/file-finding.tsx index ad45b0f6..f05ffd19 100644 --- a/src/client/components/features/job-detail/file-finding.tsx +++ b/src/client/components/features/job-detail/file-finding.tsx @@ -1,13 +1,13 @@ import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { ChevronRight } from 'lucide-react'; -import type { FileReviewRecord, ParsedReviewComment } from '@codra/schema'; +import type { FileReviewRecord, ParsedReviewComment } from '@codraoss/schema'; import { CommentCard } from './comment-card'; -import { preventToggleOnTextSelection } from '@codra/ui/selection'; +import { preventToggleOnTextSelection } from '@codraoss/ui/selection'; import { MonoPath, StatusDot, VerdictPill } from './job-chips'; import { statusLabel } from './job-chip-utils'; -import { safeRehypePlugins } from '@codra/ui/markdown-plugins'; +import { safeRehypePlugins } from '@codraoss/ui/markdown-plugins'; interface FileFindingProps { file: FileReviewRecord; } diff --git a/src/client/components/features/job-detail/job-chips.tsx b/src/client/components/features/job-detail/job-chips.tsx index 0ab958e7..f50f9155 100644 --- a/src/client/components/features/job-detail/job-chips.tsx +++ b/src/client/components/features/job-detail/job-chips.tsx @@ -4,10 +4,10 @@ */ import { useState, type ReactNode } from 'react'; import { CheckCircle2, MessageSquare, type LucideIcon } from 'lucide-react'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; import { STATUS_DOT, jobDuration, statusLabel } from '@client/lib/job-format'; -import type { JobDetail, JobSummary } from '@codra/schema'; +import type { JobDetail, JobSummary } from '@codraoss/schema'; /** Status dot alone, for rows that render their own label. */ diff --git a/src/client/components/features/job-detail/job-diffs.tsx b/src/client/components/features/job-detail/job-diffs.tsx index 7f65324f..0543780c 100644 --- a/src/client/components/features/job-detail/job-diffs.tsx +++ b/src/client/components/features/job-detail/job-diffs.tsx @@ -9,10 +9,10 @@ import { Info, } from 'lucide-react'; import { api } from '@client/lib/api'; -import { buildTree } from '@codra/ui/file-tree'; -import { diffStats } from '@codra/ui/prompt-diff'; +import { buildTree } from '@codraoss/ui/file-tree'; +import { diffStats } from '@codraoss/ui/prompt-diff'; import { readDiffsCache, writeDiffsCache } from '@client/lib/diffs-cache'; -import type { FileReviewRecord, JobDetail } from '@codra/schema'; +import type { FileReviewRecord, JobDetail } from '@codraoss/schema'; import { FileDiff } from './diff-file-panel'; import { panelCvStyle, fileAnchorId } from './diff-file-panel-utils'; diff --git a/src/client/components/features/job-detail/job-findings-list.tsx b/src/client/components/features/job-detail/job-findings-list.tsx index 38256d3e..0191abb0 100644 --- a/src/client/components/features/job-detail/job-findings-list.tsx +++ b/src/client/components/features/job-detail/job-findings-list.tsx @@ -1,8 +1,8 @@ import { useState, type ReactNode } from 'react'; import { FileText } from 'lucide-react'; -import type { JobDetail } from '@codra/schema'; -import { reviewSeverities } from '@codra/schema/review-limits'; -import { Tabs, TabsList, TabsTrigger } from '@codra/ui/motion'; +import type { JobDetail } from '@codraoss/schema'; +import { reviewSeverities } from '@codraoss/schema/review-limits'; +import { Tabs, TabsList, TabsTrigger } from '@codraoss/ui/motion'; import { FileFinding } from './file-finding'; import { CommentCard } from './comment-card'; import { severityConfig } from './constants'; diff --git a/src/client/components/features/job-detail/job-header.tsx b/src/client/components/features/job-detail/job-header.tsx index 2cbafbb4..081fddf3 100644 --- a/src/client/components/features/job-detail/job-header.tsx +++ b/src/client/components/features/job-detail/job-header.tsx @@ -1,4 +1,4 @@ -import { Button, ConfirmDialog } from '@codra/ui'; +import { Button, ConfirmDialog } from '@codraoss/ui'; import { useState } from 'react'; import type { ComponentType } from 'react'; import { Link } from 'react-router-dom'; @@ -14,11 +14,11 @@ import { Terminal, Trash2, } from 'lucide-react'; -import type { ButtonProps } from '@codra/ui'; +import type { ButtonProps } from '@codraoss/ui'; import { UpdatesEmailPrompt } from '@client/components/features/dashboard/updates-email-prompt'; import { AuthorChip, JobStatusLine, MetaChip, VerdictPill } from './job-chips'; import { formatAbsoluteDate, formatRelativeDate } from './job-chip-utils'; -import type { JobDetail } from '@codra/schema'; +import type { JobDetail } from '@codraoss/schema'; // Lucide's CircleStop strokes the inner square too, which reads as a blob at 14px; filling it // instead keeps the stop symbol legible. diff --git a/src/client/components/features/job-detail/job-meta-cards.tsx b/src/client/components/features/job-detail/job-meta-cards.tsx index e6e4d6e9..1ca022e9 100644 --- a/src/client/components/features/job-detail/job-meta-cards.tsx +++ b/src/client/components/features/job-detail/job-meta-cards.tsx @@ -1,8 +1,8 @@ import type { ReactNode } from 'react'; import { AtSign, ExternalLink, Info, ListChecks, RotateCcw, Zap } from 'lucide-react'; import { Link } from 'react-router-dom'; -import { cn, formatPreciseDuration } from '@codra/ui/utils'; -import type { JobDetail, JobStep } from '@codra/schema'; +import { cn, formatPreciseDuration } from '@codraoss/ui/utils'; +import type { JobDetail, JobStep } from '@codraoss/schema'; import { EmptyValue, JobStatusLine, diff --git a/src/client/components/features/job-detail/job-progress.tsx b/src/client/components/features/job-detail/job-progress.tsx index dca35f20..97e42fb5 100644 --- a/src/client/components/features/job-detail/job-progress.tsx +++ b/src/client/components/features/job-detail/job-progress.tsx @@ -1,5 +1,5 @@ import { FileCode2, Hourglass } from 'lucide-react'; -import type { JobDetail } from '@codra/schema'; +import type { JobDetail } from '@codraoss/schema'; interface JobProgressProps { job: JobDetail; diff --git a/src/client/components/features/job-detail/job-review-overview.tsx b/src/client/components/features/job-detail/job-review-overview.tsx index 435f8ce9..d3e875e1 100644 --- a/src/client/components/features/job-detail/job-review-overview.tsx +++ b/src/client/components/features/job-detail/job-review-overview.tsx @@ -1,11 +1,11 @@ import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { CheckCircle2, ClipboardList, TriangleAlert } from 'lucide-react'; -import type { JobDetail } from '@codra/schema'; -import { reviewSeverities } from '@codra/schema/review-limits'; +import type { JobDetail } from '@codraoss/schema'; +import { reviewSeverities } from '@codraoss/schema/review-limits'; import { OutlinePill } from './job-chips'; -import { safeRehypePlugins } from '@codra/ui/markdown-plugins'; +import { safeRehypePlugins } from '@codraoss/ui/markdown-plugins'; interface JobReviewOverviewProps { job: JobDetail; } diff --git a/src/client/components/features/job-detail/job-skeleton.tsx b/src/client/components/features/job-detail/job-skeleton.tsx index d54412b6..03538968 100644 --- a/src/client/components/features/job-detail/job-skeleton.tsx +++ b/src/client/components/features/job-detail/job-skeleton.tsx @@ -1,4 +1,4 @@ -import { LoadError, Skeleton } from '@codra/ui'; +import { LoadError, Skeleton } from '@codraoss/ui'; import { Link } from 'react-router-dom'; import { ChevronRight, ClipboardList, FileDiff, Info, ListChecks } from 'lucide-react'; import { DETAIL_LABEL, DETAIL_ROW } from './job-chip-utils'; diff --git a/src/client/components/features/job-detail/status-badge.tsx b/src/client/components/features/job-detail/status-badge.tsx index f53f4c7b..d63f7eaa 100644 --- a/src/client/components/features/job-detail/status-badge.tsx +++ b/src/client/components/features/job-detail/status-badge.tsx @@ -1,5 +1,5 @@ -import { Badge } from '@codra/ui'; -import type { JobSummary } from '@codra/schema'; +import { Badge } from '@codraoss/ui'; +import type { JobSummary } from '@codraoss/schema'; import { LiveReviewStepper } from '@client/components/features/reviews/live-review-stepper'; type BadgeVariant = 'success' | 'info' | 'warning' | 'danger' | 'neutral'; diff --git a/src/client/components/features/models/model-chain.tsx b/src/client/components/features/models/model-chain.tsx index d5b7156c..8e6ffbb5 100644 --- a/src/client/components/features/models/model-chain.tsx +++ b/src/client/components/features/models/model-chain.tsx @@ -1,6 +1,6 @@ -import { Button, Select } from '@codra/ui'; +import { Button, Select } from '@codraoss/ui'; import { useId, useMemo, useState } from 'react'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; import { Trash2, ListPlus } from 'lucide-react'; import type { diff --git a/src/client/components/features/repos/repo-model-modal.tsx b/src/client/components/features/repos/repo-model-modal.tsx index 4d3015af..9961e1ea 100644 --- a/src/client/components/features/repos/repo-model-modal.tsx +++ b/src/client/components/features/repos/repo-model-modal.tsx @@ -1,10 +1,10 @@ -import { Alert, Button } from '@codra/ui'; +import { Alert, Button } from '@codraoss/ui'; import { useMemo, useState } from 'react'; import { Dialog } from '@base-ui/react/dialog'; import { toast } from 'sonner'; import { api } from '@client/lib/api'; import { Save, RotateCcw, X } from 'lucide-react'; -import type { RepoConfigRecord } from '@codra/schema'; +import type { RepoConfigRecord } from '@codraoss/schema'; import { ModelRouteEditor } from '@client/components/features/models/model-chain'; import { EMPTY_MODEL_ROUTE, diff --git a/src/client/components/features/repos/repo-route.ts b/src/client/components/features/repos/repo-route.ts index c3e5ccff..214e7997 100644 --- a/src/client/components/features/repos/repo-route.ts +++ b/src/client/components/features/repos/repo-route.ts @@ -1,5 +1,5 @@ import { formatDateTime } from '@client/lib/timezone'; -import type { RepoConfig, RepoConfigRecord } from '@codra/schema'; +import type { RepoConfig, RepoConfigRecord } from '@codraoss/schema'; import { EMPTY_MODEL_ROUTE, normalizeModelRoute, routesEqual, type ModelRouteConfig } from '@client/components/features/models/model-route'; // Shared by the repos page, its rows and the strategy dialog, so it can't live in any single one. diff --git a/src/client/components/features/repos/repo-row.tsx b/src/client/components/features/repos/repo-row.tsx index 4c7f50af..6584afe5 100644 --- a/src/client/components/features/repos/repo-row.tsx +++ b/src/client/components/features/repos/repo-row.tsx @@ -1,6 +1,6 @@ -import { Badge, Button, Switch } from '@codra/ui'; +import { Badge, Button, Switch } from '@codraoss/ui'; import { Settings2 } from 'lucide-react'; -import type { RepoConfigRecord } from '@codra/schema'; +import type { RepoConfigRecord } from '@codraoss/schema'; import { describeModelRoute, type ModelOption, type ModelRouteConfig } from '@client/components/features/models/model-route'; import { getRepoRoute, hasMeaningfulCustomStrategy, formatLastActivity, type GlobalModelConfig } from './repo-route'; diff --git a/src/client/components/features/reviews/live-review-stepper.tsx b/src/client/components/features/reviews/live-review-stepper.tsx index 6c1f05b3..37409c8c 100644 --- a/src/client/components/features/reviews/live-review-stepper.tsx +++ b/src/client/components/features/reviews/live-review-stepper.tsx @@ -1,4 +1,4 @@ -import type { JobSummary } from '@codra/schema'; +import type { JobSummary } from '@codraoss/schema'; interface LiveReviewStepperProps { job: JobSummary; diff --git a/src/client/components/features/settings/about-section.tsx b/src/client/components/features/settings/about-section.tsx index d875566e..9e9e32d0 100644 --- a/src/client/components/features/settings/about-section.tsx +++ b/src/client/components/features/settings/about-section.tsx @@ -1,4 +1,4 @@ -import { Badge, LayerCard, SectionCard, Text } from '@codra/ui'; +import { Badge, LayerCard, SectionCard, Text } from '@codraoss/ui'; import pkg from '../../../../../package.json'; import { ExternalLink } from 'lucide-react'; diff --git a/src/client/components/features/settings/default-models-section.tsx b/src/client/components/features/settings/default-models-section.tsx index edbcc2f4..952a3258 100644 --- a/src/client/components/features/settings/default-models-section.tsx +++ b/src/client/components/features/settings/default-models-section.tsx @@ -1,6 +1,6 @@ -import { Skeleton } from '@codra/ui'; +import { Skeleton } from '@codraoss/ui'; import { useMemo } from 'react'; -import type { ModelConfig } from '@codra/schema'; +import type { ModelConfig } from '@codraoss/schema'; import { ModelRouteEditor } from '@client/components/features/models/model-chain'; import type { ModelOption, diff --git a/src/client/components/features/settings/new-provider-form.tsx b/src/client/components/features/settings/new-provider-form.tsx index 5cdc6ad2..2c212a56 100644 --- a/src/client/components/features/settings/new-provider-form.tsx +++ b/src/client/components/features/settings/new-provider-form.tsx @@ -1,4 +1,4 @@ -import { Button, Input, Select } from '@codra/ui'; +import { Button, Input, Select } from '@codraoss/ui'; import type { Dispatch, SetStateAction } from 'react'; import { Plus } from 'lucide-react'; import { FieldLabel } from './field-label'; diff --git a/src/client/components/features/settings/provider-list.tsx b/src/client/components/features/settings/provider-list.tsx index fe04bf94..c797d33f 100644 --- a/src/client/components/features/settings/provider-list.tsx +++ b/src/client/components/features/settings/provider-list.tsx @@ -1,6 +1,6 @@ -import { Skeleton } from '@codra/ui'; +import { Skeleton } from '@codraoss/ui'; import { toast } from 'sonner'; -import type { LlmProvider } from '@codra/schema'; +import type { LlmProvider } from '@codraoss/schema'; import { ProviderRow } from './provider-row'; import type { ProviderDraft } from './settings-support'; diff --git a/src/client/components/features/settings/provider-row.tsx b/src/client/components/features/settings/provider-row.tsx index 29ef4810..191127f0 100644 --- a/src/client/components/features/settings/provider-row.tsx +++ b/src/client/components/features/settings/provider-row.tsx @@ -1,7 +1,7 @@ -import { Badge, Button, Input, Select, Switch } from '@codra/ui'; +import { Badge, Button, Input, Select, Switch } from '@codraoss/ui'; import { ChevronRight, Save, Trash2 } from 'lucide-react'; -import { cn } from '@codra/ui/utils'; -import type { LlmApiFormat, LlmProvider } from '@codra/schema'; +import { cn } from '@codraoss/ui/utils'; +import type { LlmApiFormat, LlmProvider } from '@codraoss/schema'; import { FieldLabel } from './field-label'; import { API_FORMAT_OPTIONS, diff --git a/src/client/components/features/settings/review-section.tsx b/src/client/components/features/settings/review-section.tsx index 5cc20dab..1a5418d8 100644 --- a/src/client/components/features/settings/review-section.tsx +++ b/src/client/components/features/settings/review-section.tsx @@ -1,7 +1,7 @@ -import { ConfirmDialog, Input, SectionCard, Skeleton } from '@codra/ui'; -import { SteppedSlider } from '@codra/ui/motion'; -import type { ReviewSettings } from '@codra/schema'; -import { REVIEW_CONCURRENCY_LIMITS, reviewMaxFilesRange } from '@codra/schema/review-limits'; +import { ConfirmDialog, Input, SectionCard, Skeleton } from '@codraoss/ui'; +import { SteppedSlider } from '@codraoss/ui/motion'; +import type { ReviewSettings } from '@codraoss/schema'; +import { REVIEW_CONCURRENCY_LIMITS, reviewMaxFilesRange } from '@codraoss/schema/review-limits'; import { FieldLabel } from './field-label'; import { CONCURRENCY_LEVEL_LABEL, diff --git a/src/client/components/features/settings/settings-support.ts b/src/client/components/features/settings/settings-support.ts index 27c3f9c1..6be96600 100644 --- a/src/client/components/features/settings/settings-support.ts +++ b/src/client/components/features/settings/settings-support.ts @@ -1,5 +1,5 @@ -import type { LlmApiFormat, LlmProvider } from '@codra/schema'; -import { REVIEW_CONCURRENCY_LIMITS, reviewMaxCommentsOptions, type ReviewConcurrencyLevel } from '@codra/schema/review-limits'; +import type { LlmApiFormat, LlmProvider } from '@codraoss/schema'; +import { REVIEW_CONCURRENCY_LIMITS, reviewMaxCommentsOptions, type ReviewConcurrencyLevel } from '@codraoss/schema/review-limits'; // Pure and render-free, so the settings page and its sections can all depend on it without depending on each other. diff --git a/src/client/components/features/stats/chart-primitives.tsx b/src/client/components/features/stats/chart-primitives.tsx index fd8e1be0..718d50c8 100644 --- a/src/client/components/features/stats/chart-primitives.tsx +++ b/src/client/components/features/stats/chart-primitives.tsx @@ -1,4 +1,4 @@ -import { Skeleton, GraphShell } from '@codra/ui'; +import { Skeleton, GraphShell } from '@codraoss/ui'; import type { ReactNode } from 'react'; import { Activity, Boxes, Coins, FolderGit2, ShieldCheck } from 'lucide-react'; import { formatCompact, formatDayRange } from './chart-support'; diff --git a/src/client/components/features/stats/chart-support.ts b/src/client/components/features/stats/chart-support.ts index 764571ec..6235c777 100644 --- a/src/client/components/features/stats/chart-support.ts +++ b/src/client/components/features/stats/chart-support.ts @@ -1,4 +1,4 @@ -import { fmtNumber } from '@codra/ui/utils'; +import { fmtNumber } from '@codraoss/ui/utils'; import { formatDayLabel } from '@client/lib/timezone'; // Pure and render-free, so the chart components and the grid can share it without Fast Refresh diff --git a/src/client/components/features/stats/metrics-grid-charts.tsx b/src/client/components/features/stats/metrics-grid-charts.tsx index c9ec35ec..1fbea113 100644 --- a/src/client/components/features/stats/metrics-grid-charts.tsx +++ b/src/client/components/features/stats/metrics-grid-charts.tsx @@ -13,7 +13,7 @@ import { YAxis, } from 'recharts'; import { Activity, Boxes, Coins, FolderGit2, ShieldCheck } from 'lucide-react'; -import type { StatsPayload } from '@codra/schema'; +import type { StatsPayload } from '@codraoss/schema'; import { ChartTooltip } from './chart-primitives'; @@ -23,7 +23,7 @@ import { ChartDefs, MeterList, TickMeter -} from '@codra/ui'; +} from '@codraoss/ui'; import { CHART, MONO_STACK, diff --git a/src/client/components/features/stats/metrics-grid.tsx b/src/client/components/features/stats/metrics-grid.tsx index 40ec05d0..36b8548b 100644 --- a/src/client/components/features/stats/metrics-grid.tsx +++ b/src/client/components/features/stats/metrics-grid.tsx @@ -1,5 +1,5 @@ import React, { Suspense } from 'react'; -import type { StatsPayload } from '@codra/schema'; +import type { StatsPayload } from '@codraoss/schema'; import { MetricsGridSkeleton } from './chart-primitives'; // Recharts is only needed once stats have loaded, so it stays out of the initial bundle and the diff --git a/src/client/components/features/stats/overview-stats.tsx b/src/client/components/features/stats/overview-stats.tsx index 0dcb3619..39b3203e 100644 --- a/src/client/components/features/stats/overview-stats.tsx +++ b/src/client/components/features/stats/overview-stats.tsx @@ -1,9 +1,9 @@ import { useMemo } from 'react'; import { Activity, ArrowUpRight, Cpu, MessageSquare } from 'lucide-react'; import { StatsGrid, type StatDelta } from './stats-grid'; -import { fmtStat } from '@codra/ui/utils'; -import { useIsDarkMode } from '@codra/ui/hooks'; -import type { StatsPayload } from '@codra/schema'; +import { fmtStat } from '@codraoss/ui/utils'; +import { useIsDarkMode } from '@codraoss/ui/hooks'; +import type { StatsPayload } from '@codraoss/schema'; interface OverviewStatsProps { stats: StatsPayload | null; diff --git a/src/client/components/features/stats/stats-grid.tsx b/src/client/components/features/stats/stats-grid.tsx index c210add7..8b8db681 100644 --- a/src/client/components/features/stats/stats-grid.tsx +++ b/src/client/components/features/stats/stats-grid.tsx @@ -1,6 +1,6 @@ -import { BarSparkline, Skeleton } from '@codra/ui'; +import { BarSparkline, Skeleton } from '@codraoss/ui'; import * as React from 'react'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; import type { LucideIcon } from 'lucide-react'; export interface StatDelta { diff --git a/src/client/components/features/stats/time-range-select.tsx b/src/client/components/features/stats/time-range-select.tsx index 9ea6a94e..f6dd9d09 100644 --- a/src/client/components/features/stats/time-range-select.tsx +++ b/src/client/components/features/stats/time-range-select.tsx @@ -1,8 +1,8 @@ -import { Select } from '@codra/ui'; +import { Select } from '@codraoss/ui'; import type { CSSProperties } from 'react'; import { Clock } from 'lucide-react'; import { DEFAULT_STATS_DAYS } from '@client/hooks/use-stats-range'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; interface TimeRangeSelectProps { value: number; diff --git a/src/client/components/layout/account-menu.tsx b/src/client/components/layout/account-menu.tsx index 612d6b04..4c939422 100644 --- a/src/client/components/layout/account-menu.tsx +++ b/src/client/components/layout/account-menu.tsx @@ -1,10 +1,10 @@ -import { GithubMark } from '@codra/ui'; +import { GithubMark } from '@codraoss/ui'; import { Link } from 'react-router-dom'; import { useEffect, useRef, useState } from 'react'; import { api } from '@client/lib/api'; import { LogOut, ChevronsUpDown, UserRound } from 'lucide-react'; -import { cn } from '@codra/ui/utils'; -import type { AuthSessionUser } from '@codra/schema/api'; +import { cn } from '@codraoss/ui/utils'; +import type { AuthSessionUser } from '@codraoss/schema/api'; /** * Built from scratch (no shared dropdown primitive): a local popover anchored diff --git a/src/client/components/layout/app-shell.tsx b/src/client/components/layout/app-shell.tsx index 325adb2e..9b9c6568 100644 --- a/src/client/components/layout/app-shell.tsx +++ b/src/client/components/layout/app-shell.tsx @@ -1,13 +1,13 @@ import { Outlet, Link } from 'react-router-dom'; import { useEffect, useState } from 'react'; -import { SharedLayoutBg } from '@codra/ui/motion'; +import { SharedLayoutBg } from '@codraoss/ui/motion'; import { api } from '@client/lib/api'; import { LayoutDashboard, AlignLeft, GitBranch, BarChart2, Sun, Moon, Activity, Settings, Star, X, ArrowUpRight } from 'lucide-react'; -import { cn } from '@codra/ui/utils'; -import { useTheme } from '@codra/ui/theme'; +import { cn } from '@codraoss/ui/utils'; +import { useTheme } from '@codraoss/ui/theme'; import codraDark from '@/assets/codra-fullicon-dark.svg'; import codraLight from '@/assets/codra-fullicon-light.svg'; -import type { AuthSessionUser } from '@codra/schema/api'; +import type { AuthSessionUser } from '@codraoss/schema/api'; import { SidebarNavItem } from '@client/components/layout/sidebar-nav-item'; import { AccountMenu } from '@client/components/layout/account-menu'; diff --git a/src/client/components/layout/page-header.tsx b/src/client/components/layout/page-header.tsx index 177dcaaa..2a3690b3 100644 --- a/src/client/components/layout/page-header.tsx +++ b/src/client/components/layout/page-header.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; import { UpdatesEmailPrompt } from '@client/components/features/dashboard/updates-email-prompt'; interface PageHeaderProps extends React.HTMLAttributes { diff --git a/src/client/components/layout/sidebar-nav-item.tsx b/src/client/components/layout/sidebar-nav-item.tsx index d271f0fb..3b42d97f 100644 --- a/src/client/components/layout/sidebar-nav-item.tsx +++ b/src/client/components/layout/sidebar-nav-item.tsx @@ -1,6 +1,6 @@ import { NavLink, useMatch, useResolvedPath } from 'react-router-dom'; import { type ComponentType } from 'react'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; /** * Resolves active state via hooks instead of NavLink's render-prop pattern, diff --git a/src/client/components/shared/jobs-table.tsx b/src/client/components/shared/jobs-table.tsx index 3175ebc3..d1a92e5e 100644 --- a/src/client/components/shared/jobs-table.tsx +++ b/src/client/components/shared/jobs-table.tsx @@ -1,13 +1,13 @@ -import { Skeleton } from '@codra/ui'; +import { Skeleton } from '@codraoss/ui'; import { Link } from 'react-router-dom'; import { FolderGit2, GitCommitHorizontal, GitPullRequest } from 'lucide-react'; import { VerdictPill, MetaChip, AuthorAvatar } from '@client/components/features/job-detail/job-chips'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; import { formatDateTime } from '@client/lib/timezone'; import { STATUS_DOT, formatRelativeDate, jobDuration, statusLabel } from '@client/lib/job-format'; -import type { JobSummary } from '@codra/schema'; +import type { JobSummary } from '@codraoss/schema'; type Column = | 'title' diff --git a/src/client/components/shared/page-header-actions.tsx b/src/client/components/shared/page-header-actions.tsx index 5a7e1fc2..0c572d44 100644 --- a/src/client/components/shared/page-header-actions.tsx +++ b/src/client/components/shared/page-header-actions.tsx @@ -1,4 +1,4 @@ -import { Button } from '@codra/ui'; +import { Button } from '@codraoss/ui'; import { RefreshCw } from 'lucide-react'; import { TimeRangeSelect } from '@client/components/features/stats/time-range-select'; diff --git a/src/client/components/shared/route-error-boundary.tsx b/src/client/components/shared/route-error-boundary.tsx index 95fca937..bc814955 100644 --- a/src/client/components/shared/route-error-boundary.tsx +++ b/src/client/components/shared/route-error-boundary.tsx @@ -1,4 +1,4 @@ -import { Button } from '@codra/ui'; +import { Button } from '@codraoss/ui'; import { isRouteErrorResponse, Link, useRouteError } from 'react-router-dom'; import { AlertTriangle, Compass, LayoutDashboard, RefreshCw } from 'lucide-react'; diff --git a/src/client/hooks/use-job-detail.ts b/src/client/hooks/use-job-detail.ts index f7303da1..cd3fcb8a 100644 --- a/src/client/hooks/use-job-detail.ts +++ b/src/client/hooks/use-job-detail.ts @@ -2,7 +2,7 @@ import { useEffect, useState, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'sonner'; import { api } from '@client/lib/api'; -import type { JobDetail } from '@codra/schema'; +import type { JobDetail } from '@codraoss/schema'; /* Job detail carries every file's full diff, so cache writes are best-effort (quota is swallowed). */ function jobCacheKey(id: string) { diff --git a/src/client/hooks/use-provider-settings.ts b/src/client/hooks/use-provider-settings.ts index 892ad228..8cf2bf20 100644 --- a/src/client/hooks/use-provider-settings.ts +++ b/src/client/hooks/use-provider-settings.ts @@ -1,8 +1,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { toast } from 'sonner'; import { api, type ProviderPayload } from '@client/lib/api'; -import type { LlmProvider, ModelConfig, ReviewSettings } from '@codra/schema'; -import type { ModelConfigsResponse } from '@codra/schema/api'; +import type { LlmProvider, ModelConfig, ReviewSettings } from '@codraoss/schema'; +import type { ModelConfigsResponse } from '@codraoss/schema/api'; import { normalizeModelRoute, routesEqual, diff --git a/src/client/hooks/use-review-settings.ts b/src/client/hooks/use-review-settings.ts index 7494c91c..74db8913 100644 --- a/src/client/hooks/use-review-settings.ts +++ b/src/client/hooks/use-review-settings.ts @@ -1,8 +1,8 @@ import { useState } from 'react'; import { toast } from 'sonner'; import { api } from '@client/lib/api'; -import type { ReviewSettings } from '@codra/schema'; -import { reviewMaxFilesRange } from '@codra/schema/review-limits'; +import type { ReviewSettings } from '@codraoss/schema'; +import { reviewMaxFilesRange } from '@codraoss/schema/review-limits'; import { CONCURRENCY_LEVEL_LABEL, CONCURRENCY_MAX_VALUE, diff --git a/src/client/lib/api.ts b/src/client/lib/api.ts index 12cbe802..0005a340 100644 --- a/src/client/lib/api.ts +++ b/src/client/lib/api.ts @@ -11,8 +11,8 @@ import type { StatsResponse, SyncReposResponse, UpdatesEmailResponse, -} from '@codra/schema/api'; -import type { LlmApiFormat, LlmProvider, RepoConfig, ReviewSettings } from '@codra/schema'; +} from '@codraoss/schema/api'; +import type { LlmApiFormat, LlmProvider, RepoConfig, ReviewSettings } from '@codraoss/schema'; import { resolvedTimeZone } from '@client/lib/timezone'; const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']); diff --git a/src/client/lib/batch-groups.ts b/src/client/lib/batch-groups.ts index 718a7eef..7adbd6fb 100644 --- a/src/client/lib/batch-groups.ts +++ b/src/client/lib/batch-groups.ts @@ -1,4 +1,4 @@ -import type { FileReviewRecord } from '@codra/schema'; +import type { FileReviewRecord } from '@codraoss/schema'; // Bin membership is never persisted (pack.ts derives it rather than storing it). But every file in // a bin is written with the SAME shared response, so grouping on `rawAiOutput` reconstructs the bins diff --git a/src/client/lib/job-format.ts b/src/client/lib/job-format.ts index 40bcbac6..a29c0f1c 100644 --- a/src/client/lib/job-format.ts +++ b/src/client/lib/job-format.ts @@ -1,4 +1,4 @@ -import type { JobSummary } from '@codra/schema'; +import type { JobSummary } from '@codraoss/schema'; /** Shared job status/duration formatting - keep it here, not duplicated per-component (previous copies diverged). */ diff --git a/src/client/lib/timezone.ts b/src/client/lib/timezone.ts index fcdc0f43..f5621a71 100644 --- a/src/client/lib/timezone.ts +++ b/src/client/lib/timezone.ts @@ -1,4 +1,4 @@ -import { isSupportedTimeZone } from '@codra/schema/timezone'; +import { isSupportedTimeZone } from '@codraoss/schema/timezone'; /** * Display timezone for dashboard timestamps; storage is always TIMESTAMPTZ, so this is purely diff --git a/src/client/main.tsx b/src/client/main.tsx index 8ad18ac9..2c45ac51 100644 --- a/src/client/main.tsx +++ b/src/client/main.tsx @@ -19,9 +19,9 @@ const NotFoundPage = React.lazy(() => import('./pages/not-found').then(m => ({ d import './app.css'; -import { ThemeProvider } from '@codra/ui/theme'; -import { useIsDarkMode } from '@codra/ui/hooks'; -import { SmoothScroll } from '@codra/ui/motion'; +import { ThemeProvider } from '@codraoss/ui/theme'; +import { useIsDarkMode } from '@codraoss/ui/hooks'; +import { SmoothScroll } from '@codraoss/ui/motion'; function ToasterWrapper() { const isDark = useIsDarkMode(); diff --git a/src/client/pages/account.tsx b/src/client/pages/account.tsx index 52e034f5..d9a4ea22 100644 --- a/src/client/pages/account.tsx +++ b/src/client/pages/account.tsx @@ -1,4 +1,4 @@ -import { LoadError } from '@codra/ui'; +import { LoadError } from '@codraoss/ui'; import { useEffect, useRef, useState } from 'react'; import { toast } from 'sonner'; import { api } from '@client/lib/api'; @@ -8,7 +8,7 @@ import { resolvedTimeZone, setStoredTimeZone, } from '@client/lib/timezone'; -import type { AccountSettings, AuthSessionUser } from '@codra/schema/api'; +import type { AccountSettings, AuthSessionUser } from '@codraoss/schema/api'; import { ProfileCard } from '@client/components/features/account/profile-card'; import { AccountDetailsSection } from '@client/components/features/account/details-section'; diff --git a/src/client/pages/dashboard.tsx b/src/client/pages/dashboard.tsx index 324f23aa..bc1dae78 100644 --- a/src/client/pages/dashboard.tsx +++ b/src/client/pages/dashboard.tsx @@ -1,7 +1,7 @@ -import { Button, EmptyState, LoadError } from '@codra/ui'; +import { Button, EmptyState, LoadError } from '@codraoss/ui'; import { useState } from 'react'; import { api } from '@client/lib/api'; -import type { StatsPayload, JobSummary } from '@codra/schema'; +import type { StatsPayload, JobSummary } from '@codraoss/schema'; import { ArrowRight, GitPullRequest, Activity } from 'lucide-react'; import { JobsTable } from '@client/components/shared/jobs-table'; import { PageHeaderActions } from '@client/components/shared/page-header-actions'; diff --git a/src/client/pages/job-detail.tsx b/src/client/pages/job-detail.tsx index 7386f95a..7552493b 100644 --- a/src/client/pages/job-detail.tsx +++ b/src/client/pages/job-detail.tsx @@ -1,4 +1,4 @@ -import { LoadError } from '@codra/ui'; +import { LoadError } from '@codraoss/ui'; import { useState } from 'react'; import { useParams } from 'react-router-dom'; import { LazyMotion, m, domMax } from 'motion/react'; @@ -11,7 +11,7 @@ import { JobReviewOverview } from '@client/components/features/job-detail/job-re import { JobFindingsList } from '@client/components/features/job-detail/job-findings-list'; import { JobDiffs } from '@client/components/features/job-detail/job-diffs'; import { JobDetailSkeleton } from '@client/components/features/job-detail/job-skeleton'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; type DetailTab = 'overview' | 'files'; diff --git a/src/client/pages/job-logs.tsx b/src/client/pages/job-logs.tsx index 083f6f1e..5334b649 100644 --- a/src/client/pages/job-logs.tsx +++ b/src/client/pages/job-logs.tsx @@ -1,9 +1,9 @@ -import { Badge, CopyButton, LoadError } from '@codra/ui'; +import { Badge, CopyButton, LoadError } from '@codraoss/ui'; import { useEffect, useMemo, useState } from 'react'; import { useParams, Link } from 'react-router-dom'; -import { preventToggleOnTextSelection } from '@codra/ui/selection'; +import { preventToggleOnTextSelection } from '@codraoss/ui/selection'; import { readDiffsCache, writeDiffsCache } from '@client/lib/diffs-cache'; import { groupBatches } from '@client/lib/batch-groups'; import type { BatchGroup } from '@client/lib/batch-groups'; @@ -16,9 +16,9 @@ import { useJobDetail } from '@client/hooks/use-job-detail'; import { JobDetailSkeleton } from '@client/components/features/job-detail/job-skeleton'; import { api } from '@client/lib/api'; -import type { FileReviewRecord } from '@codra/schema'; +import type { FileReviewRecord } from '@codraoss/schema'; -import { formatPreciseDuration } from '@codra/ui/utils'; +import { formatPreciseDuration } from '@codraoss/ui/utils'; function fmtK(n: number | null) { if (n === null) return null; diff --git a/src/client/pages/jobs.tsx b/src/client/pages/jobs.tsx index 7e9e9e9b..8407117f 100644 --- a/src/client/pages/jobs.tsx +++ b/src/client/pages/jobs.tsx @@ -1,11 +1,11 @@ -import { Button, EmptyState, Input, LoadError, Select } from '@codra/ui'; +import { Button, EmptyState, Input, LoadError, Select } from '@codraoss/ui'; import { useState, useCallback } from 'react'; import { api } from '@client/lib/api'; import { JobsTable } from '@client/components/shared/jobs-table'; import { PageHeader } from '@client/components/layout/page-header'; import { usePolling } from '@client/hooks/use-polling'; import { Activity, ChevronLeft, ChevronRight, ListFilter, RefreshCw, Search } from 'lucide-react'; -import type { JobSummary } from '@codra/schema'; +import type { JobSummary } from '@codraoss/schema'; export function JobsPage() { const [jobs, setJobs] = useState([]); diff --git a/src/client/pages/landing.tsx b/src/client/pages/landing.tsx index 7168fe39..4040c0a8 100644 --- a/src/client/pages/landing.tsx +++ b/src/client/pages/landing.tsx @@ -1,6 +1,6 @@ -import { Button, GithubMark, LinkButton } from '@codra/ui'; +import { Button, GithubMark, LinkButton } from '@codraoss/ui'; import { Sun, Moon, ExternalLink } from 'lucide-react'; -import { useTheme } from '@codra/ui/theme'; +import { useTheme } from '@codraoss/ui/theme'; import codraDark from '@/assets/codra-fullicon-dark.svg'; import codraLight from '@/assets/codra-fullicon-light.svg'; diff --git a/src/client/pages/login.tsx b/src/client/pages/login.tsx index 1092ee9d..3cccb8fe 100644 --- a/src/client/pages/login.tsx +++ b/src/client/pages/login.tsx @@ -1,8 +1,8 @@ -import { GithubMark } from '@codra/ui'; +import { GithubMark } from '@codraoss/ui'; import { useMemo } from 'react'; import { useSearchParams, Link } from 'react-router-dom'; import { Sun, Moon, ShieldCheck, ArrowLeft, AlertCircle } from 'lucide-react'; -import { useTheme } from '@codra/ui/theme'; +import { useTheme } from '@codraoss/ui/theme'; import codraDark from '@/assets/codra-fullicon-dark.svg'; import codraLight from '@/assets/codra-fullicon-light.svg'; diff --git a/src/client/pages/not-found.tsx b/src/client/pages/not-found.tsx index 90914de4..2a2eb4c9 100644 --- a/src/client/pages/not-found.tsx +++ b/src/client/pages/not-found.tsx @@ -1,4 +1,4 @@ -import { Button } from '@codra/ui'; +import { Button } from '@codraoss/ui'; import { Link } from 'react-router-dom'; import { Ghost, Home, ArrowLeft } from 'lucide-react'; diff --git a/src/client/pages/repos.tsx b/src/client/pages/repos.tsx index 5905b65c..54b13157 100644 --- a/src/client/pages/repos.tsx +++ b/src/client/pages/repos.tsx @@ -1,11 +1,11 @@ -import { Button, EmptyState, Input, LinkButton, LoadError, Select, Skeleton } from '@codra/ui'; +import { Button, EmptyState, Input, LinkButton, LoadError, Select, Skeleton } from '@codraoss/ui'; import { useEffect, useMemo, useReducer, useState } from 'react'; import { toast } from 'sonner'; import { api } from '@client/lib/api'; import { PageHeader } from '@client/components/layout/page-header'; import { GitBranch, RefreshCw, ArrowUpRight, Search } from 'lucide-react'; -import { cn } from '@codra/ui/utils'; -import type { RepoConfigRecord } from '@codra/schema'; +import { cn } from '@codraoss/ui/utils'; +import type { RepoConfigRecord } from '@codraoss/schema'; import { EMPTY_MODEL_ROUTE, normalizeModelRoute, diff --git a/src/client/pages/settings.tsx b/src/client/pages/settings.tsx index a354072c..751b15cc 100644 --- a/src/client/pages/settings.tsx +++ b/src/client/pages/settings.tsx @@ -1,8 +1,8 @@ -import { Alert, Button, LoadError } from '@codra/ui'; +import { Alert, Button, LoadError } from '@codraoss/ui'; import { useEffect, useState } from 'react'; import { PageHeader } from '@client/components/layout/page-header'; import { RefreshCw, Plus, X } from 'lucide-react'; -import { cn } from '@codra/ui/utils'; +import { cn } from '@codraoss/ui/utils'; import { AboutSection } from '@client/components/features/settings/about-section'; import { DefaultModelsSection } from '@client/components/features/settings/default-models-section'; import { NewProviderForm } from '@client/components/features/settings/new-provider-form'; diff --git a/src/client/pages/stats.tsx b/src/client/pages/stats.tsx index 82e41204..fd93d213 100644 --- a/src/client/pages/stats.tsx +++ b/src/client/pages/stats.tsx @@ -1,12 +1,12 @@ -import { LoadError } from '@codra/ui'; +import { LoadError } from '@codraoss/ui'; import { useEffect, useState } from 'react'; import { PageHeaderActions } from '@client/components/shared/page-header-actions'; import { PageHeader } from '@client/components/layout/page-header'; -import { useIsDarkMode } from '@codra/ui/hooks'; +import { useIsDarkMode } from '@codraoss/ui/hooks'; import { usePolling } from '@client/hooks/use-polling'; import { useStatsRange } from '@client/hooks/use-stats-range'; import { api } from '@client/lib/api'; -import type { StatsPayload } from '@codra/schema'; +import type { StatsPayload } from '@codraoss/schema'; import { MetricsGridSkeleton } from '@client/components/features/stats/chart-primitives'; diff --git a/src/server/adapters/file-review-store.ts b/src/server/adapters/file-review-store.ts index bc68d2ff..a480b7f3 100644 --- a/src/server/adapters/file-review-store.ts +++ b/src/server/adapters/file-review-store.ts @@ -1,7 +1,7 @@ import type { AppBindings } from '@server/env'; -import type { FileReviewStore } from '@codra/core/ports'; -import { makeFileReviewStore as makeDbFileReviewStore } from '@codra/db/repositories'; -import type { DbEnv } from '@codra/db/env'; +import type { FileReviewStore } from '@codraoss/core/ports'; +import { makeFileReviewStore as makeDbFileReviewStore } from '@codraoss/db/repositories'; +import type { DbEnv } from '@codraoss/db/env'; export function makeFileReviewStore(env: AppBindings): FileReviewStore { const dbEnv: DbEnv = { diff --git a/src/server/adapters/index.ts b/src/server/adapters/index.ts index 3135aea0..cf020ef4 100644 --- a/src/server/adapters/index.ts +++ b/src/server/adapters/index.ts @@ -1,8 +1,8 @@ -import type { ReviewRuntime } from '@codra/core/ports'; -import { TokenTracker } from '@codra/core/token-tracker'; +import type { ReviewRuntime } from '@codraoss/core/ports'; +import { TokenTracker } from '@codraoss/core/token-tracker'; import type { AppBindings } from '@server/env'; // Imported for its side effect as well as never: this installs the AsyncLocalStorage-backed logger as -// the sink @codra/core's logger facade delegates to. Explicit here so engine log lines carry request +// the sink @codraoss/core's logger facade delegates to. Explicit here so engine log lines carry request // context by construction, rather than because some other module happened to be loaded first. import '@server/core/logger'; import { cryptoIds, makeKvStore, makeTelemetrySink, systemClock } from './platform'; @@ -24,7 +24,7 @@ import { } from './services'; // The composition root: the one place Cloudflare bindings, Postgres and the GitHub/model services are -// wired to the engine's ports. @codra/core sees this object and nothing else. +// wired to the engine's ports. @codraoss/core sees this object and nothing else. // // Called once per Worker invocation, before the job is known. It only allocates closures, so it is // cheap enough to build on the webhook path too. diff --git a/src/server/adapters/jobs-store.ts b/src/server/adapters/jobs-store.ts index 24a01c7d..8cfa90e7 100644 --- a/src/server/adapters/jobs-store.ts +++ b/src/server/adapters/jobs-store.ts @@ -1,7 +1,7 @@ import type { AppBindings } from '@server/env'; -import type { JobStore } from '@codra/core/ports'; -import { makeJobStore as makeDbJobStore } from '@codra/db/repositories'; -import type { DbEnv } from '@codra/db/env'; +import type { JobStore } from '@codraoss/core/ports'; +import { makeJobStore as makeDbJobStore } from '@codraoss/db/repositories'; +import type { DbEnv } from '@codraoss/db/env'; export function makeJobStore(env: AppBindings): JobStore { const dbEnv: DbEnv = { diff --git a/src/server/adapters/platform.ts b/src/server/adapters/platform.ts index be9d0313..709419b1 100644 --- a/src/server/adapters/platform.ts +++ b/src/server/adapters/platform.ts @@ -1,4 +1,4 @@ -import type { Clock, IdGenerator, KvStore, TelemetrySink } from '@codra/core/ports'; +import type { Clock, IdGenerator, KvStore, TelemetrySink } from '@codraoss/core/ports'; import type { AppBindings } from '@server/env'; import { sendTelemetryEvent } from '@server/core/telemetry'; diff --git a/src/server/adapters/services.ts b/src/server/adapters/services.ts index e0130d95..fdc99dae 100644 --- a/src/server/adapters/services.ts +++ b/src/server/adapters/services.ts @@ -1,13 +1,13 @@ -import type { GitProviderFactory, ModelErrorClassifier, ReviewFormatter, ReviewGitProvider, ReviewModel } from '@codra/core/ports'; -import type { TokenTracker } from '@codra/core/token-tracker'; +import type { GitProviderFactory, ModelErrorClassifier, ReviewFormatter, ReviewGitProvider, ReviewModel } from '@codraoss/core/ports'; +import type { TokenTracker } from '@codraoss/core/token-tracker'; import type { AppBindings } from '@server/env'; -import { GitHubService } from '@codra/provider-github'; -import { isRetryableModelError, ModelRunner, nextChainIndexOf } from '@codra/models'; +import { GitHubService } from '@codraoss/provider-github'; +import { isRetryableModelError, ModelRunner, nextChainIndexOf } from '@codraoss/models'; import { FormatterService } from '@server/services/formatter'; -import { getResolvedModelConfig } from '@codra/db/model-configs'; +import { getResolvedModelConfig } from '@codraoss/db/model-configs'; // The only place the four job-scoped collaborators are constructed. Every specifier above is the -// barrel form on purpose: nine specs vi.mock '@server/services/github' and '@codra/models', +// barrel form on purpose: nine specs vi.mock '@server/services/github' and '@codraoss/models', // and reaching for a sibling here would bypass those mocks while the tests kept passing. export function makeGitHubFactory(env: AppBindings) { diff --git a/src/server/adapters/settings-store.ts b/src/server/adapters/settings-store.ts index 55be40cc..84efe8c2 100644 --- a/src/server/adapters/settings-store.ts +++ b/src/server/adapters/settings-store.ts @@ -1,7 +1,7 @@ import type { AppBindings } from '@server/env'; -import type { LearningStore, ModelConfigReader, RepoConfigLoader, ReviewSettingsReader, WebhookDeliveryReader } from '@codra/core/ports'; -import { makeLearningStore as makeDbLearningStore, makeModelConfigReader as makeDbModelConfigReader, makeReviewSettingsReader as makeDbReviewSettingsReader, makeWebhookDeliveryReader as makeDbWebhookDeliveryReader } from '@codra/db/repositories'; -import type { DbEnv } from '@codra/db/env'; +import type { LearningStore, ModelConfigReader, RepoConfigLoader, ReviewSettingsReader, WebhookDeliveryReader } from '@codraoss/core/ports'; +import { makeLearningStore as makeDbLearningStore, makeModelConfigReader as makeDbModelConfigReader, makeReviewSettingsReader as makeDbReviewSettingsReader, makeWebhookDeliveryReader as makeDbWebhookDeliveryReader } from '@codraoss/db/repositories'; +import type { DbEnv } from '@codraoss/db/env'; import { loadRepoConfig } from '@server/core/config'; function toDbEnv(env: AppBindings): DbEnv { diff --git a/src/server/core/claim-checks.ts b/src/server/core/claim-checks.ts index a4251d81..cd7b421b 100644 --- a/src/server/core/claim-checks.ts +++ b/src/server/core/claim-checks.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/claim-checks; see the note in ./fingerprint.ts. -export * from '@codra/core/claim-checks'; +// Moved to @codraoss/core/claim-checks; see the note in ./fingerprint.ts. +export * from '@codraoss/core/claim-checks'; diff --git a/src/server/core/config.ts b/src/server/core/config.ts index 04a69446..bb5e323e 100644 --- a/src/server/core/config.ts +++ b/src/server/core/config.ts @@ -1,7 +1,7 @@ -import { defaultRepoConfig, normalizeRepoModelConfig, repoConfigSchema, type RepoConfig } from '@codra/schema'; -import { REPO_CONFIG_CACHE_VERSION } from '@codra/schema'; +import { defaultRepoConfig, normalizeRepoModelConfig, repoConfigSchema, type RepoConfig } from '@codraoss/schema'; +import { REPO_CONFIG_CACHE_VERSION } from '@codraoss/schema'; import type { AppBindings } from '@server/env'; -import { getRepoConfigRecord, syncRepoConfig } from '@codra/db/repo-configs'; +import { getRepoConfigRecord, syncRepoConfig } from '@codraoss/db/repo-configs'; type CachedConfig = { parsedJson: RepoConfig; diff --git a/src/server/core/diff/index.ts b/src/server/core/diff/index.ts index e714101c..51a45667 100644 --- a/src/server/core/diff/index.ts +++ b/src/server/core/diff/index.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/diff; see the note in ../fingerprint.ts. 22 importers name this path. -export * from '@codra/core/diff'; +// Moved to @codraoss/core/diff; see the note in ../fingerprint.ts. 22 importers name this path. +export * from '@codraoss/core/diff'; diff --git a/src/server/core/fingerprint.ts b/src/server/core/fingerprint.ts index 14989f19..8b7e3083 100644 --- a/src/server/core/fingerprint.ts +++ b/src/server/core/fingerprint.ts @@ -1,4 +1,4 @@ -// Moved to @codra/core/fingerprint. Kept as a re-export so the existing `@server/core/fingerprint` +// Moved to @codraoss/core/fingerprint. Kept as a re-export so the existing `@server/core/fingerprint` // importers -- and the specs that name that specifier -- do not all have to change in the same // commit as the move. -export * from '@codra/core/fingerprint'; +export * from '@codraoss/core/fingerprint'; diff --git a/src/server/core/job-recovery.ts b/src/server/core/job-recovery.ts index ecbeb216..f91be5ba 100644 --- a/src/server/core/job-recovery.ts +++ b/src/server/core/job-recovery.ts @@ -1,7 +1,7 @@ import type { AppBindings } from '@server/env'; -import { getTerminalJobsNeedingCheckRunCompletion, markJobCheckRunCompleted, recoverExpiredJobLeases } from '@codra/db/jobs'; +import { getTerminalJobsNeedingCheckRunCompletion, markJobCheckRunCompleted, recoverExpiredJobLeases } from '@codraoss/db/jobs'; import { logger } from '@server/core/logger'; -import { GitHubService } from '@codra/provider-github'; +import { GitHubService } from '@codraoss/provider-github'; const MAX_RECOVERY_COUNT = 3; diff --git a/src/server/core/logger.ts b/src/server/core/logger.ts index f6f8ec86..859c8027 100644 --- a/src/server/core/logger.ts +++ b/src/server/core/logger.ts @@ -1,10 +1,10 @@ import { AsyncLocalStorage } from 'node:async_hooks'; -import { formatLogRecord, setLoggerSink } from '@codra/core/logger'; +import { formatLogRecord, setLoggerSink } from '@codraoss/core/logger'; -// The request-context half of the logger. Scrubbing and record shaping live in @codra/core/logger; +// The request-context half of the logger. Scrubbing and record shaping live in @codraoss/core/logger; // this file owns everything platform-bound -- AsyncLocalStorage and the console sink -- so that // node:async_hooks never enters the engine package. Importing this module installs it as the sink -// that @codra/core's `logger` facade delegates to (see the bottom of the file). +// that @codraoss/core's `logger` facade delegates to (see the bottom of the file). const storage = new AsyncLocalStorage>(); class Logger { @@ -58,7 +58,7 @@ class Logger { export const logger = new Logger(); -// Wired at import scope so engine code logging through @codra/core's facade lands here, with request +// Wired at import scope so engine code logging through @codraoss/core's facade lands here, with request // context attached, rather than in core's bare console fallback. src/server/index.ts and // src/server/adapters/index.ts both import this module explicitly to make the wiring deliberate // rather than a side effect of whichever file happened to be loaded first. diff --git a/src/server/core/model-output/index.ts b/src/server/core/model-output/index.ts index 20cee825..d5eead39 100644 --- a/src/server/core/model-output/index.ts +++ b/src/server/core/model-output/index.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/model-output; see the note in ../fingerprint.ts. -export * from '@codra/core/model-output'; +// Moved to @codraoss/core/model-output; see the note in ../fingerprint.ts. +export * from '@codraoss/core/model-output'; diff --git a/src/server/core/oauth.ts b/src/server/core/oauth.ts index 19a0227d..682a782b 100644 --- a/src/server/core/oauth.ts +++ b/src/server/core/oauth.ts @@ -1,4 +1,4 @@ -import { randomHex } from '@codra/schema/hex'; +import { randomHex } from '@codraoss/schema/hex'; import type { AppBindings } from '@server/env'; const OAUTH_STATE_TTL_SECONDS = 60 * 10; diff --git a/src/server/core/review/index.ts b/src/server/core/review/index.ts index 7c90c19a..4b211ada 100644 --- a/src/server/core/review/index.ts +++ b/src/server/core/review/index.ts @@ -1,12 +1,12 @@ -import type { ReviewJobMessage } from '@codra/schema'; -import { runReview, type ReviewJobRunResult } from '@codra/core'; +import type { ReviewJobMessage } from '@codraoss/schema'; +import { runReview, type ReviewJobRunResult } from '@codraoss/core'; import { createReviewRuntime } from '@server/adapters'; import type { AppBindings } from '@server/env'; -// The seam between the Worker and the engine. The engine moved to @codra/core; this converts +// The seam between the Worker and the engine. The engine moved to @codraoss/core; this converts // AppBindings into the ports it takes, and is the ONLY place in production that does. // -// Kept as a module rather than repointing callers at @codra/core because +// Kept as a module rather than repointing callers at @codraoss/core because // test/review/workflow-finalize-fresh-instance.spec.ts partially mocks this specifier -- substituting // runReviewJob while keeping FRESH_INVOCATION_YIELD_SECONDS real -- and test/mocks/review-harness.ts // types itself as Parameters[1]. Sixteen DB-backed review suites reach the @@ -43,4 +43,4 @@ export { type ReviewUnit, type VerifyDrop, type VerifyOutcome, -} from '@codra/core'; +} from '@codraoss/core'; diff --git a/src/server/core/rules/detect.ts b/src/server/core/rules/detect.ts index 783de864..b85553b0 100644 --- a/src/server/core/rules/detect.ts +++ b/src/server/core/rules/detect.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/rules/detect; see the note in ../fingerprint.ts. -export * from '@codra/core/rules/detect'; +// Moved to @codraoss/core/rules/detect; see the note in ../fingerprint.ts. +export * from '@codraoss/core/rules/detect'; diff --git a/src/server/core/rules/table.ts b/src/server/core/rules/table.ts index 5cbea257..58445181 100644 --- a/src/server/core/rules/table.ts +++ b/src/server/core/rules/table.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/rules/table; see the note in ../fingerprint.ts. -export * from '@codra/core/rules/table'; +// Moved to @codraoss/core/rules/table; see the note in ../fingerprint.ts. +export * from '@codraoss/core/rules/table'; diff --git a/src/server/core/telemetry.ts b/src/server/core/telemetry.ts index 51a86936..d0c25ea0 100644 --- a/src/server/core/telemetry.ts +++ b/src/server/core/telemetry.ts @@ -4,7 +4,7 @@ import { logger } from './logger'; const TELEMETRY_SECRET = 'codra-telemetry-v1-secret-8f9a2b5c'; const INSTANCE_ID_KEY = 'codra:instance_id'; -import { queryRows } from '@codra/db/client'; +import { queryRows } from '@codraoss/db/client'; // Static import: version string is inlined at build time by Vite - no runtime cost. import pkg from '../../../package.json'; diff --git a/src/server/core/timeout.ts b/src/server/core/timeout.ts index 1ac1008e..dedf69dd 100644 --- a/src/server/core/timeout.ts +++ b/src/server/core/timeout.ts @@ -1,3 +1,3 @@ -// Moved to @codra/core/timeout; see the note in ./fingerprint.ts. Eight importers live outside the +// Moved to @codraoss/core/timeout; see the note in ./fingerprint.ts. Eight importers live outside the // review engine (core/github/*, models/*), which is why this shim stays rather than being inlined. -export * from '@codra/core/timeout'; +export * from '@codraoss/core/timeout'; diff --git a/src/server/core/token-tracker.ts b/src/server/core/token-tracker.ts index 8b707f52..9a7c00cb 100644 --- a/src/server/core/token-tracker.ts +++ b/src/server/core/token-tracker.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/token-tracker; see the note in ./fingerprint.ts. -export * from '@codra/core/token-tracker'; +// Moved to @codraoss/core/token-tracker; see the note in ./fingerprint.ts. +export * from '@codraoss/core/token-tracker'; diff --git a/src/server/core/verify.ts b/src/server/core/verify.ts index 42ebb87c..cf08e7a0 100644 --- a/src/server/core/verify.ts +++ b/src/server/core/verify.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/verify; see the note in ./fingerprint.ts. -export * from '@codra/core/verify'; +// Moved to @codraoss/core/verify; see the note in ./fingerprint.ts. +export * from '@codraoss/core/verify'; diff --git a/src/server/env.d.ts b/src/server/env.d.ts index 195bf6e6..b4c600bb 100644 --- a/src/server/env.d.ts +++ b/src/server/env.d.ts @@ -1,4 +1,4 @@ -import type { DashboardSessionUser, SessionStore } from '@codra/core'; +import type { DashboardSessionUser, SessionStore } from '@codraoss/core'; export interface AppBindings { SESSION_STORE: SessionStore; APP_PRIVATE_KEY: string; diff --git a/src/server/env.ts b/src/server/env.ts index 958dad25..3f85a169 100644 --- a/src/server/env.ts +++ b/src/server/env.ts @@ -1,4 +1,4 @@ -import type { DashboardSessionUser, SessionStore, IdentityProvider } from '@codra/core'; +import type { DashboardSessionUser, SessionStore, IdentityProvider } from '@codraoss/core'; export type { DashboardSessionUser }; export interface AppBindings { diff --git a/src/server/prompts/file-review.ts b/src/server/prompts/file-review.ts index 356c3701..4f3109fe 100644 --- a/src/server/prompts/file-review.ts +++ b/src/server/prompts/file-review.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/prompts/file-review; see the note in ../core/fingerprint.ts. -export * from '@codra/core/prompts/file-review'; +// Moved to @codraoss/core/prompts/file-review; see the note in ../core/fingerprint.ts. +export * from '@codraoss/core/prompts/file-review'; diff --git a/src/server/prompts/languages.ts b/src/server/prompts/languages.ts index b644f694..37287ba3 100644 --- a/src/server/prompts/languages.ts +++ b/src/server/prompts/languages.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/prompts/languages; see the note in ../core/fingerprint.ts. -export * from '@codra/core/prompts/languages'; +// Moved to @codraoss/core/prompts/languages; see the note in ../core/fingerprint.ts. +export * from '@codraoss/core/prompts/languages'; diff --git a/src/server/prompts/summary.ts b/src/server/prompts/summary.ts index 336e7870..ff2f3afb 100644 --- a/src/server/prompts/summary.ts +++ b/src/server/prompts/summary.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/prompts/summary; see the note in ../core/fingerprint.ts. -export * from '@codra/core/prompts/summary'; +// Moved to @codraoss/core/prompts/summary; see the note in ../core/fingerprint.ts. +export * from '@codraoss/core/prompts/summary'; diff --git a/src/server/prompts/verify.ts b/src/server/prompts/verify.ts index e0588b25..c5c11a45 100644 --- a/src/server/prompts/verify.ts +++ b/src/server/prompts/verify.ts @@ -1,2 +1,2 @@ -// Moved to @codra/core/prompts/verify; see the note in ../core/fingerprint.ts. -export * from '@codra/core/prompts/verify'; +// Moved to @codraoss/core/prompts/verify; see the note in ../core/fingerprint.ts. +export * from '@codraoss/core/prompts/verify'; diff --git a/src/server/services/formatter.ts b/src/server/services/formatter.ts index bbaa88fd..d7b4402b 100644 --- a/src/server/services/formatter.ts +++ b/src/server/services/formatter.ts @@ -1,4 +1,4 @@ -import type { ParsedReviewComment } from '@codra/schema'; +import type { ParsedReviewComment } from '@codraoss/schema'; // The third field is OPTIONAL so every comment already on GitHub still parses; requiring it would silently stop recording deletions of historical comments. const FINDING_MARKER_PATTERN = //; diff --git a/test/api/auth.spec.ts b/test/api/auth.spec.ts index 7b551459..78abac7d 100644 --- a/test/api/auth.spec.ts +++ b/test/api/auth.spec.ts @@ -6,15 +6,15 @@ // That is why this file is over the max-lines limit and carries an explicit eslint override rather // than being divided; the account/session half could move out, but the settings half cannot. -import { getReviewSettings, updateReviewSettings } from '@codra/db/app-settings'; -import { reviewMaxFilesRange } from '@codra/schema'; -import { createApiRouter } from '@codra/api'; +import { getReviewSettings, updateReviewSettings } from '@codraoss/db/app-settings'; +import { reviewMaxFilesRange } from '@codraoss/schema'; +import { createApiRouter } from '@codraoss/api'; -import { queryRows, runWithDb } from '@codra/db/client'; +import { queryRows, runWithDb } from '@codraoss/db/client'; import { syncUpdatesEmail } from '@server/core/updates-email'; -import type { AccountResponse, JobsResponse, UpdatesEmailResponse } from '@codra/schema/api'; +import type { AccountResponse, JobsResponse, UpdatesEmailResponse } from '@codraoss/schema/api'; import { createTestEnv, dbDescribe } from '../helpers'; import { vi } from 'vitest'; diff --git a/test/api/jobs.spec.ts b/test/api/jobs.spec.ts index 8cdd604a..42a2b768 100644 --- a/test/api/jobs.spec.ts +++ b/test/api/jobs.spec.ts @@ -1,5 +1,5 @@ -import { createApiRouter } from '@codra/api'; -import { getJobForProcessing, insertJob } from '@codra/db/jobs'; +import { createApiRouter } from '@codraoss/api'; +import { getJobForProcessing, insertJob } from '@codraoss/db/jobs'; import { createTestEnv, uniqueName } from '../helpers'; import { vi } from 'vitest'; diff --git a/test/api/repos.spec.ts b/test/api/repos.spec.ts index f3b01786..ae874459 100644 --- a/test/api/repos.spec.ts +++ b/test/api/repos.spec.ts @@ -1,11 +1,11 @@ -import { createApiRouter } from '@codra/api'; -import { getJobForProcessing, insertJob } from '@codra/db/jobs'; +import { createApiRouter } from '@codraoss/api'; +import { getJobForProcessing, insertJob } from '@codraoss/db/jobs'; -import { getRepoConfigRecord } from '@codra/db/repo-configs'; +import { getRepoConfigRecord } from '@codraoss/db/repo-configs'; import { loadRepoConfig, updateGlobalConfig } from '@server/core/config'; -import { GitHubClient } from '@codra/provider-github'; +import { GitHubClient } from '@codraoss/provider-github'; -import { defaultRepoConfig } from '@codra/schema'; +import { defaultRepoConfig } from '@codraoss/schema'; import { createTestEnv, uniqueName } from '../helpers'; import { vi } from 'vitest'; diff --git a/test/api/webhook-handling.spec.ts b/test/api/webhook-handling.spec.ts index b8542837..38d4e437 100644 --- a/test/api/webhook-handling.spec.ts +++ b/test/api/webhook-handling.spec.ts @@ -1,4 +1,4 @@ -import { createApiRouter } from '@codra/api'; +import { createApiRouter } from '@codraoss/api'; import { createMockPRWebhook, createTestEnv, uniqueName } from '../helpers'; import { signPayload } from '../mocks/fixtures'; diff --git a/test/comment-feedback.spec.ts b/test/comment-feedback.spec.ts index 8ab1ccfb..b60aa2ca 100644 --- a/test/comment-feedback.spec.ts +++ b/test/comment-feedback.spec.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { createApiRouter } from '@codra/api'; +import { createApiRouter } from '@codraoss/api'; import { createTestEnv } from './helpers'; import { FormatterService, formatFindingMarker, parseFindingMarker } from '@server/services/formatter'; -import type { ParsedReviewComment } from '@codra/schema'; +import type { ParsedReviewComment } from '@codraoss/schema'; import { signPayload } from './mocks/fixtures'; diff --git a/test/db/bulk-upsert.spec.ts b/test/db/bulk-upsert.spec.ts index a60b0e51..a607d34a 100644 --- a/test/db/bulk-upsert.spec.ts +++ b/test/db/bulk-upsert.spec.ts @@ -5,9 +5,9 @@ import { bulkRecordRetryableFileReviewFailures, bulkUpsertFileReviews, getFileReviewsForJobs, -} from '@codra/db/file-reviews'; -import { getJobDetail, insertJob } from '@codra/db/jobs'; -import type { ParsedReviewComment } from '@codra/schema'; +} from '@codraoss/db/file-reviews'; +import { getJobDetail, insertJob } from '@codraoss/db/jobs'; +import type { ParsedReviewComment } from '@codraoss/schema'; import { createTestEnv, dbDescribe, sha, uniqueName } from '../helpers'; const env = createTestEnv(); diff --git a/test/db/stats-trend.spec.ts b/test/db/stats-trend.spec.ts index f6239551..f8c34bee 100644 --- a/test/db/stats-trend.spec.ts +++ b/test/db/stats-trend.spec.ts @@ -1,5 +1,5 @@ import { expect, it } from 'vitest'; -import { getStats, trendBucketDays } from '@codra/db/stats'; +import { getStats, trendBucketDays } from '@codraoss/db/stats'; import { createTestEnv, dbDescribe } from '../helpers'; const env = createTestEnv(); diff --git a/test/diff.spec.ts b/test/diff.spec.ts index c4935e98..c498d24d 100644 --- a/test/diff.spec.ts +++ b/test/diff.spec.ts @@ -7,7 +7,7 @@ import { parseUnifiedDiff, truncateFileDiff, } from '@server/core/diff'; -import { defaultRepoConfig } from '@codra/schema'; +import { defaultRepoConfig } from '@codraoss/schema'; describe('Diff Engine Deep Dive', () => { const sampleDiff = `diff --git a/src/example.ts b/src/example.ts diff --git a/test/e2e/accordion-selection.spec.tsx b/test/e2e/accordion-selection.spec.tsx index 1fa7b933..7a5d803c 100644 --- a/test/e2e/accordion-selection.spec.tsx +++ b/test/e2e/accordion-selection.spec.tsx @@ -3,7 +3,7 @@ */ import { describe, expect, it } from 'vitest'; import { render } from '@testing-library/react'; -import { preventToggleOnTextSelection } from '@codra/ui/selection'; +import { preventToggleOnTextSelection } from '@codraoss/ui/selection'; /** * Accordion headers used to carry `select-none`, making the file path uncopyable. Removing it is diff --git a/test/e2e/dashboard.spec.tsx b/test/e2e/dashboard.spec.tsx index 328721aa..c8cc41f6 100644 --- a/test/e2e/dashboard.spec.tsx +++ b/test/e2e/dashboard.spec.tsx @@ -7,7 +7,7 @@ import { LoginPage } from '@client/pages/login'; import { DashboardPage } from '@client/pages/dashboard'; import { MemoryRouter } from 'react-router-dom'; import { api } from '@client/lib/api'; -import { ThemeProvider } from '@codra/ui/theme'; +import { ThemeProvider } from '@codraoss/ui/theme'; vi.mock('@client/lib/api', () => ({ api: { diff --git a/test/findings/claim-types.spec.ts b/test/findings/claim-types.spec.ts index 7ee7a1e8..b9790714 100644 --- a/test/findings/claim-types.spec.ts +++ b/test/findings/claim-types.spec.ts @@ -4,7 +4,7 @@ import { CLAIM_TYPE_DECIDABILITY, DEFAULT_DENIED_CLAIM_TYPES, claimTypes, -} from '@codra/schema'; +} from '@codraoss/schema'; import type { FileDiff } from '@server/core/diff'; import { reviewJson } from '../mocks/fixtures'; diff --git a/test/findings/gold-set.spec.ts b/test/findings/gold-set.spec.ts index 0fa1c7aa..7d43eae7 100644 --- a/test/findings/gold-set.spec.ts +++ b/test/findings/gold-set.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { parseFileReviewResponse } from '@server/core/model-output'; import { verifyFindings } from '@server/core/review'; -import { DEFAULT_DENIED_CLAIM_TYPES, defaultRepoConfig } from '@codra/schema'; +import { DEFAULT_DENIED_CLAIM_TYPES, defaultRepoConfig } from '@codraoss/schema'; import type { FileDiff } from '@server/core/diff'; // The regression wall. diff --git a/test/findings/prompts-batch-review.spec.ts b/test/findings/prompts-batch-review.spec.ts index 43a65a9c..ff705ef9 100644 --- a/test/findings/prompts-batch-review.spec.ts +++ b/test/findings/prompts-batch-review.spec.ts @@ -6,8 +6,8 @@ import { generatorFindingCap, } from '@server/prompts/file-review'; import { BIN_DIFF_CHAR_BUDGET, BIN_MAX_FILES } from '@server/core/review'; -import { PROMPT_FIT_SAFETY_FACTOR, estimatePromptTokens } from '@codra/models'; -import { defaultRepoConfig } from '@codra/schema'; +import { PROMPT_FIT_SAFETY_FACTOR, estimatePromptTokens } from '@codraoss/models'; +import { defaultRepoConfig } from '@codraoss/schema'; import type { FileDiff } from '@server/core/diff'; function file(path: string, lines: string[]): FileDiff { diff --git a/test/findings/prompts-file-review.spec.ts b/test/findings/prompts-file-review.spec.ts index 7303c689..c9e8854c 100644 --- a/test/findings/prompts-file-review.spec.ts +++ b/test/findings/prompts-file-review.spec.ts @@ -6,7 +6,7 @@ import { buildReviewResponseSchema, generatorFindingCap, } from '@server/prompts/file-review'; -import { defaultRepoConfig } from '@codra/schema'; +import { defaultRepoConfig } from '@codraoss/schema'; import type { FileDiff } from '@server/core/diff'; function fileAt(path: string): FileDiff { diff --git a/test/findings/review-verify.spec.ts b/test/findings/review-verify.spec.ts index 408f1ba6..8f75bf30 100644 --- a/test/findings/review-verify.spec.ts +++ b/test/findings/review-verify.spec.ts @@ -1,5 +1,5 @@ import { verifyFindings } from '@server/core/review'; -import { defaultRepoConfig, type ParsedReviewComment } from '@codra/schema'; +import { defaultRepoConfig, type ParsedReviewComment } from '@codraoss/schema'; import type { FileDiff } from '@server/core/diff'; const files: FileDiff[] = [ diff --git a/test/findings/rules-detect.spec.ts b/test/findings/rules-detect.spec.ts index d6e7b6bc..76cd0b91 100644 --- a/test/findings/rules-detect.spec.ts +++ b/test/findings/rules-detect.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { scanFileForRuleHits, ruleHitsToComments } from '@server/core/rules/detect'; import { RULES } from '@server/core/rules/table'; -import { CLAIM_TYPE_DECIDABILITY, DEFAULT_SHADOW_RULE_IDS } from '@codra/schema'; +import { CLAIM_TYPE_DECIDABILITY, DEFAULT_SHADOW_RULE_IDS } from '@codraoss/schema'; import { addedLinesFile } from '../mocks/fixtures'; diff --git a/test/findings/rules-pipeline.spec.ts b/test/findings/rules-pipeline.spec.ts index b9f27854..6fe30448 100644 --- a/test/findings/rules-pipeline.spec.ts +++ b/test/findings/rules-pipeline.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { dedupeFindings } from '@server/core/model-output'; import { ruleHitsToComments, scanFileForRuleHits } from '@server/core/rules/detect'; -import { defaultRepoConfig } from '@codra/schema'; +import { defaultRepoConfig } from '@codraoss/schema'; import type { FileDiff } from '@server/core/diff'; import { addedLinesFile } from '../mocks/fixtures'; diff --git a/test/findings/suppression.spec.ts b/test/findings/suppression.spec.ts index 1e6fa555..d5b8cf98 100644 --- a/test/findings/suppression.spec.ts +++ b/test/findings/suppression.spec.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from 'vitest'; import { createTestEnv, dbDescribe, sha, uniqueName } from '../helpers'; -import { upsertDashboardFeedback } from '@codra/db/comment-feedback'; -import { runWithDb, queryRows } from '@codra/db/client'; -import { insertJob } from '@codra/db/jobs'; -import { getSuppressedFindings, markCommentsPosted, upsertFileReview } from '@codra/db/file-reviews'; -import type { ParsedReviewComment } from '@codra/schema'; +import { upsertDashboardFeedback } from '@codraoss/db/comment-feedback'; +import { runWithDb, queryRows } from '@codraoss/db/client'; +import { insertJob } from '@codraoss/db/jobs'; +import { getSuppressedFindings, markCommentsPosted, upsertFileReview } from '@codraoss/db/file-reviews'; +import type { ParsedReviewComment } from '@codraoss/schema'; diff --git a/test/helpers.ts b/test/helpers.ts index fcc8d990..432711cf 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -1,10 +1,10 @@ import { describe } from 'vitest'; import type { AppBindings } from '@server/env'; -import { InMemorySessionStore } from '@codra/core'; -import { encryptLlmApiKey, ModelRunner } from '@codra/models'; -import { queryRows } from '@codra/db/client'; -import { getResolvedModelConfig } from '@codra/db/model-configs'; -import type { TokenTracker } from '@codra/core/token-tracker'; +import { InMemorySessionStore } from '@codraoss/core'; +import { encryptLlmApiKey, ModelRunner } from '@codraoss/models'; +import { queryRows } from '@codraoss/db/client'; +import { getResolvedModelConfig } from '@codraoss/db/model-configs'; +import type { TokenTracker } from '@codraoss/core/token-tracker'; import { createApiRouterDeps } from '../apps/worker/src/api-deps'; export class MemoryKV { diff --git a/test/jsonb-encoding.spec.ts b/test/jsonb-encoding.spec.ts index f8c12a82..bc2f736e 100644 --- a/test/jsonb-encoding.spec.ts +++ b/test/jsonb-encoding.spec.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { defaultRepoConfig } from '@codra/schema'; -import { queryRows } from '@codra/db/client'; -import { insertJob } from '@codra/db/jobs'; -import { upsertFileReview } from '@codra/db/file-reviews'; -import { syncRepoConfig, upsertRepoConfig } from '@codra/db/repo-configs'; +import { defaultRepoConfig } from '@codraoss/schema'; +import { queryRows } from '@codraoss/db/client'; +import { insertJob } from '@codraoss/db/jobs'; +import { upsertFileReview } from '@codraoss/db/file-reviews'; +import { syncRepoConfig, upsertRepoConfig } from '@codraoss/db/repo-configs'; import { createTestEnv } from './helpers'; // `JSON.stringify(x)` bound to `$n::jsonb` stores a jsonb STRING SCALAR, so every SQL JSON operator diff --git a/test/mocks/review-harness.ts b/test/mocks/review-harness.ts index 98ee6e75..b327acde 100644 --- a/test/mocks/review-harness.ts +++ b/test/mocks/review-harness.ts @@ -1,5 +1,5 @@ import { runReviewJob } from '@server/core/review'; -import { runWithDb, queryRows } from '@codra/db/client'; +import { runWithDb, queryRows } from '@codraoss/db/client'; import type { AppBindings } from '@server/env'; // Drives a review job through every phase the way the workflow would, in-process. diff --git a/test/mocks/services.ts b/test/mocks/services.ts index 60746737..cccd129f 100644 --- a/test/mocks/services.ts +++ b/test/mocks/services.ts @@ -1,4 +1,4 @@ -import type { BatchReviewOutcome } from '@codra/models'; +import type { BatchReviewOutcome } from '@codraoss/models'; // Shared service doubles for the DB-backed review suites. `vi.mock` factories are hoisted, so pull // these in with a dynamic `await import(...)` inside the factory. @@ -141,7 +141,7 @@ export function makeModelServiceMock(overrides: Record = {}) { export const isRetryableModelErrorMock = (error: unknown) => Boolean(error && typeof error === 'object' && (error as { retryable?: boolean }).retryable === true); -// Every hand-built mock of the '@codra/models' barrel must include this. The review +// Every hand-built mock of the '@codraoss/models' barrel must include this. The review // runners call it inside their catch block, so a missing export is a TypeError raised while // handling a failure -- which silently converts a deferral into a terminal one. export const nextChainIndexOfMock = (error: unknown) => { diff --git a/test/review/async-batch.spec.ts b/test/review/async-batch.spec.ts index 863d4d61..d7139ce1 100644 --- a/test/review/async-batch.spec.ts +++ b/test/review/async-batch.spec.ts @@ -1,16 +1,16 @@ import { runReviewJob } from '@server/core/review'; import { createTestEnv, dbDescribe, sha, uniqueName, uniqueRepo } from '../helpers'; import { afterAll, expect, vi } from 'vitest'; -import { findExistingJobForHead, getJobForProcessing } from '@codra/db/jobs'; -import { getFileReviewsForJobs } from '@codra/db/file-reviews'; -import { runWithDb, queryRows } from '@codra/db/client'; +import { findExistingJobForHead, getJobForProcessing } from '@codraoss/db/jobs'; +import { getFileReviewsForJobs } from '@codraoss/db/file-reviews'; +import { runWithDb, queryRows } from '@codraoss/db/client'; const { getOtherRunningJobsCountMock } = vi.hoisted(() => ({ getOtherRunningJobsCountMock: vi.fn().mockResolvedValue(0), })); -vi.mock('@codra/db/jobs', async (importOriginal) => { +vi.mock('@codraoss/db/jobs', async (importOriginal) => { const mod = await importOriginal>(); return { ...mod, getOtherRunningJobsCount: getOtherRunningJobsCountMock }; }); @@ -19,14 +19,14 @@ vi.mock('@codra/db/jobs', async (importOriginal) => { // parallel. This suite only needs some fixed concurrency, so pin the schema default. const { getReviewSettingsMock } = vi.hoisted(() => ({ getReviewSettingsMock: vi.fn() })); -vi.mock('@codra/db/app-settings', async (importOriginal) => { +vi.mock('@codraoss/db/app-settings', async (importOriginal) => { const mod = await importOriginal>(); - const { reviewSettingsSchema } = await import('@codra/schema'); + const { reviewSettingsSchema } = await import('@codraoss/schema'); getReviewSettingsMock.mockResolvedValue(reviewSettingsSchema.parse({})); return { ...mod, getReviewSettings: getReviewSettingsMock }; }); -vi.mock('@codra/provider-github', async (importOriginal) => { +vi.mock('@codraoss/provider-github', async (importOriginal) => { const mod = await importOriginal>(); const { makeGitHubServiceMock } = await import('../mocks/services'); return { ...mod, GitHubService: makeGitHubServiceMock() }; @@ -36,7 +36,7 @@ vi.mock('@codra/provider-github', async (importOriginal) => { // pending, the second completes. reviewFile must NOT be called on the async path. const pollCalls = { count: 0 }; const reviewFileSpy = vi.fn(); -vi.mock('@codra/models', async () => { +vi.mock('@codraoss/models', async () => { class MockModelService { async submitReviewBatch() { return { requestId: 'req-async-1', model: '@cf/moonshotai/kimi-k2.6' }; @@ -67,7 +67,7 @@ vi.mock('@codra/models', async () => { dbDescribe('Async batch review flow', () => { // Tripwire: if runReviewJob ever stops importing getOtherRunningJobsCount from the - // @codra/db/jobs barrel, this mock silently stops applying and every test here still passes. + // @codraoss/db/jobs barrel, this mock silently stops applying and every test here still passes. afterAll(() => { expect(getOtherRunningJobsCountMock).toHaveBeenCalled(); expect(getReviewSettingsMock).toHaveBeenCalled(); @@ -95,7 +95,7 @@ dbDescribe('Async batch review flow', () => { repository: { owner: { login: 'test-owner' }, name: repo }, pull_request: { number: 1, head: { sha: headSha, ref: 'feature' }, base: { sha: sha('d'), ref: 'main' }, title: 'Test PR', user: { login: 'author' }, draft: false }, }; - const { normalizeGitHubWebhook } = await import('@codra/provider-github'); + const { normalizeGitHubWebhook } = await import('@codraoss/provider-github'); const normalized = normalizeGitHubWebhook('pull_request', rawPayload); const prep = await runReviewJob(env, { deliveryId: uniqueName('delivery-async'), diff --git a/test/review/batch-flow.spec.ts b/test/review/batch-flow.spec.ts index a22ec0fd..dc2a81bb 100644 --- a/test/review/batch-flow.spec.ts +++ b/test/review/batch-flow.spec.ts @@ -1,30 +1,30 @@ import { BIN_MAX_FILES, runReviewJob } from '@server/core/review'; import { createTestEnv, dbDescribe, generateMockDiff, sha, uniqueRepo } from '../helpers'; import { afterEach, expect, it, vi } from 'vitest'; -import { insertJob, updateJobFileCount, updateJobStep } from '@codra/db/jobs'; -import { getFileReviewsForJobs } from '@codra/db/file-reviews'; -import { defaultRepoConfig } from '@codra/schema'; -import { runWithDb } from '@codra/db/client'; +import { insertJob, updateJobFileCount, updateJobStep } from '@codraoss/db/jobs'; +import { getFileReviewsForJobs } from '@codraoss/db/file-reviews'; +import { defaultRepoConfig } from '@codraoss/schema'; +import { runWithDb } from '@codraoss/db/client'; import { REVIEW_FLOW_TIMEOUT_MS } from '../mocks/review-harness'; -vi.mock('@codra/db/jobs', async (importOriginal) => { +vi.mock('@codraoss/db/jobs', async (importOriginal) => { const mod = await importOriginal>(); return { ...mod, getOtherRunningJobsCount: vi.fn().mockResolvedValue(0) }; }); -vi.mock('@codra/db/app-settings', async (importOriginal) => { +vi.mock('@codraoss/db/app-settings', async (importOriginal) => { const mod = await importOriginal>(); - const { reviewSettingsSchema } = await import('@codra/schema'); + const { reviewSettingsSchema } = await import('@codraoss/schema'); return { ...mod, getReviewSettings: vi.fn().mockResolvedValue(reviewSettingsSchema.parse({})) }; }); -vi.mock('@codra/provider-github', async (importOriginal) => { +vi.mock('@codraoss/provider-github', async (importOriginal) => { const mod = await importOriginal>(); const { makeGitHubServiceMock } = await import('../mocks/services'); return { ...mod, GitHubService: makeGitHubServiceMock() }; }); -vi.mock('@codra/models', async () => { +vi.mock('@codraoss/models', async () => { const { makeModelServiceMock, isRetryableModelErrorMock, nextChainIndexOfMock } = await import('../mocks/services'); return { ModelRunner: makeModelServiceMock(), isRetryableModelError: isRetryableModelErrorMock, nextChainIndexOf: nextChainIndexOfMock }; }); @@ -69,8 +69,8 @@ dbDescribe('Review flow: batched small files', () => { }); it('reviews several small files in one call and writes a row per file', async () => { - const { GitHubService } = await import('@codra/provider-github'); - const { ModelRunner } = await import('@codra/models'); + const { GitHubService } = await import('@codraoss/provider-github'); + const { ModelRunner } = await import('@codraoss/models'); const getDiffSpy = vi.spyOn(GitHubService.prototype, 'getPullRequestDiff') .mockResolvedValue(generateMockDiff(smallFiles)); @@ -113,10 +113,10 @@ dbDescribe('Review flow: batched small files', () => { // An error after the write must not take committed rows down: the catch-all's comment DELETE // would wipe correct findings. it('keeps already-committed rows when a later step fails', async () => { - const { GitHubService } = await import('@codra/provider-github'); - const { ModelRunner } = await import('@codra/models'); + const { GitHubService } = await import('@codraoss/provider-github'); + const { ModelRunner } = await import('@codraoss/models'); const { reviewBatchResponse } = await import('../mocks/services'); - const fileReviews = await import('@codra/db/file-reviews'); + const fileReviews = await import('@codraoss/db/file-reviews'); vi.spyOn(GitHubService.prototype, 'getPullRequestDiff').mockResolvedValue(generateMockDiff(smallFiles)); vi.spyOn(ModelRunner.prototype as any, 'reviewFiles').mockImplementation(async () => { @@ -144,10 +144,10 @@ dbDescribe('Review flow: batched small files', () => { // A silently omitted file is re-queued as retryable, and is not progress for the wedge counter. it('re-queues a file the model omitted instead of approving it', async () => { - const { GitHubService } = await import('@codra/provider-github'); - const { ModelRunner } = await import('@codra/models'); + const { GitHubService } = await import('@codraoss/provider-github'); + const { ModelRunner } = await import('@codraoss/models'); const { reviewBatchResponse } = await import('../mocks/services'); - const jobsModule = await import('@codra/db/jobs'); + const jobsModule = await import('@codraoss/db/jobs'); const getDiffSpy = vi.spyOn(GitHubService.prototype, 'getPullRequestDiff') .mockResolvedValue(generateMockDiff(smallFiles)); @@ -183,9 +183,9 @@ dbDescribe('Review flow: batched small files', () => { // After a transient failure the bin must not re-form. The ledger is seeded directly: a real // failure also sets a 30s job delay, which would pass for the wrong reason. it('falls back to single-file reviews once a bin member has failed transiently', async () => { - const { GitHubService } = await import('@codra/provider-github'); - const { ModelRunner } = await import('@codra/models'); - const { bulkRecordRetryableFileReviewFailures } = await import('@codra/db/file-reviews'); + const { GitHubService } = await import('@codraoss/provider-github'); + const { ModelRunner } = await import('@codraoss/models'); + const { bulkRecordRetryableFileReviewFailures } = await import('@codraoss/db/file-reviews'); vi.spyOn(GitHubService.prototype, 'getPullRequestDiff').mockResolvedValue(generateMockDiff(smallFiles)); const job = await seedJob(env, uniqueRepo('batch-deescalate')); diff --git a/test/review/chunk-concurrency.spec.ts b/test/review/chunk-concurrency.spec.ts index 66b196e1..4f1ed005 100644 --- a/test/review/chunk-concurrency.spec.ts +++ b/test/review/chunk-concurrency.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { budgetAwareFileLimit, estimatedSubrequestsPerFile } from '@server/core/review'; import { TokenTracker } from '@server/core/token-tracker'; -import { REVIEW_CONCURRENCY_LIMITS, reviewConcurrencyLevels } from '@codra/schema'; +import { REVIEW_CONCURRENCY_LIMITS, reviewConcurrencyLevels } from '@codraoss/schema'; // Regression guard for "concurrency slider is dead above medium": the per-chunk budget cap must // NOT silently override the configured concurrency at a healthy budget. Exercises the REAL diff --git a/test/review/comments.spec.ts b/test/review/comments.spec.ts index 9b696119..fb4cba2d 100644 --- a/test/review/comments.spec.ts +++ b/test/review/comments.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi } from 'vitest'; -import { GitHubClient } from '@codra/provider-github'; -import type { ReviewComment } from '@codra/core/ports'; +import { GitHubClient } from '@codraoss/provider-github'; +import type { ReviewComment } from '@codraoss/core/ports'; // Regression: inline comments silently stopped reaching GitHub because `createReview` kept only // comments carrying a legacy diff `position` -- a value nothing in the pipeline computes anymore diff --git a/test/review/flow-chunking.spec.ts b/test/review/flow-chunking.spec.ts index 87017d05..cee11fe3 100644 --- a/test/review/flow-chunking.spec.ts +++ b/test/review/flow-chunking.spec.ts @@ -1,17 +1,17 @@ import { runReviewJob } from '@server/core/review'; import { createTestEnv, dbDescribe, generateMockDiff, sha, uniqueRepo } from '../helpers'; import { afterAll, vi } from 'vitest'; -import { getJobForProcessing, insertJob, updateJobFileCount, updateJobStep } from '@codra/db/jobs'; -import { getFileReviewsForJobs, upsertFileReview } from '@codra/db/file-reviews'; -import { defaultRepoConfig } from '@codra/schema'; -import { runWithDb } from '@codra/db/client'; +import { getJobForProcessing, insertJob, updateJobFileCount, updateJobStep } from '@codraoss/db/jobs'; +import { getFileReviewsForJobs, upsertFileReview } from '@codraoss/db/file-reviews'; +import { defaultRepoConfig } from '@codraoss/schema'; +import { runWithDb } from '@codraoss/db/client'; import { REVIEW_FLOW_TIMEOUT_MS } from '../mocks/review-harness'; const { getOtherRunningJobsCountMock } = vi.hoisted(() => ({ getOtherRunningJobsCountMock: vi.fn().mockResolvedValue(0), })); -vi.mock('@codra/db/jobs', async (importOriginal) => { +vi.mock('@codraoss/db/jobs', async (importOriginal) => { const mod = await importOriginal>(); return { ...mod, getOtherRunningJobsCount: getOtherRunningJobsCountMock }; }); @@ -20,26 +20,26 @@ vi.mock('@codra/db/jobs', async (importOriginal) => { // parallel. This suite only needs some fixed concurrency, so pin the schema default. const { getReviewSettingsMock } = vi.hoisted(() => ({ getReviewSettingsMock: vi.fn() })); -vi.mock('@codra/db/app-settings', async (importOriginal) => { +vi.mock('@codraoss/db/app-settings', async (importOriginal) => { const mod = await importOriginal>(); - const { reviewSettingsSchema } = await import('@codra/schema'); + const { reviewSettingsSchema } = await import('@codraoss/schema'); getReviewSettingsMock.mockResolvedValue(reviewSettingsSchema.parse({})); return { ...mod, getReviewSettings: getReviewSettingsMock }; }); -vi.mock('@codra/provider-github', async (importOriginal) => { +vi.mock('@codraoss/provider-github', async (importOriginal) => { const mod = await importOriginal>(); const { makeGitHubServiceMock } = await import('../mocks/services'); return { ...mod, GitHubService: makeGitHubServiceMock() }; }); -vi.mock('@codra/models', async () => { +vi.mock('@codraoss/models', async () => { const { makeModelServiceMock, isRetryableModelErrorMock, nextChainIndexOfMock } = await import('../mocks/services'); return { ModelRunner: makeModelServiceMock(), isRetryableModelError: isRetryableModelErrorMock, nextChainIndexOf: nextChainIndexOfMock }; }); dbDescribe('Review flow: chunking, partial reviews and re-posting', () => { - // Tripwire: if a refactor rewires runReviewJob past the @codra/db/jobs barrel, the mock stops + // Tripwire: if a refactor rewires runReviewJob past the @codraoss/db/jobs barrel, the mock stops // applying and every test here still passes while asserting nothing. afterAll(() => { expect(getOtherRunningJobsCountMock).toHaveBeenCalled(); @@ -55,8 +55,8 @@ dbDescribe('Review flow: chunking, partial reviews and re-posting', () => { }; it('reviews files in a chunk concurrently', async () => { - const { GitHubService } = await import('@codra/provider-github'); - const { ModelRunner } = await import('@codra/models'); + const { GitHubService } = await import('@codraoss/provider-github'); + const { ModelRunner } = await import('@codraoss/models'); const repo = uniqueRepo('concurrent'); const headSha = sha('8'); const baseSha = sha('9'); @@ -130,8 +130,8 @@ dbDescribe('Review flow: chunking, partial reviews and re-posting', () => { }, REVIEW_FLOW_TIMEOUT_MS); it('marks completed jobs with skipped files as partial reviews', async () => { - const { GitHubService } = await import('@codra/provider-github'); - const { ModelRunner } = await import('@codra/models'); + const { GitHubService } = await import('@codraoss/provider-github'); + const { ModelRunner } = await import('@codraoss/models'); const repo = uniqueRepo('partial'); const headSha = sha('e'); const baseSha = sha('f'); @@ -216,7 +216,7 @@ dbDescribe('Review flow: chunking, partial reviews and re-posting', () => { }, REVIEW_FLOW_TIMEOUT_MS); it('reuses an already-posted review instead of double-posting when finalize re-runs past the posting stage', async () => { - const { GitHubService } = await import('@codra/provider-github'); + const { GitHubService } = await import('@codraoss/provider-github'); const repo = uniqueRepo('doublepost'); const getDiffSpy = vi.spyOn(GitHubService.prototype, 'getPullRequestDiff').mockResolvedValue( generateMockDiff([{ path: 'src/app.ts', content: 'console.log(1);' }]), diff --git a/test/review/flow-lifecycle.spec.ts b/test/review/flow-lifecycle.spec.ts index e7ede928..568f7c16 100644 --- a/test/review/flow-lifecycle.spec.ts +++ b/test/review/flow-lifecycle.spec.ts @@ -1,18 +1,18 @@ import { runReviewJob } from '@server/core/review'; import { createTestEnv, dbDescribe, generateMockDiff, sha, uniqueRepo } from '../helpers'; import { afterAll, vi } from 'vitest'; -import { findExistingJobForHead, getJobForProcessing, insertJob } from '@codra/db/jobs'; -import { getFileReviewsForJobs } from '@codra/db/file-reviews'; -import { defaultRepoConfig } from '@codra/schema'; -import { runWithDb, queryRows } from '@codra/db/client'; -import { normalizeGitHubWebhook } from '@codra/provider-github'; +import { findExistingJobForHead, getJobForProcessing, insertJob } from '@codraoss/db/jobs'; +import { getFileReviewsForJobs } from '@codraoss/db/file-reviews'; +import { defaultRepoConfig } from '@codraoss/schema'; +import { runWithDb, queryRows } from '@codraoss/db/client'; +import { normalizeGitHubWebhook } from '@codraoss/provider-github'; import { makeRunAndDrain, REVIEW_FLOW_TIMEOUT_MS } from '../mocks/review-harness'; const { getOtherRunningJobsCountMock } = vi.hoisted(() => ({ getOtherRunningJobsCountMock: vi.fn().mockResolvedValue(0), })); -vi.mock('@codra/db/jobs', async (importOriginal) => { +vi.mock('@codraoss/db/jobs', async (importOriginal) => { const mod = await importOriginal>(); return { ...mod, getOtherRunningJobsCount: getOtherRunningJobsCountMock }; }); @@ -22,20 +22,20 @@ vi.mock('@codra/db/jobs', async (importOriginal) => { // fixed concurrency, so pin the schema default; the suites that test the table take a lock. const { getReviewSettingsMock } = vi.hoisted(() => ({ getReviewSettingsMock: vi.fn() })); -vi.mock('@codra/db/app-settings', async (importOriginal) => { +vi.mock('@codraoss/db/app-settings', async (importOriginal) => { const mod = await importOriginal>(); - const { reviewSettingsSchema } = await import('@codra/schema'); + const { reviewSettingsSchema } = await import('@codraoss/schema'); getReviewSettingsMock.mockResolvedValue(reviewSettingsSchema.parse({})); return { ...mod, getReviewSettings: getReviewSettingsMock }; }); -vi.mock('@codra/provider-github', async (importOriginal) => { +vi.mock('@codraoss/provider-github', async (importOriginal) => { const mod = await importOriginal>(); const { makeGitHubServiceMock } = await import('../mocks/services'); return { ...mod, GitHubService: makeGitHubServiceMock() }; }); -vi.mock('@codra/models', async () => { +vi.mock('@codraoss/models', async () => { const { makeModelServiceMock, isRetryableModelErrorMock, nextChainIndexOfMock } = await import('../mocks/services'); return { ModelRunner: makeModelServiceMock(), isRetryableModelError: isRetryableModelErrorMock, nextChainIndexOf: nextChainIndexOfMock }; }); @@ -59,7 +59,7 @@ async function needsCheckRunCompletion(env: Parameters[0], job dbDescribe('Review flow: lifecycle and finalize', () => { // Tripwire: if a refactor rewires runReviewJob to import getOtherRunningJobsCount from a - // sibling rather than the @codra/db/jobs barrel, the mock stops applying and every test here + // sibling rather than the @codraoss/db/jobs barrel, the mock stops applying and every test here // still passes while asserting nothing. afterAll(() => { expect(getOtherRunningJobsCountMock).toHaveBeenCalled(); @@ -102,7 +102,7 @@ dbDescribe('Review flow: lifecycle and finalize', () => { }, REVIEW_FLOW_TIMEOUT_MS); it('stops processing if the job is superseded mid-way', async () => { - const { GitHubService } = await import('@codra/provider-github'); + const { GitHubService } = await import('@codraoss/provider-github'); const repo = uniqueRepo('supersede'); const headSha = sha('c'); const baseSha = sha('d'); @@ -110,7 +110,7 @@ dbDescribe('Review flow: lifecycle and finalize', () => { const getDiffSpy = vi.spyOn(GitHubService.prototype, 'getPullRequestDiff'); getDiffSpy.mockImplementationOnce(async () => { - const { getDb } = await import('@codra/db/client'); + const { getDb } = await import('@codraoss/db/client'); const sql = getDb(env); await sql.query( ` @@ -156,7 +156,7 @@ dbDescribe('Review flow: lifecycle and finalize', () => { }, REVIEW_FLOW_TIMEOUT_MS); it('throttles a new (queued) job at the concurrency limit but never a running continuation', async () => { - const jobsMod = await import('@codra/db/jobs'); + const jobsMod = await import('@codraoss/db/jobs'); const repo = uniqueRepo('admission'); const baseSha = sha('0'); const base = { @@ -189,7 +189,7 @@ dbDescribe('Review flow: lifecycle and finalize', () => { }, REVIEW_FLOW_TIMEOUT_MS); it('bulk-marks missing files failed in a single pass without clobbering existing rows', async () => { - const { bulkMarkFilesFailed } = await import('@codra/db/file-reviews'); + const { bulkMarkFilesFailed } = await import('@codraoss/db/file-reviews'); const job = await insertJob(env, { installationId: '123', owner: 'test-owner', repo: uniqueRepo('bulk-failed'), prNumber: 40, prTitle: 'Bulk failed', prAuthor: 'author', commitSha: sha('e'), baseSha: sha('0'), @@ -218,7 +218,7 @@ dbDescribe('Review flow: lifecycle and finalize', () => { it('completes the job with the review recorded even if post-review check-run/label updates fail', async () => { // Regression: the review is posted mid-finalize, so if the cosmetic check-run or label calls // throw, the job must still finish 'done' with review_id set rather than stranded 'failed'. - const { GitHubService } = await import('@codra/provider-github'); + const { GitHubService } = await import('@codraoss/provider-github'); const checkRunSpy = vi.spyOn(GitHubService.prototype, 'updateCheckRun' as any) .mockRejectedValue(new Error('Too many subrequests by single Worker invocation')); diff --git a/test/review/flow-retry.spec.ts b/test/review/flow-retry.spec.ts index 2da2744e..6c2718cb 100644 --- a/test/review/flow-retry.spec.ts +++ b/test/review/flow-retry.spec.ts @@ -1,18 +1,18 @@ import { runReviewJob } from '@server/core/review'; import { createTestEnv, dbDescribe, sha, uniqueRepo } from '../helpers'; import { afterAll, vi } from 'vitest'; -import { getJobForProcessing, insertJob, updateJobFileCount, updateJobStep } from '@codra/db/jobs'; -import { getFileReviewsForJobs, upsertFileReview } from '@codra/db/file-reviews'; -import { defaultRepoConfig, type ParsedReviewComment } from '@codra/schema'; -import { runWithDb } from '@codra/db/client'; -import { normalizeGitHubWebhook } from '@codra/provider-github'; +import { getJobForProcessing, insertJob, updateJobFileCount, updateJobStep } from '@codraoss/db/jobs'; +import { getFileReviewsForJobs, upsertFileReview } from '@codraoss/db/file-reviews'; +import { defaultRepoConfig, type ParsedReviewComment } from '@codraoss/schema'; +import { runWithDb } from '@codraoss/db/client'; +import { normalizeGitHubWebhook } from '@codraoss/provider-github'; import { makeRunAndDrain, REVIEW_FLOW_TIMEOUT_MS } from '../mocks/review-harness'; const { getOtherRunningJobsCountMock } = vi.hoisted(() => ({ getOtherRunningJobsCountMock: vi.fn().mockResolvedValue(0), })); -vi.mock('@codra/db/jobs', async (importOriginal) => { +vi.mock('@codraoss/db/jobs', async (importOriginal) => { const mod = await importOriginal>(); return { ...mod, getOtherRunningJobsCount: getOtherRunningJobsCountMock }; }); @@ -21,26 +21,26 @@ vi.mock('@codra/db/jobs', async (importOriginal) => { // parallel. This suite only needs some fixed concurrency, so pin the schema default. const { getReviewSettingsMock } = vi.hoisted(() => ({ getReviewSettingsMock: vi.fn() })); -vi.mock('@codra/db/app-settings', async (importOriginal) => { +vi.mock('@codraoss/db/app-settings', async (importOriginal) => { const mod = await importOriginal>(); - const { reviewSettingsSchema } = await import('@codra/schema'); + const { reviewSettingsSchema } = await import('@codraoss/schema'); getReviewSettingsMock.mockResolvedValue(reviewSettingsSchema.parse({})); return { ...mod, getReviewSettings: getReviewSettingsMock }; }); -vi.mock('@codra/provider-github', async (importOriginal) => { +vi.mock('@codraoss/provider-github', async (importOriginal) => { const mod = await importOriginal>(); const { makeGitHubServiceMock } = await import('../mocks/services'); return { ...mod, GitHubService: makeGitHubServiceMock() }; }); -vi.mock('@codra/models', async () => { +vi.mock('@codraoss/models', async () => { const { makeModelServiceMock, isRetryableModelErrorMock, nextChainIndexOfMock } = await import('../mocks/services'); return { ModelRunner: makeModelServiceMock(), isRetryableModelError: isRetryableModelErrorMock, nextChainIndexOf: nextChainIndexOfMock }; }); dbDescribe('Review flow: retries, inheritance and continuations', () => { - // Tripwire: if a refactor rewires runReviewJob past the @codra/db/jobs barrel, the mock stops + // Tripwire: if a refactor rewires runReviewJob past the @codraoss/db/jobs barrel, the mock stops // applying and every test here still passes while asserting nothing. afterAll(() => { expect(getOtherRunningJobsCountMock).toHaveBeenCalled(); @@ -97,7 +97,7 @@ dbDescribe('Review flow: retries, inheritance and continuations', () => { }, REVIEW_FLOW_TIMEOUT_MS); it('does not inherit parent file reviews from models outside the current retry strategy', async () => { - const { ModelRunner } = await import('@codra/models'); + const { ModelRunner } = await import('@codraoss/models'); const reviewSpy = vi.spyOn(ModelRunner.prototype, 'reviewFile'); const repo = uniqueRepo('retry-model-filter'); const sourceHeadSha = sha('8'); @@ -181,7 +181,7 @@ dbDescribe('Review flow: retries, inheritance and continuations', () => { // Regression: file reviews persist the bare model id (e.g. `gemini-3.1-flash-lite`) while the // configured strategy stores the provider-qualified id (e.g. `google:gemini-3.1-flash-lite`). // Inheritance must match on the bare name; otherwise every retry re-reviews every file. - const { ModelRunner } = await import('@codra/models'); + const { ModelRunner } = await import('@codraoss/models'); const reviewSpy = vi.spyOn(ModelRunner.prototype, 'reviewFile'); const repo = uniqueRepo('retry-prefix'); const sourceHeadSha = sha('a'); @@ -312,7 +312,7 @@ dbDescribe('Review flow: retries, inheritance and continuations', () => { }, REVIEW_FLOW_TIMEOUT_MS); it('schedules a delayed continuation instead of spending queue retries on transient model failures', async () => { - const { ModelRunner } = await import('@codra/models'); + const { ModelRunner } = await import('@codraoss/models'); const retryableError = Object.assign(new Error('Google API timed out after 45000ms'), { retryable: true }); const reviewSpy = vi.spyOn(ModelRunner.prototype, 'reviewFile').mockRejectedValue(retryableError); const repo = uniqueRepo('transient'); diff --git a/test/review/pipeline-regression.spec.ts b/test/review/pipeline-regression.spec.ts index e4063e37..13930614 100644 --- a/test/review/pipeline-regression.spec.ts +++ b/test/review/pipeline-regression.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { parseFileReviewResponse } from '@server/core/model-output'; -import { DEFAULT_DENIED_CLAIM_TYPES } from '@codra/schema'; +import { DEFAULT_DENIED_CLAIM_TYPES } from '@codraoss/schema'; import type { FileDiff } from '@server/core/diff'; // End-to-end regression over the parse-time chain: JSON extraction, evidence grounding, the diff --git a/test/review/quota-deferral.spec.ts b/test/review/quota-deferral.spec.ts index a85885cc..f045808c 100644 --- a/test/review/quota-deferral.spec.ts +++ b/test/review/quota-deferral.spec.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { isRetryableModelError } from '@codra/models'; +import { isRetryableModelError } from '@codraoss/models'; import { createTestEnv, saveTestProviderApiKey } from '../helpers'; -import { defaultRepoConfig } from '@codra/schema'; +import { defaultRepoConfig } from '@codraoss/schema'; import { makeModelFactory } from '@server/adapters/services'; const file = { diff --git a/test/review/resumable-queue.spec.ts b/test/review/resumable-queue.spec.ts index 1c90856f..73a85f02 100644 --- a/test/review/resumable-queue.spec.ts +++ b/test/review/resumable-queue.spec.ts @@ -1,7 +1,7 @@ import worker from '../../apps/worker/src/index'; -import { claimJobLease, getJobForProcessing, insertJob, markJobContinuationQueued, recoverExpiredJobLeases, releaseJobLease } from '@codra/db/jobs'; -import { getFileReviewsForJobs, recordRetryableFileReviewFailure, upsertFileReview } from '@codra/db/file-reviews'; -import { getDb } from '@codra/db/client'; +import { claimJobLease, getJobForProcessing, insertJob, markJobContinuationQueued, recoverExpiredJobLeases, releaseJobLease } from '@codraoss/db/jobs'; +import { getFileReviewsForJobs, recordRetryableFileReviewFailure, upsertFileReview } from '@codraoss/db/file-reviews'; +import { getDb } from '@codraoss/db/client'; import { createTestEnv, dbDescribe, sha, uniqueName } from '../helpers'; diff --git a/test/review/scheduled-maintenance.spec.ts b/test/review/scheduled-maintenance.spec.ts index 86d80cf8..1eac006d 100644 --- a/test/review/scheduled-maintenance.spec.ts +++ b/test/review/scheduled-maintenance.spec.ts @@ -15,7 +15,7 @@ vi.mock('@server/core/job-recovery', async (importOriginal) => ({ runBestEffortJobMaintenance: runBestEffortJobMaintenanceMock, })); -vi.mock('@codra/db/jobs', async (importOriginal) => ({ +vi.mock('@codraoss/db/jobs', async (importOriginal) => ({ ...(await importOriginal()), hasPendingMaintenanceWork: hasPendingMaintenanceWorkMock, })); @@ -62,7 +62,7 @@ describe('scheduled() cron maintenance gating', () => { }); it('markSystemActive writes the flag only once while it is set (no per-chunk KV write storm)', async () => { - const { markSystemActive } = await import('@codra/db/jobs'); + const { markSystemActive } = await import('@codraoss/db/jobs'); const env = createTestEnv(); const putSpy = vi.spyOn(env.APP_KV, 'put'); diff --git a/test/review/subrequest-completion.spec.ts b/test/review/subrequest-completion.spec.ts index 836a6aef..b0a5260b 100644 --- a/test/review/subrequest-completion.spec.ts +++ b/test/review/subrequest-completion.spec.ts @@ -32,7 +32,7 @@ const { getPullRequestMock: vi.fn(), })); -vi.mock('@codra/db/jobs', async (importOriginal) => { +vi.mock('@codraoss/db/jobs', async (importOriginal) => { const mod = await importOriginal(); return { ...mod, @@ -49,7 +49,7 @@ vi.mock('@codra/db/jobs', async (importOriginal) => { }; }); -vi.mock('@codra/db/app-settings', async (importOriginal) => { +vi.mock('@codraoss/db/app-settings', async (importOriginal) => { const mod = await importOriginal(); return { ...mod, @@ -57,7 +57,7 @@ vi.mock('@codra/db/app-settings', async (importOriginal) => { }; }); -vi.mock('@codra/provider-github', async (importOriginal) => { +vi.mock('@codraoss/provider-github', async (importOriginal) => { const mod = await importOriginal>(); return { ...mod, diff --git a/test/review/workflow-finalize-fresh-instance.spec.ts b/test/review/workflow-finalize-fresh-instance.spec.ts index dd55abe0..4f286237 100644 --- a/test/review/workflow-finalize-fresh-instance.spec.ts +++ b/test/review/workflow-finalize-fresh-instance.spec.ts @@ -17,11 +17,11 @@ vi.mock('@server/core/review', async (importOriginal) => ({ runReviewJob: runReviewJobMock, })); vi.mock('@server/core/job-recovery', () => ({ runBestEffortJobMaintenance: maintenanceMock })); -vi.mock('@codra/db/jobs', async (importOriginal) => ({ +vi.mock('@codraoss/db/jobs', async (importOriginal) => ({ ...(await importOriginal()), setJobWorkflowInstance: setInstanceMock, })); -vi.mock('@codra/db/client', () => ({ runWithDb: (_env: any, fn: any) => fn() })); +vi.mock('@codraoss/db/client', () => ({ runWithDb: (_env: any, fn: any) => fn() })); import { ReviewWorkflow } from '../../apps/worker/src/workflows/review'; diff --git a/tsconfig.base.json b/tsconfig.base.json index 2d445738..1185103f 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -4,6 +4,7 @@ "lib": ["ES2024", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "Bundler", + "ignoreDeprecations": "6.0", "paths": { "@client/*": ["./src/client/*", "../../src/client/*"], "@server/*": ["./src/server/*", "../../src/server/*"], From ea9d6a7fb13dead6b2e00134b471940696a3cc80 Mon Sep 17 00:00:00 2001 From: Devarshi Shimpi Date: Wed, 19 Aug 2026 04:59:57 +0530 Subject: [PATCH 2/7] feat: add opt-in full-file review context and truncation-salvage fallback chain Adds `full_file_context` config to feed a validated post-change file into review prompts, plus `review_breadth` to decouple pipeline findings from the posted comment cap. Also adds truncation salvage and per-rung timeout budgeting to the model fallback chain. --- packages/api/src/routes/api/jobs.ts | 15 +- packages/core/src/constants.ts | 6 + packages/core/src/finding-gates.ts | 7 +- packages/core/src/ports/git-provider.ts | 2 + packages/core/src/ports/model.ts | 8 +- packages/core/src/prompts/file-review.ts | 152 ++++++++++-- packages/core/src/prompts/languages.ts | 8 + packages/core/src/review/bin-runner.ts | 2 + packages/core/src/review/budget.ts | 9 +- packages/core/src/review/file-context.ts | 55 +++++ packages/core/src/review/file-runner.ts | 4 + packages/core/src/review/gate-pipeline.ts | 3 +- packages/core/src/review/phase.ts | 24 +- .../models/src/internal/model-chain-runner.ts | 2 + .../models/src/internal/model-review-batch.ts | 6 +- .../models/src/internal/model-review-chain.ts | 41 +++- .../models/src/internal/model-review-file.ts | 22 +- packages/models/src/limits.ts | 20 +- packages/models/src/providers/google.ts | 67 +++-- packages/models/src/providers/vertex.ts | 148 +++++++---- packages/models/src/types.ts | 27 ++ .../models/test/model/chain-salvage.spec.ts | 231 ++++++++++++++++++ .../test/model/service-requests.spec.ts | 2 +- packages/models/test/model/truncation.spec.ts | 173 +++++++++++++ packages/models/test/model/vertex.spec.ts | 192 +++++++++++++++ packages/provider-github/src/client.ts | 17 +- packages/provider-github/src/service.ts | 4 + packages/schema/src/schema-repo-config.ts | 8 + test/api/repos.spec.ts | 25 +- test/findings/prompts-batch-review.spec.ts | 5 +- test/findings/prompts-file-context.spec.ts | 136 +++++++++++ test/findings/prompts-file-review.spec.ts | 18 +- test/findings/prompts-intent.spec.ts | 129 ++++++++++ test/review/chunk-concurrency.spec.ts | 8 + 34 files changed, 1447 insertions(+), 129 deletions(-) create mode 100644 packages/core/src/review/file-context.ts create mode 100644 packages/models/test/model/chain-salvage.spec.ts create mode 100644 packages/models/test/model/truncation.spec.ts create mode 100644 packages/models/test/model/vertex.spec.ts create mode 100644 test/findings/prompts-file-context.spec.ts create mode 100644 test/findings/prompts-intent.spec.ts diff --git a/packages/api/src/routes/api/jobs.ts b/packages/api/src/routes/api/jobs.ts index 96c850f7..221a7dd4 100644 --- a/packages/api/src/routes/api/jobs.ts +++ b/packages/api/src/routes/api/jobs.ts @@ -3,7 +3,7 @@ import type { Context } from 'hono'; import { defaultRepoConfig, findingLabelSchema, jobsQuerySchema } from '@codraoss/schema'; import { jsonError } from '../../http'; import { parseUnifiedDiff } from '@codraoss/core/diff'; -import { buildFileReviewPrompts } from '@codraoss/core/prompts/file-review'; +import { buildFileReviewPrompts, changelogExcerptFromDiff, wantsFileContext } from '@codraoss/core/prompts/file-review'; import type { ApiEnv } from '../../ports'; // Best-effort terminate; .get() throws if the instance is gone and .terminate() if already terminal, both non-fatal. @@ -102,14 +102,21 @@ export function createJobsRouter() { // The ENTIRE PR diff, not just files with a review row, so Files-changed matches GitHub mid-review. const diffs: Record = {}; - for (const file of parseUnifiedDiff(rawDiff, config.review)) { + const parsedFiles = parseUnifiedDiff(rawDiff, config.review); + const changelogExcerpt = changelogExcerptFromDiff(parsedFiles); + for (const file of parsedFiles) { if (file.isDeleted || file.isBinary || !file.path) continue; - diffs[file.path] = buildFileReviewPrompts({ + const { userPrompt } = buildFileReviewPrompts({ file, prTitle: job.prTitle, prDescription, + changelogExcerpt, config: config.review, - }).userPrompt; + }); + const hadContext = wantsFileContext(file, config.review.full_file_context); + diffs[file.path] = hadContext + ? `${userPrompt}\n\n[The full file at the reviewed commit was included here at review time; it is omitted from this preview.]` + : userPrompt; } const response = c.json({ diffs }); diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index c3d9456f..b74976c1 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -37,3 +37,9 @@ export const MAX_RULE_SCAN_ADDED_LINES = 600; // Prompts export const EXEMPLAR_BLOCK_CHARS = 700; export const PR_DESCRIPTION_CHARS = 2_000; +// Changelog entries are short; keep well under the description budget. +export const CHANGELOG_EXCERPT_CHARS = 600; +// Whole-file context budget when full_file_context is on; half the bin diff budget. +export const FILE_CONTEXT_CHAR_BUDGET = 8_000; +// Per-chunk window, not a repeated whole-file block, to stay within the budget above. +export const FILE_CONTEXT_WINDOW_LINES = 120; diff --git a/packages/core/src/finding-gates.ts b/packages/core/src/finding-gates.ts index cae87bd9..99f82c80 100644 --- a/packages/core/src/finding-gates.ts +++ b/packages/core/src/finding-gates.ts @@ -3,6 +3,7 @@ import type { FileDiff } from './diff'; import type { ReviewModel } from './ports'; import { renderDiffSnippet, parseVerifyResponse, type VerifyCandidate } from './prompts/verify'; import { logger } from './logger'; +import { reviewBreadth } from './prompts/file-review'; type VerifiableJob = { id: string }; @@ -24,8 +25,8 @@ export function shadowEvaluate(candidates: ParsedReviewComment[], posted: Parsed }; } -function verifyCandidateLimit(effectiveMaxComments: number) { - return Math.min(40, Math.max(10, effectiveMaxComments * 3)); +function verifyCandidateLimit(breadth: number) { + return Math.min(40, Math.max(10, breadth * 3)); } import { VERIFY_MIN_ANSWER_RATIO } from './constants'; @@ -56,7 +57,7 @@ export async function verifyFindings(params: { if (comments.length === 0) return keepAll(); - const limit = verifyCandidateLimit(params.maxCandidates ?? config.review.max_comments); + const limit = verifyCandidateLimit(params.maxCandidates ?? reviewBreadth(config.review)); const toVerify = comments.slice(0, limit); const fileByPath = new Map(files.map((file) => [file.path, file])); diff --git a/packages/core/src/ports/git-provider.ts b/packages/core/src/ports/git-provider.ts index 457df8bd..2ece3464 100644 --- a/packages/core/src/ports/git-provider.ts +++ b/packages/core/src/ports/git-provider.ts @@ -23,6 +23,8 @@ export interface ReviewGitProvider { getPullRequest(owner: string, repo: string, prNumber: number): Promise; getPullRequestDiff(owner: string, repo: string, prNumber: number): Promise; getCompareDiff(owner: string, repo: string, base: string, head: string): Promise; + /** File content at `ref`, or null if unavailable. Optional: backs opt-in file-context enrichment. */ + getRepoFile?(owner: string, repo: string, path: string, ref?: string): Promise; createCheckRun(owner: string, repo: string, params: { headSha: string; title: string; summary: string }): Promise<{ id: number }>; updateCheckRun(owner: string, repo: string, checkRunId: number, params: { title: string; diff --git a/packages/core/src/ports/model.ts b/packages/core/src/ports/model.ts index 8f3dd06c..ef41ba0a 100644 --- a/packages/core/src/ports/model.ts +++ b/packages/core/src/ports/model.ts @@ -12,7 +12,8 @@ export type ModelResponse = { outputTokens: number; modelUsed: string; provider: string; - degraded?: 'schema-dropped'; + // schema-dropped: model refused the grammar. truncated: parsed prefix of a cut-off answer, may be incomplete. + degraded?: 'schema-dropped' | 'truncated'; }; export type ModelResponseSchema = { @@ -30,8 +31,10 @@ export type FileReviewOutcome = ModelResponse & { export interface ReviewModel { reviewFile(params: { file: FileDiff; + fileContext?: string | null; prTitle: string | null; prDescription: string | null; + changelogExcerpt?: string | null; config: RepoConfig; totalLineCount: number; compactPrompt?: boolean; @@ -42,6 +45,7 @@ export interface ReviewModel { files: readonly FileDiff[]; prTitle: string | null; prDescription: string | null; + changelogExcerpt?: string | null; config: RepoConfig; totalLineCount: number; rejectedExemplars?: readonly RejectedExemplar[]; @@ -49,8 +53,10 @@ export interface ReviewModel { submitReviewBatch(params: { file: FileDiff; + fileContext?: string | null; prTitle: string | null; prDescription: string | null; + changelogExcerpt?: string | null; config: RepoConfig; totalLineCount: number; compactPrompt?: boolean; diff --git a/packages/core/src/prompts/file-review.ts b/packages/core/src/prompts/file-review.ts index fcef81a6..f95ac32e 100644 --- a/packages/core/src/prompts/file-review.ts +++ b/packages/core/src/prompts/file-review.ts @@ -1,16 +1,26 @@ import { claimTypes, type RepoConfig } from '@codraoss/schema'; import type { FileDiff } from '../diff'; import type { ModelResponseSchema } from '../ports/model'; -import { getLanguageForFile } from './languages'; +import { getLanguageForFile, isChangelogPath } from './languages'; import { + CHANGELOG_EXCERPT_CHARS, + FILE_CONTEXT_CHAR_BUDGET, + FILE_CONTEXT_WINDOW_LINES, + PACKABLE_MAX_DIFF_LINES, EXEMPLAR_BLOCK_CHARS, PR_DESCRIPTION_CHARS, } from '../constants'; +// Pre-review_breadth fallback: generator was allowed ~2x the posted cap. export function generatorFindingCap(maxComments: number): number { return Math.max(1, maxComments * 2); } +/** Internal candidate cap upstream of posting; falls back for job snapshots queued before this field existed. */ +export function reviewBreadth(config: Pick & { review_breadth?: number }): number { + return config.review_breadth ?? generatorFindingCap(config.max_comments); +} + function findingItemSchema() { return { type: 'object', @@ -48,7 +58,7 @@ function findingItemSchema() { }; } -export function buildReviewResponseSchema(maxComments: number): ModelResponseSchema { +export function buildReviewResponseSchema(findingCap: number): ModelResponseSchema { return { name: 'codra_file_review', schema: { @@ -58,7 +68,7 @@ export function buildReviewResponseSchema(maxComments: number): ModelResponseSch properties: { findings: { type: 'array', - maxItems: generatorFindingCap(maxComments), + maxItems: Math.max(1, findingCap), items: findingItemSchema(), }, overall_explanation: { type: 'string' }, @@ -69,7 +79,7 @@ export function buildReviewResponseSchema(maxComments: number): ModelResponseSch }; } -export function buildBatchReviewResponseSchema(maxComments: number, fileCount: number): ModelResponseSchema { +export function buildBatchReviewResponseSchema(findingCap: number, fileCount: number): ModelResponseSchema { return { name: 'codra_batch_review', schema: { @@ -144,13 +154,17 @@ const MULTI_FILE_SCHEMA_FORMAT = `{ "overall_confidence_score": number (0 to 1) }`; -export function buildFileReviewSystemPromptBase(opts?: { multiFile?: boolean }): string { +export function buildFileReviewSystemPromptBase(opts?: { multiFile?: boolean; fileContext?: boolean }): string { const multi = opts?.multiFile === true; + const singleFileScope = opts?.fileContext === true + ? '- You can see the diff below and, after it, the full content of that one file. You cannot see the rest of the repository. Findings must still be about lines the diff CHANGED; the file content is there to tell you what the surrounding code does, not to be reviewed.' + : '- You can see ONLY the diff below, not the whole file or the rest of the repository.'; + const contextScope = multi ? `- You can see ONLY the diffs below, not the whole files or the rest of the repository. - Each file below is INDEPENDENT. A finding about one file must be grounded in a line from THAT file's diff, and must be reported inside that file's entry. Never carry a claim from one file to another, and never assume two files interact unless both diffs show it.` - : '- You can see ONLY the diff below, not the whole file or the rest of the repository.'; + : singleFileScope; const evidenceSource = multi ? `the single line of code the finding is about, copied VERBATIM from that file's diff below.` @@ -211,11 +225,11 @@ export const fileReviewSystemPromptBase = buildFileReviewSystemPromptBase(); export function buildFileReviewSystemPrompt( config: RepoConfig['review'], languagePersona?: string, - opts?: { multiFile?: boolean }, + opts?: { multiFile?: boolean; fileContext?: boolean }, ) { const persona = languagePersona ? ` as ${languagePersona}` : ''; const prompt = buildFileReviewSystemPromptBase(opts) - .replace('{{MAX_COMMENTS}}', generatorFindingCap(config.max_comments).toString()); + .replace('{{MAX_COMMENTS}}', reviewBreadth(config).toString()); return `You are a world-class professional senior code reviewer${persona}. ${prompt}`; } @@ -242,12 +256,101 @@ function renderExemplars(exemplars: readonly RejectedExemplar[] | undefined): st -function renderPrContext(prDescription: string | null): string | null { - const trimmed = prDescription?.trim(); - if (!trimmed) return null; - return `PR description (author intent - use to judge whether a change is deliberate):\n${trimmed.slice(0, PR_DESCRIPTION_CHARS)}${trimmed.length > PR_DESCRIPTION_CHARS ? '…' : ''}`; +function clip(text: string, limit: number): string { + return text.length > limit ? `${text.slice(0, limit)}…` : text; +} + +// Must avoid the `===== FILE ` delimiter and lines starting `Language: `: the batch prompt splits on those. +function renderIntentBlock(input: { + prTitle: string | null; + prDescription: string | null; + changelogExcerpt?: string | null; +}): string { + const description = input.prDescription?.trim(); + const changelog = input.changelogExcerpt?.trim(); + + return [ + '## PR INTENT (what the author set out to do)', + `Title: ${input.prTitle ?? 'Untitled PR'}`, + ...(description ? ['Description:', clip(description, PR_DESCRIPTION_CHARS)] : []), + ...(changelog ? ['Changelog lines added by this PR:', clip(changelog, CHANGELOG_EXCERPT_CHARS)] : []), + 'Behaviour that serves this stated intent is deliberate. Do not report it as an accident, an oversight, or a regression.', + ].join('\n'); +} + +export function changelogExcerptFromDiff(files: readonly FileDiff[]): string | null { + const added: string[] = []; + let used = 0; + + for (const file of files) { + if (!isChangelogPath(file.path)) continue; + for (const hunk of file.hunks) { + for (const line of hunk.lines) { + if (line.kind !== 'add') continue; + const text = line.content.trim(); + if (!text) continue; + if (used + text.length > CHANGELOG_EXCERPT_CHARS) { + return added.length > 0 ? added.join('\n') : null; + } + added.push(text); + used += text.length + 1; + } + } + } + + return added.length > 0 ? added.join('\n') : null; +} + +export function wantsFileContext( + file: Pick, + fullFileContext: boolean, + gate: { compactPrompt?: boolean } = {}, +): boolean { + if (!fullFileContext) return false; + if (gate.compactPrompt) return false; + if (file.isNew || file.isDeleted || file.isBinary) return false; + return file.lineCount > PACKABLE_MAX_DIFF_LINES; +} + +// Windowed per chunk's own hunks so a chunked file doesn't repeat the whole block MAX_CHUNKS times. +function renderFileContext(file: FileDiff, content: string): string | null { + const lines = content.split('\n'); + + let lowest = Number.POSITIVE_INFINITY; + let highest = 0; + for (const hunk of file.hunks) { + for (const line of hunk.lines) { + if (typeof line.newLineNumber !== 'number') continue; + lowest = Math.min(lowest, line.newLineNumber); + highest = Math.max(highest, line.newLineNumber); + } + } + if (!Number.isFinite(lowest)) return null; + + const start = Math.max(1, lowest - FILE_CONTEXT_WINDOW_LINES); + const end = Math.min(lines.length, highest + FILE_CONTEXT_WINDOW_LINES); + + const numbered: string[] = []; + let used = 0; + for (let n = start; n <= end; n++) { + const rendered = `${n}\t${lines[n - 1] ?? ''}`; + if (used + rendered.length > FILE_CONTEXT_CHAR_BUDGET) break; + numbered.push(rendered); + used += rendered.length + 1; + } + if (numbered.length === 0) return null; + + const last = start + numbered.length - 1; + const partial = start > 1 || last < lines.length; + return [ + `Full file after the change, lines ${start}-${last}${partial ? ` of ${lines.length}` : ''} (CONTEXT ONLY, not reviewable):`, + ...numbered, + ].join('\n'); } +const INTENT_CHECK_INSTRUCTION = + 'Intent check: every finding must survive a comparison with the PR INTENT above. If what you are about to flag IS the stated intent, it is not a finding - drop it. Otherwise open the `body` with one line saying how the problem differs from what the author set out to do.'; + function renderCustomRules(config: RepoConfig['review']): string { const rules = config.custom_rules.length > 0 ? config.custom_rules.map((rule) => `- ${rule}`).join('\n') : '- None'; return `Custom rules:\n${rules}`; @@ -263,30 +366,35 @@ function renderLanguageGuidelines(path: string): string { export function buildFileReviewPrompts(input: { file: FileDiff; + fileContext?: string | null; prTitle: string | null; prDescription: string | null; + changelogExcerpt?: string | null; config: RepoConfig['review']; rejectedExemplars?: readonly RejectedExemplar[]; }) { const languageInfo = getLanguageForFile(input.file.path); const rules = input.config.custom_rules.length > 0 ? input.config.custom_rules.map((rule) => `- ${rule}`).join('\n') : '- None'; - const systemPrompt = buildFileReviewSystemPrompt(input.config, languageInfo?.persona); - const languageGuidelines = renderLanguageGuidelines(input.file.path); + const intentBlock = renderIntentBlock(input); + const fileContext = input.fileContext ? renderFileContext(input.file, input.fileContext) : null; - const prContext = renderPrContext(input.prDescription); + const systemPrompt = buildFileReviewSystemPrompt(input.config, languageInfo?.persona, { + fileContext: fileContext !== null, + }); + const languageGuidelines = renderLanguageGuidelines(input.file.path); const exemplars = renderExemplars(input.rejectedExemplars); const userPrompt = [ - `PR title: ${input.prTitle ?? 'Untitled PR'}`, - ...(prContext ? [prContext] : []), + intentBlock, ...(exemplars ? [exemplars] : []), `File path: ${input.file.path}`, languageGuidelines, `Custom rules:\n${rules}`, 'Review ONLY the diff shown below. You cannot see the rest of the file or repository - do not report something as undefined, unimported, unused, or missing just because it is not in the diff. If the diff note says it was truncated, do not infer issues from omitted lines.', 'Line numbers: every diff line below is prefixed with two columns - the OLD file line number, then the NEW file line number. Always report `line` (and `line_range`) using the NEW (second, right-hand) number, and only ever cite a line that appears in the diff. For a removed line, cite the nearest NEW line number shown next to it.', - 'Evidence: every finding must carry an `evidence` string containing the exact code of the line it is about, copied character-for-character from the diff below. Strip the two leading line-number columns and the +/-/space marker - quote only the code itself. A finding whose evidence does not appear in the diff will be discarded.', + `Evidence: every finding must carry an \`evidence\` string containing the exact code of the line it is about, copied character-for-character from the UNIFIED DIFF below. Strip the two leading line-number columns and the +/-/space marker - quote only the code itself. A finding whose evidence does not appear in the diff will be discarded${fileContext ? ', and the full-file context below is NOT the diff -- a line quoted from it counts as no evidence at all' : ''}.`, + INTENT_CHECK_INSTRUCTION, 'Prioritize correctness, security, and production-impacting bugs. Raise anything you can ground in a quoted line; avoid subjective style feedback.', '', `## Output JSON Schema (STRICTLY REQUIRED)`, @@ -313,6 +421,7 @@ export function buildFileReviewPrompts(input: { '', 'Unified diff:', renderFileDiff(input.file), + ...(fileContext ? ['', fileContext] : []), ].join('\n'); return { systemPrompt, userPrompt }; @@ -326,6 +435,7 @@ export function buildBatchReviewPrompts(input: { files: readonly FileDiff[]; prTitle: string | null; prDescription: string | null; + changelogExcerpt?: string | null; config: RepoConfig['review']; rejectedExemplars?: readonly RejectedExemplar[]; }) { @@ -336,7 +446,7 @@ export function buildBatchReviewPrompts(input: { const systemPrompt = buildFileReviewSystemPrompt(input.config, uniformLanguage?.persona, { multiFile: true }); - const prContext = renderPrContext(input.prDescription); + const intentBlock = renderIntentBlock(input); const exemplars = renderExemplars(input.rejectedExemplars); const pathList = files.map((file) => `- ${file.path}`).join('\n'); @@ -349,8 +459,7 @@ export function buildBatchReviewPrompts(input: { ]); const userPrompt = [ - `PR title: ${input.prTitle ?? 'Untitled PR'}`, - ...(prContext ? [prContext] : []), + intentBlock, ...(exemplars ? [exemplars] : []), `You are reviewing ${files.length} files in ONE response. Return exactly ${files.length} entries in "files", one per path, in this order:\n${pathList}`, ...(uniformLanguage ? [renderLanguageGuidelines(files[0].path)] : []), @@ -359,6 +468,7 @@ export function buildBatchReviewPrompts(input: { 'File scoping: each finding belongs to exactly ONE file. Put it inside that file\'s entry, set that file\'s path in `absolute_file_path`, and quote evidence from that file\'s diff only. Never report a finding about one file inside another file\'s entry, and never quote a line from a different file.', 'Line numbers: every diff line below is prefixed with two columns - the OLD file line number, then the NEW file line number. Always report `line` (and `line_range`) using the NEW (second, right-hand) number, and only ever cite a line that appears in that file\'s diff. For a removed line, cite the nearest NEW line number shown next to it.', 'Evidence: every finding must carry an `evidence` string containing the exact code of the line it is about, copied character-for-character from its own file\'s diff below. Strip the two leading line-number columns and the +/-/space marker - quote only the code itself. A finding whose evidence does not appear in that file\'s diff will be discarded.', + INTENT_CHECK_INSTRUCTION, 'Prioritize correctness, security, and production-impacting bugs. Raise anything you can ground in a quoted line; avoid subjective style feedback.', '', `## Output JSON Schema (STRICTLY REQUIRED)`, diff --git a/packages/core/src/prompts/languages.ts b/packages/core/src/prompts/languages.ts index bb6e15d6..d4ae8ddb 100644 --- a/packages/core/src/prompts/languages.ts +++ b/packages/core/src/prompts/languages.ts @@ -86,3 +86,11 @@ export function getLanguageForFile(path: string): LanguageGuideline | undefined return matches[0]; } + +// Matched by filename convention, not extension; kept narrow to avoid false positives. +export function isChangelogPath(path: string): boolean { + const name = path.split('/').pop()?.toLowerCase() ?? ''; + const stem = name.replace(/\.(md|mdx|markdown|rst|txt)$/, ''); + return stem === 'changelog' || stem === 'changes' || stem === 'history' || stem === 'news' + || stem === 'release-notes' || stem === 'release_notes' || stem === 'releasenotes'; +} diff --git a/packages/core/src/review/bin-runner.ts b/packages/core/src/review/bin-runner.ts index 2870f739..8f877202 100644 --- a/packages/core/src/review/bin-runner.ts +++ b/packages/core/src/review/bin-runner.ts @@ -36,6 +36,7 @@ export async function reviewAndPersistBin( model: ReviewModel, resolveFailureModelProvider: () => Promise, rejectedExemplars: readonly RejectedExemplar[] = [], + changelogExcerpt: string | null = null, ): Promise { const startedAt = env.clock.now(); @@ -66,6 +67,7 @@ export async function reviewAndPersistBin( files, prTitle: pr.title ?? null, prDescription: pr.body ?? null, + changelogExcerpt, config, totalLineCount, rejectedExemplars, diff --git a/packages/core/src/review/budget.ts b/packages/core/src/review/budget.ts index f9249a05..d23e1428 100644 --- a/packages/core/src/review/budget.ts +++ b/packages/core/src/review/budget.ts @@ -5,12 +5,15 @@ export function budgetAwareFileLimit( remainingSafeBudget: number, configuredChunkFileLimit: number, modelChainLength = 1, + fetchesFileContent = false, ) { - const budgetLimit = Math.floor(remainingSafeBudget / estimatedSubrequestsPerFile(modelChainLength)); + const budgetLimit = Math.floor( + remainingSafeBudget / estimatedSubrequestsPerFile(modelChainLength, fetchesFileContent), + ); return Math.min(configuredChunkFileLimit, budgetLimit); } -export function estimatedSubrequestsPerFile(modelChainLength: number) { +export function estimatedSubrequestsPerFile(modelChainLength: number, fetchesFileContent = false) { const modelAttempts = Math.max(1, Math.min(modelChainLength, MAX_MODEL_ATTEMPTS_ESTIMATE)); - return FILE_FIXED_SUBREQUESTS + modelAttempts; + return FILE_FIXED_SUBREQUESTS + modelAttempts + (fetchesFileContent ? 1 : 0); } diff --git a/packages/core/src/review/file-context.ts b/packages/core/src/review/file-context.ts new file mode 100644 index 00000000..b189eb00 --- /dev/null +++ b/packages/core/src/review/file-context.ts @@ -0,0 +1,55 @@ +import type { FileDiff } from '../diff'; +import type { ReviewGitProvider } from '../ports'; +import { logger } from '../logger'; + +const VALIDATION_SAMPLES = 5; + +export function contentMatchesDiff(file: FileDiff, content: string): boolean { + const lines = content.split('\n'); + const samples: Array<{ newLineNumber: number; content: string }> = []; + + for (const hunk of file.hunks) { + for (const line of hunk.lines) { + if (line.kind === 'del' || typeof line.newLineNumber !== 'number') continue; + if (!line.content.trim()) continue; + samples.push({ newLineNumber: line.newLineNumber, content: line.content }); + } + } + if (samples.length === 0) return false; + + const step = Math.max(1, Math.floor(samples.length / VALIDATION_SAMPLES)); + for (let i = 0; i < samples.length; i += step) { + const sample = samples[i]; + if (lines[sample.newLineNumber - 1] !== sample.content) return false; + } + return true; +} + +export async function loadFileContext( + github: Pick, + job: { owner: string; repo: string; commitSha: string }, + file: FileDiff, + onFetch?: () => void, +): Promise { + if (!github.getRepoFile) return null; + + let content: string | null; + try { + onFetch?.(); + content = await github.getRepoFile(job.owner, job.repo, file.path, job.commitSha); + } catch (error) { + logger.warn(`Could not fetch file content for ${file.path}; reviewing the diff alone`, { + error: error instanceof Error ? error.message : String(error), + }); + return null; + } + + if (!content) return null; + if (!contentMatchesDiff(file, content)) { + logger.warn(`Fetched content for ${file.path} does not line up with the diff; reviewing the diff alone`, { + commitSha: job.commitSha, + }); + return null; + } + return content; +} diff --git a/packages/core/src/review/file-runner.ts b/packages/core/src/review/file-runner.ts index 0d8780a6..a3ed6e5b 100644 --- a/packages/core/src/review/file-runner.ts +++ b/packages/core/src/review/file-runner.ts @@ -116,6 +116,8 @@ export async function reviewAndPersistFile( resolveFailureModelProvider: () => Promise, previousReview?: { transient_error_count: number }, rejectedExemplars: readonly RejectedExemplar[] = [], + changelogExcerpt: string | null = null, + fileContext: string | null = null, ) { const startedAt = env.clock.now(); const compactPrompt = (previousReview?.transient_error_count ?? 0) > 0; @@ -125,8 +127,10 @@ export async function reviewAndPersistFile( try { const response = await model.reviewFile({ file, + fileContext, prTitle: pr.title ?? null, prDescription: pr.body ?? null, + changelogExcerpt, config, totalLineCount, compactPrompt, diff --git a/packages/core/src/review/gate-pipeline.ts b/packages/core/src/review/gate-pipeline.ts index 97976396..c9add47d 100644 --- a/packages/core/src/review/gate-pipeline.ts +++ b/packages/core/src/review/gate-pipeline.ts @@ -5,6 +5,7 @@ import type { FileDiff } from '../diff'; import type { PersistedReviewJob } from './phase-control'; import type { ReviewModel, ReviewRuntime } from '../ports'; import { loadSuppressedFingerprints } from './telemetry'; +import { reviewBreadth } from '../prompts/file-review'; export async function applyFindingGates(params: { env: Pick; @@ -79,7 +80,7 @@ export async function applyFindingGates(params: { }); const beforeVerifyList = finalComments; - const verify = await verifyFindings({ job, config, files, comments: finalComments, model, maxCandidates: effectiveMaxComments }); + const verify = await verifyFindings({ job, config, files, comments: finalComments, model, maxCandidates: reviewBreadth(config.review) }); finalComments = verify.comments; const droppedByVerification = verify.dropped.length; for (const drop of verify.dropped) recordDisposition([drop.comment], drop.disposition); diff --git a/packages/core/src/review/phase.ts b/packages/core/src/review/phase.ts index 3fb90cd3..fa8b2689 100644 --- a/packages/core/src/review/phase.ts +++ b/packages/core/src/review/phase.ts @@ -4,6 +4,8 @@ import { budgetAwareFileLimit } from './budget'; import { narrowUnit, planReviewUnits } from './pack'; import { reviewAndPersistBin } from './bin-runner'; import { getDiffFiles } from './diff-cache'; +import { changelogExcerptFromDiff, wantsFileContext } from '../prompts/file-review'; +import { loadFileContext } from './file-context'; import type { ReviewGitProvider, ReviewModel, ReviewRuntime } from '../ports'; import { TokenTracker } from '../token-tracker'; import { @@ -58,12 +60,14 @@ export async function runReviewPhase( const { concurrencyLevel, maxFiles } = await env.settings.getReviewSettings(); const { files } = await getDiffFiles(env, job, github, config, maxFiles); const totalLineCount = files.reduce((sum, file) => sum + file.lineCount, 0); + const changelogExcerpt = changelogExcerptFromDiff(files); const configuredChunkFileLimit = REVIEW_CONCURRENCY_LIMITS[concurrencyLevel]; const modelChainLength = 1 + (config.model.fallbacks?.length ?? 0); const reviewChunkFileLimit = budgetAwareFileLimit( tracker.remainingSafeBudget(), configuredChunkFileLimit, modelChainLength, + config.review.full_file_context, ); if (reviewChunkFileLimit <= 0) { throw new Error('Subrequest budget for this invocation was exhausted before starting the next review chunk.'); @@ -133,7 +137,7 @@ export async function runReviewPhase( const binFiles = unit.kind === 'bin' ? unit.files : []; binFiles.forEach((file) => binnedPaths.add(file.path)); reviewTasks.push((async () => { - const terminal = await reviewAndPersistBin(env, job, binFiles, pr, config, totalLineCount, model, resolveFailureModelProvider, rejectedExemplars); + const terminal = await reviewAndPersistBin(env, job, binFiles, pr, config, totalLineCount, model, resolveFailureModelProvider, rejectedExemplars, changelogExcerpt); terminalProgress += terminal; })()); processedThisChunk += 1; @@ -166,6 +170,16 @@ export async function runReviewPhase( } const inherited = parentReviews.get(file.path); + let fileContextPromise: Promise | null = null; + const fileContextFor = () => { + if (!wantsFileContext(file, config.review.full_file_context, { + compactPrompt: (existingReview?.transient_error_count ?? 0) > 0, + })) { + return Promise.resolve(null); + } + fileContextPromise ??= loadFileContext(github, job, file, () => tracker.incrementSubrequests(1)); + return fileContextPromise; + }; const reviewTask = async () => { if (awaitingReview) { const poll = await model.pollReviewBatch({ @@ -182,7 +196,7 @@ export async function runReviewPhase( logger.warn(`Async batch poll failed for ${file.path}; falling back to synchronous review`, { error: poll.error instanceof Error ? poll.error.message : String(poll.error), }); - await reviewAndPersistFile(env, job, file, pr, config, totalLineCount, model, resolveFailureModelProvider, existingReview, rejectedExemplars); + await reviewAndPersistFile(env, job, file, pr, config, totalLineCount, model, resolveFailureModelProvider, existingReview, rejectedExemplars, changelogExcerpt, await fileContextFor()); terminalProgress += 1; return; } @@ -194,8 +208,10 @@ export async function runReviewPhase( if (!inherited) { const submitted = await model.submitReviewBatch({ file, + fileContext: await fileContextFor(), prTitle: pr.title ?? null, prDescription: pr.body ?? null, + changelogExcerpt, config, totalLineCount, compactPrompt: (existingReview?.transient_error_count ?? 0) > 0, @@ -224,14 +240,14 @@ export async function runReviewPhase( awaitingAsync += 1; return; } - await reviewAndPersistFile(env, job, file, pr, config, totalLineCount, model, resolveFailureModelProvider, existingReview, rejectedExemplars); + await reviewAndPersistFile(env, job, file, pr, config, totalLineCount, model, resolveFailureModelProvider, existingReview, rejectedExemplars, changelogExcerpt, await fileContextFor()); terminalProgress += 1; return; } if (!canInheritParentFileReview(config, inherited)) { logger.info(`Ignoring inherited review for ${file.path}; parent model ${inherited.model_used} is not in the current model strategy`); - await reviewAndPersistFile(env, job, file, pr, config, totalLineCount, model, resolveFailureModelProvider, existingReview, rejectedExemplars); + await reviewAndPersistFile(env, job, file, pr, config, totalLineCount, model, resolveFailureModelProvider, existingReview, rejectedExemplars, changelogExcerpt, await fileContextFor()); terminalProgress += 1; } else { await env.fileReviews.upsertFileReview(job.id, { diff --git a/packages/models/src/internal/model-chain-runner.ts b/packages/models/src/internal/model-chain-runner.ts index 8d4fcb89..25f62ef2 100644 --- a/packages/models/src/internal/model-chain-runner.ts +++ b/packages/models/src/internal/model-chain-runner.ts @@ -99,6 +99,8 @@ export async function verifyFindings(ctx: ModelChainContext, params: { candidate userPrompt: buildVerifyPrompt(params.candidates), // Must be the verify grammar, not the file-review one -- that schema makes strict decoding unsatisfiable and the pass a silent no-op. responseSchema: VERIFY_RESPONSE_SCHEMA as unknown as ModelInput['responseSchema'], + // A missing verdict is not a pass; a truncated list would silently withhold findings. + truncationIntolerant: true, }; // Scale the timeout with the number of findings under review (capped inside adaptiveModelTimeoutMs). const timeoutMs = clampTimeoutToChainBudget(adaptiveModelTimeoutMs(params.candidates.length * 8)); diff --git a/packages/models/src/internal/model-review-batch.ts b/packages/models/src/internal/model-review-batch.ts index 15430167..6ea7dabc 100644 --- a/packages/models/src/internal/model-review-batch.ts +++ b/packages/models/src/internal/model-review-batch.ts @@ -1,5 +1,5 @@ import { submitCloudflareBatch, pollCloudflareBatch } from '../providers/cloudflare'; -import { buildFileReviewPrompts, buildReviewResponseSchema } from '@codraoss/core/prompts/file-review'; +import { buildFileReviewPrompts, buildReviewResponseSchema, reviewBreadth } from '@codraoss/core/prompts/file-review'; import { parseFileReviewResponse } from '@codraoss/core/model-output'; import { truncateFileDiff } from '@codraoss/core/diff'; import { logger } from '@codraoss/core/logger'; @@ -13,8 +13,10 @@ import { COMPACT_REVIEW_PROMPT_LINE_CAP, type ModelReviewContext } from './model // Returns null when unusable for the primary model, in which case the caller falls back to synchronous reviewFile. export async function submitReviewBatch(ctx: ModelReviewContext, params: { file: any; + fileContext?: string | null; prTitle: string | null; prDescription: string | null; + changelogExcerpt?: string | null; config: RepoConfig; totalLineCount: number; compactPrompt?: boolean; @@ -50,7 +52,7 @@ export async function submitReviewBatch(ctx: ModelReviewContext, params: { submitCloudflareBatch( ctx.aiBinding!, resolved.modelName, - { systemPrompt, userPrompt, responseSchema: buildReviewResponseSchema(params.config.review.max_comments) }, + { systemPrompt, userPrompt, responseSchema: buildReviewResponseSchema(reviewBreadth(params.config.review)) }, ctx.tracker, ), ); diff --git a/packages/models/src/internal/model-review-chain.ts b/packages/models/src/internal/model-review-chain.ts index dae2560e..2f6b7ce5 100644 --- a/packages/models/src/internal/model-review-chain.ts +++ b/packages/models/src/internal/model-review-chain.ts @@ -2,8 +2,8 @@ import { logger } from '@codraoss/core/logger'; import { isSubrequestBudgetMessage, isTimeoutMessage } from '@codraoss/schema/transient-errors'; import type { RepoConfig, ResolvedModelConfig } from '@codraoss/schema'; import type { CloudflareAiBinding } from '../providers/cloudflare'; -import type { ModelResponseSchema } from '../types'; -import { clampTimeoutToChainBudget, MODEL_FALLBACK_CHAIN_BUDGET_MS, SUBREQUEST_HEADROOM_FOR_MODEL_CALL } from '../limits'; +import { partialResponseOf, type ModelResponseSchema } from '../types'; +import { chainAttemptTimeoutMs, clampTimeoutToChainBudget, MODEL_MIN_VIABLE_ATTEMPT_MS, MODEL_FALLBACK_CHAIN_BUDGET_MS, SUBREQUEST_HEADROOM_FOR_MODEL_CALL } from '../limits'; import { estimatePromptTokens, isCloudflareAllocationError, @@ -42,12 +42,13 @@ export async function runModelChain(ctx: ModelReviewContext, params: { config: RepoConfig; // Needed output tokens; adapters clamp it, omission defaults to provider ceiling. outputBudgetTokens?: number; + truncationIntolerant?: boolean; // `isLastModel` allows parsers to reject weak answers and try better models, accepting them only as a last resort. parse: (rawText: string, ctx: { isLastModel: boolean }) => T; // Keys for resume memo. Bins pass member paths so progress survives mid-batch success/de-escalation. progressLabels?: readonly string[]; }) { - const { systemPrompt, userPrompt, responseSchema, label, outputBudgetTokens } = params; + const { systemPrompt, userPrompt, responseSchema, label, outputBudgetTokens, truncationIntolerant } = params; const progressLabels = params.progressLabels?.length ? params.progressLabels : [label]; const { primary, fallbacks } = ctx.selectModel({ @@ -101,8 +102,13 @@ export async function runModelChain(ctx: ModelReviewContext, params: { break; } - // Prospective ~120s limit check to avoid doomed calls and CPU faults. Defers gracefully to fresh invocations. - if (modelIndex > 0 && Date.now() - chainStartedAt - gateWaitMs + timeoutMs > MODEL_FALLBACK_CHAIN_BUDGET_MS) { + const attemptTimeoutMs = chainAttemptTimeoutMs({ + requestedMs: timeoutMs, + remainingChainMs: MODEL_FALLBACK_CHAIN_BUDGET_MS - (Date.now() - chainStartedAt - gateWaitMs), + hasAnotherModel: modelIndex < modelsToTry.length - 1, + }); + + if (modelIndex > 0 && attemptTimeoutMs === 0) { logger.warn(`Deferring ${label}: no room in the per-invocation time budget for another model`, { elapsedMs: Date.now() - chainStartedAt, gateWaitMs, @@ -171,8 +177,8 @@ export async function runModelChain(ctx: ModelReviewContext, params: { attemptedAnyModel = true; const response = await ctx.callResolvedModel( resolved, - { systemPrompt, userPrompt, responseSchema, outputBudgetTokens }, - timeoutMs, + { systemPrompt, userPrompt, responseSchema, outputBudgetTokens, truncationIntolerant }, + Math.max(attemptTimeoutMs, MODEL_MIN_VIABLE_ATTEMPT_MS), recordGateWait, ); @@ -192,6 +198,27 @@ export async function runModelChain(ctx: ModelReviewContext, params: { await ctx.chainProgress.flushPending(); return { ...response, userPrompt, parsed }; } catch (error) { + const isLastModel = startIndex + modelIndex >= wholeChain.length - 1; + // Salvage the last rung's partial answer rather than lose the file outright. + const partial = isLastModel ? partialResponseOf(error) : null; + if (partial) { + try { + const parsed = params.parse(partial.rawText, { isLastModel: true }); + logger.warn(`Salvaged a truncated response from the last model in the chain for ${label}`, { + model: partial.modelUsed, + responseChars: partial.rawText.length, + }); + if (ctx.tracker) { + ctx.tracker.record(partial.modelUsed, partial.inputTokens, partial.outputTokens); + } + await Promise.all(progressLabels.map((key) => ctx.chainProgress.clear(key))); + await ctx.chainProgress.flushPending(); + return { ...partial, userPrompt, parsed, degraded: 'truncated' as const }; + } catch { + // no-op + } + } + lastError = error; // Record failed wire transmission. ctx.tracker?.recordFailedAttempt( diff --git a/packages/models/src/internal/model-review-file.ts b/packages/models/src/internal/model-review-file.ts index 7756abd1..c0de50da 100644 --- a/packages/models/src/internal/model-review-file.ts +++ b/packages/models/src/internal/model-review-file.ts @@ -9,7 +9,7 @@ import { isNonAnswerReview, parseBatchReviewResponse, parseFileReviewResponse, t import { UnparseableModelResponseError } from '../types'; import { chunkFileDiff, type FileDiff } from '@codraoss/core/diff'; import { adaptiveModelTimeoutMs, reviewOutputBudgetTokens } from '../limits'; -import { generatorFindingCap } from '@codraoss/core/prompts/file-review'; +import { reviewBreadth } from '@codraoss/core/prompts/file-review'; import { mergeCounts } from './model-support'; import { type ModelReviewContext, runModelChain } from './model-review-chain'; import { logger } from '@codraoss/core/logger'; @@ -25,8 +25,10 @@ export type { ModelReviewContext }; export async function reviewFile(ctx: ModelReviewContext, params: { file: any; + fileContext?: string | null; prTitle: string | null; prDescription: string | null; + changelogExcerpt?: string | null; config: RepoConfig; totalLineCount: number; compactPrompt?: boolean; @@ -124,8 +126,10 @@ export async function reviewFile(ctx: ModelReviewContext, params: { // Internal to this module: reviewFile fans out to it per chunk. async function reviewFileChunk(ctx: ModelReviewContext, params: { file: any; + fileContext?: string | null; prTitle: string | null; prDescription: string | null; + changelogExcerpt?: string | null; config: RepoConfig; totalLineCount: number; compactPrompt?: boolean; @@ -140,17 +144,19 @@ async function reviewFileChunk(ctx: ModelReviewContext, params: { // One figure drives three things: the room the answer gets, and now the time it gets to write it. const outputBudgetTokens = reviewOutputBudgetTokens({ - findingCap: generatorFindingCap(params.config.review.max_comments), + findingCap: reviewBreadth(params.config.review), fileCount: 1, }); const response = await runModelChain(ctx, { systemPrompt, userPrompt, - responseSchema: buildReviewResponseSchema(params.config.review.max_comments), + responseSchema: buildReviewResponseSchema(reviewBreadth(params.config.review)), // Scales with the diff the model sees AND the answer it was asked for: small files fail over fast. timeoutMs: adaptiveModelTimeoutMs(params.file.lineCount, outputBudgetTokens), outputBudgetTokens, + // A partial review reads as "no more findings", not "ran out of room" - treat as untrusted. + truncationIntolerant: true, label: params.file.path, totalLineCount: params.totalLineCount, config: params.config, @@ -200,6 +206,7 @@ export async function reviewFiles(ctx: ModelReviewContext, params: { files: readonly FileDiff[]; prTitle: string | null; prDescription: string | null; + changelogExcerpt?: string | null; config: RepoConfig; totalLineCount: number; rejectedExemplars?: readonly RejectedExemplar[]; @@ -208,6 +215,7 @@ export async function reviewFiles(ctx: ModelReviewContext, params: { files: params.files, prTitle: params.prTitle, prDescription: params.prDescription, + changelogExcerpt: params.changelogExcerpt, config: params.config.review, rejectedExemplars: params.rejectedExemplars, }); @@ -220,16 +228,18 @@ export async function reviewFiles(ctx: ModelReviewContext, params: { // the slowest call the system makes, and its diff line count badly under-predicts that, so the same // figure sizes the timeout. const outputBudgetTokens = reviewOutputBudgetTokens({ - findingCap: generatorFindingCap(params.config.review.max_comments), + findingCap: reviewBreadth(params.config.review), fileCount: params.files.length, }); const response = await runModelChain(ctx, { systemPrompt, userPrompt, - responseSchema: buildBatchReviewResponseSchema(params.config.review.max_comments, params.files.length), + responseSchema: buildBatchReviewResponseSchema(reviewBreadth(params.config.review), params.files.length), timeoutMs: adaptiveModelTimeoutMs(binLineCount, outputBudgetTokens), outputBudgetTokens, + // Worse in a bin: a cut lands in tail entries, which then look reviewed and clean. + truncationIntolerant: true, label: `${params.files.length} files (${params.files[0]?.path ?? 'unknown'} …)`, // Per file, so progress survives the bin narrowing or exploding into singles. progressLabels: params.files.map((file) => file.path), @@ -237,7 +247,7 @@ export async function reviewFiles(ctx: ModelReviewContext, params: { config: params.config, parse: (rawText) => parseBatchReviewResponse(rawText, params.files, { deniedClaimTypes: params.config.review.deny_claim_types, - maxCommentsPerFile: params.config.review.max_comments, + maxCommentsPerFile: reviewBreadth(params.config.review), }), }); diff --git a/packages/models/src/limits.ts b/packages/models/src/limits.ts index 0bf8b144..ada26051 100644 --- a/packages/models/src/limits.ts +++ b/packages/models/src/limits.ts @@ -12,7 +12,7 @@ export const MODEL_TIMEOUT_MAX_MS = 50_000; export const MODEL_FALLBACK_CHAIN_BUDGET_MS = 55_000; // Scaled timeout buffer based on requested output tokens (1200ms per 1k tokens) to accommodate model generation time. -const MODEL_TIMEOUT_PER_1K_OUTPUT_MS = 1_200; +export const MODEL_TIMEOUT_PER_1K_OUTPUT_MS = 1_200; // Per-call timeout, scaled by diff size and expected output budget. export function adaptiveModelTimeoutMs( @@ -36,6 +36,24 @@ export function clampTimeoutToChainBudget(timeoutMs: number): number { return Math.min(timeoutMs, MODEL_FALLBACK_CHAIN_BUDGET_MS); } +export const MODEL_MIN_VIABLE_ATTEMPT_MS = 8_000; + +export const MODEL_FALLBACK_RESERVE_MS = 20_000; + +// Time to grant one rung of a fallback chain; 0 means defer to a fresh invocation. +export function chainAttemptTimeoutMs(input: { + requestedMs: number; + remainingChainMs: number; + hasAnotherModel: boolean; +}): number { + const { requestedMs, remainingChainMs, hasAnotherModel } = input; + if (remainingChainMs < MODEL_MIN_VIABLE_ATTEMPT_MS) return 0; + if (!hasAnotherModel) return Math.min(requestedMs, remainingChainMs); + + const withReserve = remainingChainMs - MODEL_FALLBACK_RESERVE_MS; + return Math.min(requestedMs, withReserve >= MODEL_MIN_VIABLE_ATTEMPT_MS ? withReserve : remainingChainMs); +} + // Max 3 calls limits connection pool (out of 6 max) to leave slots for KV/GitHub. export const MAX_CONCURRENT_MODEL_CALLS = 3; diff --git a/packages/models/src/providers/google.ts b/packages/models/src/providers/google.ts index 7fffb0d4..50a55f7d 100644 --- a/packages/models/src/providers/google.ts +++ b/packages/models/src/providers/google.ts @@ -1,10 +1,11 @@ import { logger } from '@codraoss/core/logger'; import { withTimeout } from '@codraoss/core/timeout'; -import { ProviderRequestError, UnparseableModelResponseError, providerErrorMessage, jsonOnlyPrompts, type ModelInput, type ModelResponse } from '../types'; +import { ProviderRequestError, UnparseableModelResponseError, providerErrorMessage, jsonOnlyPrompts, isThinkingRejection, attachPartialResponse, type ModelInput, type ModelResponse } from '../types'; import { toGeminiResponseJsonSchema } from '../gemini-schema'; import { assertPublicBaseUrl } from '../url-guard'; import { MODEL_TIMEOUT_MAX_MS, + MODEL_TIMEOUT_PER_1K_OUTPUT_MS, OUTPUT_TOKENS_FLOOR, geminiThinkingBudgetTokens, resolveOutputTokenCeiling, @@ -71,13 +72,6 @@ function isSchemaRejection(status: number, message: string) { ); } -// Narrow matcher probed BEFORE isSchemaRejection to prevent misidentifying thinking-config refusals as schema drops. -function isThinkingRejection(status: number, message: string) { - if (status !== 400) return false; - const lower = message.toLowerCase(); - return lower.includes('thinking') || lower.includes('thought'); -} - function isRetryableTransportError(error: unknown) { if (!(error instanceof Error)) return false; // Don't retry timeouts (caller grants up to 2m); defer to fallback chains. @@ -111,7 +105,8 @@ export async function reviewWithGoogle( GEMINI_DEFAULT_OUTPUT_TOKENS, ); const thinkingBudget = geminiThinkingBudgetTokens(answerBudget); - const outputCeiling = Math.min(GEMINI_MAX_OUTPUT_TOKENS, answerBudget + thinkingBudget); + let currentCeiling = Math.min(GEMINI_MAX_OUTPUT_TOKENS, answerBudget + thinkingBudget); + let ceilingRaised = false; // Mark error so caller latches schema-dropped state even on subsequent failure. const fail = (error: unknown): never => { if (schemaRejected && typeof error === 'object' && error !== null) { @@ -160,7 +155,7 @@ export async function reviewWithGoogle( responseMimeType: 'application/json', // See gemini-schema.ts. ...(responseJsonSchema && !schemaRejected ? { responseJsonSchema } : {}), - maxOutputTokens: outputCeiling, + maxOutputTokens: currentCeiling, // Bounded thinking budget so it doesn't consume the output ceiling. ...(thinkingRejected ? {} : { thinkingConfig: { thinkingBudget } }), // 0.9 on Gemini's 0-2 scale. @@ -257,8 +252,39 @@ export async function reviewWithGoogle( const candidate = data.candidates?.[0]; const rawText = candidate?.content?.parts?.map((part) => part.text ?? '').join('')?.trim(); + const finishReason = candidate?.finishReason; + const truncated = finishReason === 'MAX_TOKENS'; + + if (finishReason && finishReason !== 'STOP') { + logger.warn(`Gemini response for ${model} ended with finishReason=${finishReason}; output is likely incomplete`, { + // Avoid `Tokens` key name to bypass logger redaction. Sum thinking + output spend. + outputSpend: (data.usageMetadata?.candidatesTokenCount ?? 0) + (data.usageMetadata?.thoughtsTokenCount ?? 0), + thoughtSpend: data.usageMetadata?.thoughtsTokenCount ?? 0, + outputCeiling: currentCeiling, + thinkingBudget: thinkingRejected ? undefined : thinkingBudget, + schemaDropped: schemaRejected, + }); + } + + if (truncated && input.truncationIntolerant && !ceilingRaised) { + const elapsed = Date.now() - startTime; + const raisedCeiling = Math.min(GEMINI_MAX_OUTPUT_TOKENS, 2 * answerBudget + thinkingBudget); + const extraMs = ((raisedCeiling - currentCeiling) / 1_000) * MODEL_TIMEOUT_PER_1K_OUTPUT_MS; + + if (elapsed + extraMs < timeoutMs) { + ceilingRaised = true; + currentCeiling = raisedCeiling; + logger.warn(`Gemini ran out of output room on ${model}; resending once with a larger ceiling`, { + outputCeiling: raisedCeiling, + thoughtSpend: data.usageMetadata?.thoughtsTokenCount ?? 0, + hadPartialText: Boolean(rawText), + }); + attempt--; + continue; + } + } + if (!rawText) { - const finishReason = candidate?.finishReason; // Deterministic non-STOP (budget burn, safety) fails permanently; empty STOP is transient. if (finishReason && finishReason !== 'STOP') { return fail(new UnparseableModelResponseError(model, `finishReason=${finishReason}`)); @@ -266,16 +292,17 @@ export async function reviewWithGoogle( return fail(new Error('Gemini returned an empty response.')); } - // Log non-STOP prefix truncations. - if (candidate?.finishReason && candidate.finishReason !== 'STOP') { - logger.warn(`Gemini response for ${model} ended with finishReason=${candidate.finishReason}; output is likely incomplete`, { - // Avoid `Tokens` key name to bypass logger redaction. Sum thinking + output spend. - outputSpend: (data.usageMetadata?.candidatesTokenCount ?? 0) + (data.usageMetadata?.thoughtsTokenCount ?? 0), - thoughtSpend: data.usageMetadata?.thoughtsTokenCount ?? 0, - outputCeiling, - thinkingBudget: thinkingRejected ? undefined : thinkingBudget, - schemaDropped: schemaRejected, + // Still truncated after retry; fail but attach the partial text so the chain's last model can salvage it. + if (truncated && input.truncationIntolerant) { + const error = new UnparseableModelResponseError(model, 'finishReason=MAX_TOKENS'); + attachPartialResponse(error, { + rawText, + inputTokens: data.usageMetadata?.promptTokenCount ?? 0, + outputTokens: data.usageMetadata?.candidatesTokenCount ?? 0, + modelUsed: model, + provider: config.providerName ?? 'Google', }); + return fail(error); } return { diff --git a/packages/models/src/providers/vertex.ts b/packages/models/src/providers/vertex.ts index dd6a5084..3dd77bcb 100644 --- a/packages/models/src/providers/vertex.ts +++ b/packages/models/src/providers/vertex.ts @@ -1,14 +1,20 @@ import { logger } from '@codraoss/core/logger'; import { withTimeout } from '@codraoss/core/timeout'; -import { ProviderRequestError, UnparseableModelResponseError, providerErrorMessage, jsonOnlyPrompts, type ModelResponse } from '../types'; +import { ProviderRequestError, UnparseableModelResponseError, providerErrorMessage, jsonOnlyPrompts, isThinkingRejection, attachPartialResponse, type ModelResponse } from '../types'; import { assertPublicBaseUrl } from '../url-guard'; -import { MODEL_TIMEOUT_MAX_MS, OUTPUT_TOKENS_FLOOR, resolveOutputTokenCeiling } from '../limits'; +import { + MODEL_TIMEOUT_MAX_MS, + MODEL_TIMEOUT_PER_1K_OUTPUT_MS, + OUTPUT_TOKENS_FLOOR, + geminiThinkingBudgetTokens, + resolveOutputTokenCeiling, +} from '../limits'; // Vertex's REST API rejects plain API keys and requires an OAuth2 token via RFC 7523 JWT-bearer grant, so `apiKey` here holds the full service-account JSON key, not a short API key string. const VERTEX_TIMEOUT_MS = MODEL_TIMEOUT_MAX_MS; const VERTEX_DEFAULT_OUTPUT_TOKENS = OUTPUT_TOKENS_FLOOR; -// Same Gemini models as the Google adapter, so the same ceiling. No `thinkingConfig` here though: this -// adapter makes ONE attempt and has no latch, so a model that refused the field would fail the file. +// Same Gemini models as the Google adapter, so the same ceiling and thinkingConfig (unbounded reasoning +// would otherwise consume the whole ceiling and leave a truncated answer). const VERTEX_MAX_OUTPUT_TOKENS = 65_536; // Retries for a 429 only, and only while the caller's own timeout still has room. See the loop below // for why resending an unchanged request is the correct response to this particular refusal. @@ -22,6 +28,15 @@ const ACCESS_TOKEN_LIFETIME_S = 3600; // Refresh before real expiry so an in-flight review never starts a call with a token that expires mid-request. const TOKEN_REFRESH_MARGIN_MS = 60_000; +interface VertexGenerateResponse { + candidates?: Array<{ content?: { parts?: Array<{ text?: string }> }; finishReason?: string }>; + usageMetadata?: { + promptTokenCount?: number; + candidatesTokenCount?: number; + thoughtsTokenCount?: number; // billed against maxOutputTokens + }; +} + interface ServiceAccountKey { client_email: string; private_key: string; @@ -127,16 +142,19 @@ async function getAccessToken( export async function reviewWithVertex( config: { apiKey: string; baseUrl?: string | null; providerName?: string; timeoutMs?: number }, model: string, - input: { systemPrompt: string; userPrompt: string; outputBudgetTokens?: number }, + input: { systemPrompt: string; userPrompt: string; outputBudgetTokens?: number; truncationIntolerant?: boolean }, tracker?: { incrementSubrequests(count?: number): void }, ): Promise { const providerName = config.providerName ?? 'Google Vertex AI'; const timeoutMs = config.timeoutMs ?? VERTEX_TIMEOUT_MS; - const outputCeiling = resolveOutputTokenCeiling( + const answerBudget = resolveOutputTokenCeiling( input.outputBudgetTokens, VERTEX_MAX_OUTPUT_TOKENS, VERTEX_DEFAULT_OUTPUT_TOKENS, ); + const thinkingBudget = geminiThinkingBudgetTokens(answerBudget); + let currentCeiling = Math.min(VERTEX_MAX_OUTPUT_TOKENS, answerBudget + thinkingBudget); + let ceilingRaised = false; logger.info(`Calling Vertex AI model: ${model}`); assertPublicBaseUrl(config.baseUrl, providerName); @@ -159,7 +177,7 @@ export async function reviewWithVertex( } const url = `${baseUrl}/publishers/google/models/${encodeURIComponent(model)}:generateContent`; - const body = JSON.stringify({ + const buildBody = (includeThinking: boolean, ceiling: number) => JSON.stringify({ systemInstruction: { role: 'system', parts: [{ text: prompts.system }], @@ -170,13 +188,14 @@ export async function reviewWithVertex( generationConfig: { responseMimeType: 'application/json', // No `responseJsonSchema`: this adapter cannot drop a schema mid-flight, so a rejection would fail the file outright. - maxOutputTokens: outputCeiling, + maxOutputTokens: ceiling, + ...(includeThinking ? { thinkingConfig: { thinkingBudget } } : {}), // Same models as the Google adapter, so the same value keeps the two paths comparable. temperature: 0.9, }, }); - const attempt = () => + const attempt = (body: string) => withTimeout('Vertex AI', timeoutMs, (signal) => fetch(url, { method: 'POST', @@ -189,54 +208,101 @@ export async function reviewWithVertex( }), ); - if (tracker) tracker.incrementSubrequests(1); - let response = await attempt(); - - // A Vertex 429 here is queueing, not a bucket the caller can pace around. Measured over ~900 calls on - // one project: roughly three in four refused, and the refusal was uncorrelated with the requested - // output ceiling, with the endpoint, and with whether the previous call succeeded -- resending the - // IDENTICAL request works. The adapter used to make one attempt and turn every one of those into a - // failed file, which is the one case where the single-attempt rule above does not apply: there is no - // schema to re-probe and nothing about the request to change. - // - // Bounded by the caller's own timeout, not by a retry count alone: `timeoutMs` is already clamped to - // the fallback-chain budget, so a slow rung must not spend the whole invocation sitting in backoff. - for (let retry = 0; retry < VERTEX_QUOTA_RETRIES && response.status === 429; retry++) { - const waitMs = VERTEX_QUOTA_BACKOFF_MS * (retry + 1); - if (Date.now() - startTime + waitMs + VERTEX_MIN_ATTEMPT_MS > timeoutMs) break; - - logger.warn(`Vertex AI refused with 429; resending unchanged in ${waitMs}ms`, { model, retry: retry + 1 }); - await new Promise((resolve) => setTimeout(resolve, waitMs)); + let thinkingRejected = false; + let response: Response; + let data: VertexGenerateResponse; + let rawText: string | undefined; + let finishReason: string | undefined; + + for (;;) { + const body = buildBody(!thinkingRejected, currentCeiling); + if (tracker) tracker.incrementSubrequests(1); - response = await attempt(); - } + response = await attempt(body); + + // A Vertex 429 here is queueing, not a rate bucket: resending the identical request works (~3/4 of + // ~900 sampled calls). Bounded by the caller's timeout, already clamped to the fallback-chain budget. + for (let retry = 0; retry < VERTEX_QUOTA_RETRIES && response.status === 429; retry++) { + const waitMs = VERTEX_QUOTA_BACKOFF_MS * (retry + 1); + if (Date.now() - startTime + waitMs + VERTEX_MIN_ATTEMPT_MS > timeoutMs) break; + + logger.warn(`Vertex AI refused with 429; resending unchanged in ${waitMs}ms`, { model, retry: retry + 1 }); + await new Promise((resolve) => setTimeout(resolve, waitMs)); + if (tracker) tracker.incrementSubrequests(1); + response = await attempt(body); + } + + if (response.ok) { + data = (await response.json()) as VertexGenerateResponse; + const candidate = data.candidates?.[0]; + rawText = candidate?.content?.parts?.map((part) => part.text ?? '').join('')?.trim(); + finishReason = candidate?.finishReason; + + if (finishReason && finishReason !== 'STOP') { + logger.warn(`Vertex AI response for ${model} ended with finishReason=${finishReason}; output is likely incomplete`, { + outputSpend: (data.usageMetadata?.candidatesTokenCount ?? 0) + (data.usageMetadata?.thoughtsTokenCount ?? 0), + thoughtSpend: data.usageMetadata?.thoughtsTokenCount ?? 0, + outputCeiling: currentCeiling, + thinkingBudget: thinkingRejected ? undefined : thinkingBudget, + }); + } + + // thinkingConfig stays on here: dropping it switches to unbounded dynamic thinking, the opposite of the fix. + if (finishReason === 'MAX_TOKENS' && input.truncationIntolerant && !ceilingRaised) { + const raisedCeiling = Math.min(VERTEX_MAX_OUTPUT_TOKENS, 2 * answerBudget + thinkingBudget); + const extraMs = ((raisedCeiling - currentCeiling) / 1_000) * MODEL_TIMEOUT_PER_1K_OUTPUT_MS; + if (Date.now() - startTime + extraMs < timeoutMs) { + ceilingRaised = true; + currentCeiling = raisedCeiling; + logger.warn(`Vertex AI ran out of output room on ${model}; resending once with a larger ceiling`, { + outputCeiling: raisedCeiling, + thoughtSpend: data.usageMetadata?.thoughtsTokenCount ?? 0, + hadPartialText: Boolean(rawText), + }); + continue; + } + } + + break; + } - if (!response.ok) { const message = providerErrorMessage(await response.text()); + + if (!thinkingRejected && isThinkingRejection(response.status, message)) { + thinkingRejected = true; + logger.warn('Vertex AI rejected thinkingConfig; resending without an explicit thinking budget', { + model, + error: message, + }); + continue; + } + throw new ProviderRequestError(providerName, response.status, message); } const durationMs = Date.now() - startTime; logger.info(`AI model ${model} responded in ${durationMs}ms`); - const data = (await response.json()) as { - candidates?: Array<{ content?: { parts?: Array<{ text?: string }> }; finishReason?: string }>; - usageMetadata?: { - promptTokenCount?: number; - candidatesTokenCount?: number; - }; - }; - - const candidate = data.candidates?.[0]; - const rawText = candidate?.content?.parts?.map((part) => part.text ?? '').join('')?.trim(); if (!rawText) { - const finishReason = candidate?.finishReason; if (finishReason && finishReason !== 'STOP') { throw new UnparseableModelResponseError(model, `finishReason=${finishReason}`); } throw new Error('Vertex AI returned an empty response.'); } + // Still truncated after the re-probe; fail but attach the partial text so the chain's last model can salvage it. + if (finishReason === 'MAX_TOKENS' && input.truncationIntolerant) { + const error = new UnparseableModelResponseError(model, 'finishReason=MAX_TOKENS'); + attachPartialResponse(error, { + rawText, + inputTokens: data.usageMetadata?.promptTokenCount ?? 0, + outputTokens: data.usageMetadata?.candidatesTokenCount ?? 0, + modelUsed: model, + provider: providerName, + }); + throw error; + } + return { rawText, inputTokens: data.usageMetadata?.promptTokenCount ?? 0, diff --git a/packages/models/src/types.ts b/packages/models/src/types.ts index 907560d3..7e0868ec 100644 --- a/packages/models/src/types.ts +++ b/packages/models/src/types.ts @@ -3,6 +3,7 @@ // existing `@codraoss/models/types` importers are unaffected, and so there is exactly one definition. import type { ModelResponseSchema } from '@codraoss/core/ports'; export type { ModelResponse, ModelResponseSchema } from '@codraoss/core/ports'; +import type { ModelResponse as ModelResponseShape } from '@codraoss/core/ports'; // `responseSchema` is per-call on purpose: file review, verification, and summary each need a different output shape. export type ModelInput = { @@ -13,6 +14,10 @@ export type ModelInput = { // adapter clamps it to its own provider maximum and never goes BELOW its own default, so a caller // that omits it is unaffected. Omitting it on a large batched review is what truncates the response. outputBudgetTokens?: number; + // Set by callers whose answer is only usable whole (review/verification), where a truncated JSON + // prefix would silently read as a clean result. Adapters retry once with more room on MAX_TOKENS. + // Not inferred from `responseSchema`, since ModelRunner strips that once a model has refused a grammar. + truncationIntolerant?: boolean; }; export class ProviderRequestError extends Error { @@ -34,6 +39,20 @@ export class UnparseableModelResponseError extends Error { } } +// Attaches the model's truncated prefix to the thrown error so a caller can't mistake it for a whole +// answer; only a fallback chain's last rung should read it, to salvage the file instead of losing it. +export function attachPartialResponse(error: object, response: ModelResponseShape) { + Object.defineProperty(error, 'partialResponse', { value: response, configurable: true }); +} + +export function partialResponseOf(error: unknown): ModelResponseShape | null { + if (typeof error !== 'object' || error === null) return null; + const partial = (error as { partialResponse?: unknown }).partialResponse; + if (typeof partial !== 'object' || partial === null) return null; + const { rawText } = partial as { rawText?: unknown }; + return typeof rawText === 'string' && rawText.trim() ? (partial as ModelResponseShape) : null; +} + // `details[].description` and `details[].fieldViolations[].description`, flattened and deduped. function errorDetailText(error: unknown): string { const details = (error as Record | null)?.details; @@ -79,6 +98,14 @@ export function providerErrorMessage(errorText: string) { return errorText.trim() || 'The provider returned an error.'; } +// Probed before the broad schema matcher so a thinking-config refusal isn't misread as a grammar drop. +// Shared by the Google and Vertex adapters. +export function isThinkingRejection(status: number, message: string) { + if (status !== 400) return false; + const lower = message.toLowerCase(); + return lower.includes('thinking') || lower.includes('thought'); +} + // Temperature deliberately not zero: a little randomness reviews better than greedy decoding. Each adapter sits at the same relative point on its own scale (Google/Vertex/OpenAI 0-2 at 0.9; Anthropic 0-1 and Cloudflare 0-5 at 0.6); watch `droppedByVerdict` if these move. export function jsonOnlyPrompts(input: ModelInput) { return { diff --git a/packages/models/test/model/chain-salvage.spec.ts b/packages/models/test/model/chain-salvage.spec.ts new file mode 100644 index 00000000..c5be4c9d --- /dev/null +++ b/packages/models/test/model/chain-salvage.spec.ts @@ -0,0 +1,231 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { defaultRepoConfig, type ResolvedModelConfig } from '@codraoss/schema'; +import { TokenTracker } from '@codraoss/core/token-tracker'; + +import { runModelChain, type ModelReviewContext } from '../../src/internal/model-review-chain'; +import { ModelChainProgressStore } from '../../src/internal/model-chain-progress'; +import { ModelRateLimitBook } from '../../src/internal/model-rate-limits'; +import { attachPartialResponse, UnparseableModelResponseError } from '../../src/types'; +import { MODEL_FALLBACK_CHAIN_BUDGET_MS } from '../../src/limits'; + +function fakeKv() { + const store = new Map(); + return { + async get(key: string) { return store.get(key) ?? null; }, + async put(key: string, value: string) { store.set(key, value); }, + }; +} + +function resolved(modelName: string): ResolvedModelConfig { + return { + modelId: modelName, + providerId: '00000000-0000-4000-8000-000000000000', + providerName: 'Google', + apiFormat: 'gemini', + modelName, + updatedAt: new Date().toISOString(), + providerEnabled: true, + baseUrl: null, + encryptedApiKey: 'key', + }; +} + +const ANSWER = '{"findings":[]}'; + +type Attempt = { model: string; timeoutMs: number | undefined }; + +function makeContext(input: { + chain: string[]; + call: (model: string, attempt: number) => Promise<{ rawText: string; inputTokens: number; outputTokens: number; modelUsed: string; provider: string }>; + attempts: Attempt[]; + tracker?: TokenTracker; +}): ModelReviewContext { + let callCount = 0; + return { + selectModel: () => ({ primary: input.chain[0], fallbacks: input.chain.slice(1) }), + resolveModel: async (model: string) => resolved(model), + isProviderUnavailable: async () => false, + markProviderUnavailable: async () => {}, + callResolvedModel: async (model: ResolvedModelConfig, _modelInput: unknown, timeoutMs?: number) => { + input.attempts.push({ model: model.modelName, timeoutMs }); + return input.call(model.modelName, callCount++); + }, + tracker: input.tracker, + jobId: 'job-1', + aiBinding: undefined, + rateLimits: new ModelRateLimitBook(), + asyncUnsupportedModels: new Set(), + chainProgress: new ModelChainProgressStore(fakeKv(), 'job-1'), + } as unknown as ModelReviewContext; +} + +function truncatedError(model: string, rawText: string) { + const error = new UnparseableModelResponseError(model, 'finishReason=MAX_TOKENS'); + attachPartialResponse(error, { + rawText, + inputTokens: 1_200, + outputTokens: 8_000, + modelUsed: model, + provider: 'Google', + }); + return error; +} + +function chainParams(overrides: Partial[1]> = {}) { + return { + systemPrompt: 'system', + userPrompt: 'user', + responseSchema: { name: 'x', schema: {} } as never, + timeoutMs: 50_000, + label: 'src/app.ts', + totalLineCount: 400, + config: defaultRepoConfig, + parse: (rawText: string) => JSON.parse(rawText) as unknown, + ...overrides, + } as Parameters[1]; +} + +describe('runModelChain: salvaging a truncated last answer', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('parses the partial answer when the last model runs out of room', async () => { + const attempts: Attempt[] = []; + const tracker = new TokenTracker(); + const ctx = makeContext({ + chain: ['gemini-2.5-flash'], + attempts, + tracker, + call: async (model) => { throw truncatedError(model, ANSWER); }, + }); + + const result = await runModelChain(ctx, chainParams()); + + expect(result.parsed).toEqual({ findings: [] }); + expect(result.modelUsed).toBe('gemini-2.5-flash'); + expect(result.degraded).toBe('truncated'); + expect(tracker.getTotalUsage()).toMatchObject({ input: 1_200, output: 8_000 }); + expect(attempts).toHaveLength(1); + }); + + it('does not salvage while a better model is still available', async () => { + const attempts: Attempt[] = []; + const ctx = makeContext({ + chain: ['gemini-2.5-flash', 'gemini-2.5-pro'], + attempts, + call: async (model) => { + if (model === 'gemini-2.5-flash') throw truncatedError(model, ANSWER); + return { rawText: '{"findings":["real"]}', inputTokens: 10, outputTokens: 20, modelUsed: model, provider: 'Google' }; + }, + }); + + const result = await runModelChain(ctx, chainParams()); + + expect(attempts.map((a) => a.model)).toEqual(['gemini-2.5-flash', 'gemini-2.5-pro']); + expect(result.parsed).toEqual({ findings: ['real'] }); + expect(result.degraded).toBeUndefined(); + }); + + it('fails normally when the partial answer cannot be parsed', async () => { + const attempts: Attempt[] = []; + const ctx = makeContext({ + chain: ['gemini-2.5-flash'], + attempts, + call: async (model) => { throw truncatedError(model, '{"findings":[{"path":'); }, + }); + + await expect(runModelChain(ctx, chainParams())).rejects.toThrow(/no reviewable output/i); + }); + + it('leaves an ordinary failure on the last model alone', async () => { + const attempts: Attempt[] = []; + const ctx = makeContext({ + chain: ['gemini-2.5-flash'], + attempts, + call: async () => { throw new UnparseableModelResponseError('gemini-2.5-flash', 'empty'); }, + }); + + await expect(runModelChain(ctx, chainParams())).rejects.toThrow(/empty/); + }); +}); + +describe('runModelChain: sharing the invocation time budget across rungs', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + function withClock() { + let now = 1_000_000; + vi.spyOn(Date, 'now').mockImplementation(() => now); + return { spend: (ms: number) => { now += ms; } }; + } + + it('gives a single-model chain everything it asked for', async () => { + withClock(); + const attempts: Attempt[] = []; + const ctx = makeContext({ + chain: ['gemini-2.5-flash'], + attempts, + call: async (model) => ({ rawText: ANSWER, inputTokens: 1, outputTokens: 1, modelUsed: model, provider: 'Google' }), + }); + + await runModelChain(ctx, chainParams({ timeoutMs: 50_000 })); + + expect(attempts[0].timeoutMs).toBe(50_000); + }); + + it('holds back room so a fallback still runs in the same invocation', async () => { + const clock = withClock(); + const attempts: Attempt[] = []; + const ctx = makeContext({ + chain: ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-3.1-flash-lite'], + attempts, + call: async (model, attempt) => { + clock.spend(attempts[attempt].timeoutMs ?? 0); + throw new UnparseableModelResponseError(model, 'no answer'); + }, + }); + + await expect(runModelChain(ctx, chainParams({ timeoutMs: 50_000 }))).rejects.toThrow(); + + expect(attempts.map((a) => a.timeoutMs)).toEqual([35_000, 20_000]); + expect(attempts.map((a) => a.model)).toEqual(['gemini-2.5-flash', 'gemini-2.5-pro']); + }); + + it('never grants more than the chain budget in total', async () => { + const clock = withClock(); + const attempts: Attempt[] = []; + const ctx = makeContext({ + chain: ['a', 'b', 'c', 'd'], + attempts, + call: async (model, attempt) => { + clock.spend(attempts[attempt].timeoutMs ?? 0); + throw new UnparseableModelResponseError(model, 'no answer'); + }, + }); + + await expect(runModelChain(ctx, chainParams({ timeoutMs: 50_000 }))).rejects.toThrow(); + + const granted = attempts.reduce((sum, a) => sum + (a.timeoutMs ?? 0), 0); + expect(granted).toBeLessThanOrEqual(MODEL_FALLBACK_CHAIN_BUDGET_MS); + }); + + it('never grants a rung more than it asked for', async () => { + const clock = withClock(); + const attempts: Attempt[] = []; + const ctx = makeContext({ + chain: ['a', 'b', 'c'], + attempts, + call: async (model, attempt) => { + clock.spend(attempts[attempt].timeoutMs ?? 0); + throw new UnparseableModelResponseError(model, 'no answer'); + }, + }); + + await expect(runModelChain(ctx, chainParams({ timeoutMs: 12_000 }))).rejects.toThrow(); + + expect(attempts.every((a) => (a.timeoutMs ?? 0) <= 12_000)).toBe(true); + expect(attempts).toHaveLength(3); + }); +}); diff --git a/packages/models/test/model/service-requests.spec.ts b/packages/models/test/model/service-requests.spec.ts index 82729ba5..d18ed514 100644 --- a/packages/models/test/model/service-requests.spec.ts +++ b/packages/models/test/model/service-requests.spec.ts @@ -115,7 +115,7 @@ describe('ModelRunner: request shape and response handling', () => { it('sends the caller\'s grammar as responseJsonSchema, or none at all', async () => { const review = await captureGeminiBody({ systemPrompt: 'system', userPrompt: 'user', responseSchema: buildReviewResponseSchema(10) }); expect(schemaKeys(review)).toEqual(['responseJsonSchema']); - expect(review.generationConfig.responseJsonSchema.properties.findings.maxItems).toBe(20); + expect(review.generationConfig.responseJsonSchema.properties.findings.maxItems).toBe(10); expect(review.generationConfig.responseMimeType).toBe('application/json'); // No `outputBudgetTokens` on this input, so the adapter's own default answer budget applies -- and // the bounded thinking budget is added ON TOP of it, never carved out of it. diff --git a/packages/models/test/model/truncation.spec.ts b/packages/models/test/model/truncation.spec.ts new file mode 100644 index 00000000..e6cebd55 --- /dev/null +++ b/packages/models/test/model/truncation.spec.ts @@ -0,0 +1,173 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { generateKeyPairSync } from 'node:crypto'; + +import { reviewWithGoogle } from '@codraoss/models/google'; +import { reviewWithVertex } from '@codraoss/models/vertex'; +import { geminiThinkingBudgetTokens, OUTPUT_TOKENS_FLOOR } from '../../src/limits'; + +const ANSWER = '{"findings":[],"overall_correctness":"patch is correct","overall_explanation":"ok","overall_confidence_score":0.9}'; +const THINKING = geminiThinkingBudgetTokens(OUTPUT_TOKENS_FLOOR); +const FIRST_CEILING = OUTPUT_TOKENS_FLOOR + THINKING; +const RAISED_CEILING = 2 * OUTPUT_TOKENS_FLOOR + THINKING; + +function geminiResponse(finishReason: string, text: string) { + return new Response( + JSON.stringify({ + candidates: [{ content: { parts: text ? [{ text }] : [] }, finishReason }], + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 400, thoughtsTokenCount: 9_826 }, + }), + { status: 200, headers: { 'content-type': 'application/json' } }, + ); +} + +const PARTIAL = '{"findings":[{"path":"a.ts","line":1,"severity":"P1","claim_type":"bug","title":"x"'; + +function bodiesOf(fetchMock: { mock: { calls: unknown[][] } }) { + return fetchMock.mock.calls.map((call) => JSON.parse(String((call[1] as RequestInit).body))); +} + +describe('Gemini truncation handling', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + const config = { apiKey: 'key', providerName: 'Google' }; + const input = { systemPrompt: 'system', userPrompt: 'user', truncationIntolerant: true }; + + it('re-probes once with a larger ceiling when the answer is cut off', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(geminiResponse('MAX_TOKENS', PARTIAL)) + .mockResolvedValueOnce(geminiResponse('STOP', ANSWER)); + + const result = await reviewWithGoogle(config, 'gemini-2.5-flash', input); + + const sent = bodiesOf(fetchMock); + expect(sent).toHaveLength(2); + expect(sent[0].generationConfig.maxOutputTokens).toBe(FIRST_CEILING); + expect(sent[1].generationConfig.maxOutputTokens).toBe(RAISED_CEILING); + expect(sent[1].generationConfig.thinkingConfig).toEqual({ thinkingBudget: THINKING }); + expect(result.rawText).toContain('patch is correct'); + }); + + it('re-probes at most once, then fails with the partial text attached', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockImplementation(async () => geminiResponse('MAX_TOKENS', PARTIAL)); + + const error = await reviewWithGoogle(config, 'gemini-2.5-flash', input).catch((e: unknown) => e); + + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toMatch(/no reviewable output.*MAX_TOKENS/i); + expect((error as { partialResponse?: { rawText: string; outputTokens: number } }).partialResponse) + .toMatchObject({ rawText: PARTIAL, outputTokens: 400, modelUsed: 'gemini-2.5-flash' }); + expect(bodiesOf(fetchMock)).toHaveLength(2); + }); + + it('leaves a truncated answer alone for callers that can use a partial one', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(geminiResponse('MAX_TOKENS', PARTIAL)); + + const result = await reviewWithGoogle(config, 'gemini-2.5-flash', { systemPrompt: 's', userPrompt: 'u' }); + + expect(result.rawText).toBe(PARTIAL); + expect(bodiesOf(fetchMock)).toHaveLength(1); + }); + + it('does not re-probe a non-MAX_TOKENS stop reason', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(geminiResponse('SAFETY', '')); + + await expect(reviewWithGoogle(config, 'gemini-2.5-flash', input)).rejects.toThrow(/finishReason=SAFETY/); + expect(bodiesOf(fetchMock)).toHaveLength(1); + }); + + it('skips the re-probe when the remaining time cannot deliver the extra tokens', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(geminiResponse('MAX_TOKENS', PARTIAL)); + + await expect( + reviewWithGoogle({ ...config, timeoutMs: 1_000 }, 'gemini-2.5-flash', input), + ).rejects.toThrow(/no reviewable output/i); + expect(bodiesOf(fetchMock)).toHaveLength(1); + }); +}); + +describe('Vertex truncation handling', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + const { privateKey } = generateKeyPairSync('rsa', { + modulusLength: 2048, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + }); + + // Tokens are cached per client_email for the isolate's life; unique accounts avoid cross-test reuse. + let account = 0; + function freshConfig() { + account += 1; + return { + apiKey: JSON.stringify({ + client_email: 'codra-trunc-' + account + '@example.iam.gserviceaccount.com', + private_key: privateKey, + }), + baseUrl: 'https://us-central1-aiplatform.googleapis.com/v1/projects/p/locations/us-central1', + providerName: 'Google Vertex AI', + }; + } + + function tokenOk() { + return new Response(JSON.stringify({ access_token: 'token', expires_in: 3600 }), { + status: 200, + headers: { 'content-type': 'application/json' }, + }); + } + + function generateBodies(fetchMock: { mock: { calls: unknown[][] } }) { + return fetchMock.mock.calls + .filter((call) => String(call[0]).includes(':generateContent')) + .map((call) => JSON.parse(String((call[1] as RequestInit).body))); + } + + const input = { systemPrompt: 'system', userPrompt: 'user', truncationIntolerant: true }; + + it('re-probes once with a larger ceiling and keeps the thinking budget', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(geminiResponse('MAX_TOKENS', PARTIAL)) + .mockResolvedValueOnce(geminiResponse('STOP', ANSWER)); + + const result = await reviewWithVertex(freshConfig(), 'gemini-2.5-flash', input); + + const sent = generateBodies(fetchMock); + expect(sent).toHaveLength(2); + expect(sent[0].generationConfig.maxOutputTokens).toBe(FIRST_CEILING); + expect(sent[1].generationConfig.maxOutputTokens).toBe(RAISED_CEILING); + expect(sent[1].generationConfig.thinkingConfig).toEqual({ thinkingBudget: THINKING }); + expect(result.rawText).toContain('patch is correct'); + }); + + it('fails with the partial text attached when the re-probe is still cut off', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockImplementation(async () => geminiResponse('MAX_TOKENS', PARTIAL)); + + const error = await reviewWithVertex(freshConfig(), 'gemini-2.5-flash', input).catch((e: unknown) => e); + + expect((error as Error).message).toMatch(/no reviewable output.*MAX_TOKENS/i); + expect((error as { partialResponse?: { rawText: string } }).partialResponse) + .toMatchObject({ rawText: PARTIAL, provider: 'Google Vertex AI' }); + expect(generateBodies(fetchMock)).toHaveLength(2); + }); + + it('leaves a truncated answer alone for callers that can use a partial one', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(geminiResponse('MAX_TOKENS', PARTIAL)); + + const result = await reviewWithVertex(freshConfig(), 'gemini-2.5-flash', { systemPrompt: 's', userPrompt: 'u' }); + + expect(result.rawText).toBe(PARTIAL); + expect(generateBodies(fetchMock)).toHaveLength(1); + }); +}); diff --git a/packages/models/test/model/vertex.spec.ts b/packages/models/test/model/vertex.spec.ts new file mode 100644 index 00000000..01541b0a --- /dev/null +++ b/packages/models/test/model/vertex.spec.ts @@ -0,0 +1,192 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { generateKeyPairSync } from 'node:crypto'; + +import { reviewWithVertex } from '@codraoss/models/vertex'; +import { geminiThinkingBudgetTokens, OUTPUT_TOKENS_FLOOR } from '../../src/limits'; + +describe('reviewWithVertex: thinking budget and the rejection latch', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + // Vertex signs a JWT with the key, so it must be a genuine PKCS8 key, not a placeholder string. + const { privateKey } = generateKeyPairSync('rsa', { + modulusLength: 2048, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + }); + + // Tokens are cached per client_email for the isolate's life; unique accounts avoid cross-test reuse. + let account = 0; + function freshConfig() { + account += 1; + return { + apiKey: JSON.stringify({ + client_email: `codra-${account}@example.iam.gserviceaccount.com`, + private_key: privateKey, + }), + baseUrl: 'https://us-central1-aiplatform.googleapis.com/v1/projects/p/locations/us-central1', + providerName: 'Google Vertex AI', + }; + } + + const input = { systemPrompt: 'system', userPrompt: 'user' }; + + function tokenOk() { + return new Response(JSON.stringify({ access_token: 'token', expires_in: 3600 }), { + status: 200, + headers: { 'content-type': 'application/json' }, + }); + } + + function vertexOk(text = '{"findings":[],"overall_correctness":"patch is correct","overall_explanation":"ok","overall_confidence_score":0.9}') { + return new Response( + JSON.stringify({ + candidates: [{ content: { parts: [{ text }] }, finishReason: 'STOP' }], + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 20, thoughtsTokenCount: 5 }, + }), + { status: 200, headers: { 'content-type': 'application/json' } }, + ); + } + + function vertex400(message: string) { + return new Response( + JSON.stringify({ error: { code: 400, status: 'INVALID_ARGUMENT', message } }), + { status: 400, headers: { 'content-type': 'application/json' } }, + ); + } + + function vertex429() { + return new Response( + JSON.stringify({ error: { code: 429, message: 'Resource exhausted. Please try again later.' } }), + { status: 429, headers: { 'content-type': 'application/json' } }, + ); + } + + function generateBodies(fetchMock: { mock: { calls: unknown[][] } }) { + return fetchMock.mock.calls + .filter((call) => String(call[0]).includes(':generateContent')) + .map((call) => JSON.parse(String((call[1] as RequestInit).body))); + } + + it('sends a bounded thinking budget and reserves room for it in maxOutputTokens', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(vertexOk()); + + await reviewWithVertex(freshConfig(), 'gemini-2.5-flash', input); + + const [body] = generateBodies(fetchMock); + const expectedThinking = geminiThinkingBudgetTokens(OUTPUT_TOKENS_FLOOR); + + expect(body.generationConfig.thinkingConfig).toEqual({ thinkingBudget: expectedThinking }); + expect(body.generationConfig.maxOutputTokens).toBe(OUTPUT_TOKENS_FLOOR + expectedThinking); + }); + + it('scales the budget with the requested output and keeps thinking a minority of the ceiling', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(vertexOk()); + + await reviewWithVertex(freshConfig(), 'gemini-2.5-pro', { ...input, outputBudgetTokens: 32_768 }); + + const [body] = generateBodies(fetchMock); + const thinking = body.generationConfig.thinkingConfig.thinkingBudget; + + expect(thinking).toBe(geminiThinkingBudgetTokens(32_768)); + expect(thinking).toBeGreaterThanOrEqual(1_024); + expect(thinking).toBeLessThan(body.generationConfig.maxOutputTokens / 3); + expect(body.generationConfig.maxOutputTokens).toBe(32_768 + thinking); + }); + + it('drops thinkingConfig and resends once when the model refuses it', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(vertex400('Unable to submit request because thinking is not supported by this model.')) + .mockResolvedValueOnce(vertexOk()); + + const result = await reviewWithVertex(freshConfig(), 'gemini-2.5-flash', input); + + const bodies = generateBodies(fetchMock); + expect(bodies).toHaveLength(2); + expect(bodies[0].generationConfig.thinkingConfig).toBeDefined(); + expect(bodies[1].generationConfig.thinkingConfig).toBeUndefined(); + expect(bodies[1].generationConfig.maxOutputTokens).toBe(bodies[0].generationConfig.maxOutputTokens); + expect(result.rawText).toContain('patch is correct'); + }); + + it('latches the refusal so a second failure is not retried again', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(vertex400('thinking is not supported')) + .mockResolvedValueOnce(vertex400('thinking is not supported')); + + await expect(reviewWithVertex(freshConfig(), 'gemini-2.5-flash', input)).rejects.toThrow(/400/); + expect(generateBodies(fetchMock)).toHaveLength(2); + }); + + it('does not treat an unrelated 400 as a thinking rejection', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(vertex400('Request payload size exceeds the limit.')); + + await expect(reviewWithVertex(freshConfig(), 'gemini-2.5-flash', input)).rejects.toThrow(/payload size/); + expect(generateBodies(fetchMock)).toHaveLength(1); + }); + + it('still resends on a 429 after the thinking latch has fired', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(vertex400('thinking is not supported')) + .mockResolvedValueOnce(vertex429()) + .mockResolvedValueOnce(vertexOk()); + + const result = await reviewWithVertex(freshConfig(), 'gemini-2.5-flash', input); + + const bodies = generateBodies(fetchMock); + expect(bodies).toHaveLength(3); + expect(bodies[1].generationConfig.thinkingConfig).toBeUndefined(); + expect(bodies[2].generationConfig.thinkingConfig).toBeUndefined(); + expect(result.rawText).toContain('patch is correct'); + }, 20_000); + + it('resends an unchanged request on a plain 429', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(vertex429()) + .mockResolvedValueOnce(vertexOk()); + + await reviewWithVertex(freshConfig(), 'gemini-2.5-flash', input); + + const bodies = generateBodies(fetchMock); + expect(bodies).toHaveLength(2); + expect(bodies[0]).toEqual(bodies[1]); + }, 20_000); + + it('fails rather than returning nothing when the budget is spent before any answer', async () => { + vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(new Response( + JSON.stringify({ + candidates: [{ content: { parts: [] }, finishReason: 'MAX_TOKENS' }], + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 0, thoughtsTokenCount: 9_826 }, + }), + { status: 200, headers: { 'content-type': 'application/json' } }, + )); + + await expect(reviewWithVertex(freshConfig(), 'gemini-2.5-flash', input)) + .rejects.toThrow(/no reviewable output.*MAX_TOKENS/i); + }); + + it('reports reasoning spend in usage so a truncation can be attributed', async () => { + vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(tokenOk()) + .mockResolvedValueOnce(vertexOk()); + + const result = await reviewWithVertex(freshConfig(), 'gemini-2.5-flash', input); + + expect(result.inputTokens).toBe(10); + expect(result.outputTokens).toBe(20); + expect(result.modelUsed).toBe('gemini-2.5-flash'); + }); +}); diff --git a/packages/provider-github/src/client.ts b/packages/provider-github/src/client.ts index 30bf83da..24320d11 100644 --- a/packages/provider-github/src/client.ts +++ b/packages/provider-github/src/client.ts @@ -183,9 +183,12 @@ export class GitHubClient { return fetchCompareDiff(this.ctx(), owner, repo, base, head); } - async getRepoFileOrNull(owner: string, repo: string, path: string) { - return withRetry(`getRepoFileOrNull ${owner}/${repo}/${path}`, async () => { - const response = await this.request(`${repoApiPath(owner, repo)}/contents/${encodeGitHubContentPath(path)}`); + // File content at a specific commit; without `ref`, GitHub answers from the default branch instead. + // Never retried: a secondary rate limit here would sleep 60-120s, worse than just skipping the context. + async getRepoFile(owner: string, repo: string, path: string, ref?: string) { + return withRetry(`getRepoFile ${owner}/${repo}/${path}`, async () => { + const query = ref ? `?ref=${encodeURIComponent(ref)}` : ''; + const response = await this.request(`${repoApiPath(owner, repo)}/contents/${encodeGitHubContentPath(path)}${query}`); if (response.status === 404) return null; await assertResponseOk(response, path, 'GitHub repo file fetch'); @@ -193,9 +196,13 @@ export class GitHubClient { if (!data.content) { return null; } + if (data.encoding !== 'base64') return data.content; - return data.encoding === 'base64' ? atob(data.content.replace(/\n/g, '')) : data.content; - }); + // atob alone yields one code unit per byte, mangling non-ASCII source files; decode as UTF-8 instead. + const binary = atob(data.content.replace(/\n/g, '')); + const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0)); + return new TextDecoder().decode(bytes); + }, 0); } async createCheckRun( diff --git a/packages/provider-github/src/service.ts b/packages/provider-github/src/service.ts index 63ba9f22..5753c65d 100644 --- a/packages/provider-github/src/service.ts +++ b/packages/provider-github/src/service.ts @@ -31,6 +31,10 @@ export class GitHubService { return this.client.getCompareDiff(owner, repo, base, head); } + async getRepoFile(owner: string, repo: string, path: string, ref?: string) { + return this.client.getRepoFile(owner, repo, path, ref); + } + async createCheckRun(owner: string, repo: string, params: { headSha: string; title: string; summary: string }) { return this.client.createCheckRun(owner, repo, params); } diff --git a/packages/schema/src/schema-repo-config.ts b/packages/schema/src/schema-repo-config.ts index ad103812..8d2b414b 100644 --- a/packages/schema/src/schema-repo-config.ts +++ b/packages/schema/src/schema-repo-config.ts @@ -32,7 +32,15 @@ export const reviewConfigSchema = z.object({ max_diff_lines_per_file: z.number().int().min(1).max(5_000).default(800), batch_small_files: z.boolean().default(true), max_total_diff_chars: z.number().int().min(1).max(500_000).default(150_000), + // Presentation cap: comments actually posted to the PR. Findings past this are still recorded + // (disposition 'cap') and shown on the dashboard; nothing upstream of posting should read it. max_comments: z.number().int().min(1).max(150).default(10), + // How many findings the pipeline works with (generator, verifier, dashboard); independent of + // max_comments so tightening the posted cap no longer quietly shrinks the review itself. + review_breadth: z.number().int().min(1).max(150).default(25), + // Off by default: adds a large single file's post-change content as read-only context, costing + // one extra GitHub subrequest per qualifying file (see FILE_CONTEXT_CHAR_BUDGET). + full_file_context: z.boolean().default(false), min_severity: z.enum(reviewSeverities).default('P3'), min_confidence: z.number().min(0).max(1).default(0), focus: z.array(z.enum(reviewCategories)).default([...reviewCategories]), diff --git a/test/api/repos.spec.ts b/test/api/repos.spec.ts index ae874459..e1ac2163 100644 --- a/test/api/repos.spec.ts +++ b/test/api/repos.spec.ts @@ -69,12 +69,35 @@ describe('Dashboard API: repositories and repo config', () => { }); const client = new GitHubClient(env, '123'); - const content = await client.getRepoFileOrNull('owner', 'repo', 'src/path with spaces/app.ts'); + const content = await client.getRepoFile('owner', 'repo', 'src/path with spaces/app.ts'); expect(content).toBe('hello'); expect(requestedUrl).toBe('https://api.github.com/repos/owner/repo/contents/src/path%20with%20spaces/app.ts'); }); + it('pins repo file reads to the requested ref', async () => { + const env = createTestEnv(); + await env.APP_KV.put('install:123', JSON.stringify({ + token: 'cached-installation-token', + expiresAt: new Date(Date.now() + 60_000).toISOString(), + })); + + let requestedUrl = ''; + vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { + requestedUrl = String(input); + return Response.json({ + content: Buffer.from('const greeting = "café — こんにちは";').toString('base64'), + encoding: 'base64', + }); + }); + + const client = new GitHubClient(env, '123'); + const content = await client.getRepoFile('owner', 'repo', 'src/app.ts', 'abc123'); + + expect(content).toBe('const greeting = "café — こんにちは";'); + expect(requestedUrl).toBe('https://api.github.com/repos/owner/repo/contents/src/app.ts?ref=abc123'); + }); + it('keeps repo model settings inherited when loading global strategy', async () => { const env = createTestEnv(); const repo = uniqueName('global-inherit'); diff --git a/test/findings/prompts-batch-review.spec.ts b/test/findings/prompts-batch-review.spec.ts index ff705ef9..8a34565f 100644 --- a/test/findings/prompts-batch-review.spec.ts +++ b/test/findings/prompts-batch-review.spec.ts @@ -4,6 +4,7 @@ import { buildBatchReviewResponseSchema, buildReviewResponseSchema, generatorFindingCap, + reviewBreadth, } from '@server/prompts/file-review'; import { BIN_DIFF_CHAR_BUDGET, BIN_MAX_FILES } from '@server/core/review'; import { PROMPT_FIT_SAFETY_FACTOR, estimatePromptTokens } from '@codraoss/models'; @@ -66,7 +67,7 @@ describe('buildBatchReviewPrompts', () => { it('substitutes the cap and states the persona once per language present', () => { const uniform = build([file('src/a.ts', ['a']), file('src/b.ts', ['b'])]); expect(uniform.systemPrompt).not.toContain('{{MAX_COMMENTS}}'); - expect(uniform.systemPrompt).toContain(`at most ${generatorFindingCap(defaultRepoConfig.review.max_comments)} findings PER FILE`); + expect(uniform.systemPrompt).toContain(`at most ${reviewBreadth(defaultRepoConfig.review)} findings PER FILE`); expect(uniform.systemPrompt).toMatch(/^You are a world-class professional senior code reviewer as .+\./); expect(uniform.userPrompt.match(/^Language: /gm)).toHaveLength(1); @@ -113,7 +114,7 @@ describe('buildBatchReviewResponseSchema', () => { // The single-file grammar caps in the schema; the batch one cannot. Gemini rejects a bounded // array nested inside a bounded array ("too many states for serving") and we lose constrained // decoding for the whole bin, so the per-file cap lives in prose and in parse-time truncation. - expect(single.properties.findings.maxItems).toBe(generatorFindingCap(10)); + expect(single.properties.findings.maxItems).toBe(10); expect(fileEntry.properties.findings.maxItems).toBeUndefined(); expect(schema.properties.files.maxItems).toBe(4); // Path first, and required: the nesting is what carries file identity. diff --git a/test/findings/prompts-file-context.spec.ts b/test/findings/prompts-file-context.spec.ts new file mode 100644 index 00000000..437d1477 --- /dev/null +++ b/test/findings/prompts-file-context.spec.ts @@ -0,0 +1,136 @@ +import { describe, expect, it } from 'vitest'; +import { buildFileReviewPrompts, buildFileReviewSystemPromptBase, wantsFileContext } from '@server/prompts/file-review'; +import { contentMatchesDiff } from '../../packages/core/src/review/file-context'; +import { defaultRepoConfig, type RepoConfig } from '@codraoss/schema'; +import type { FileDiff } from '@server/core/diff'; + +const SOURCE = Array.from({ length: 400 }, (_, i) => `line ${i + 1}`); + +function largeFile(overrides: Partial = {}): FileDiff { + return { + path: 'src/app.ts', + previousPath: null, + isNew: false, + isDeleted: false, + isBinary: false, + lineCount: 300, + hunks: [{ + header: '@@ -200,2 +200,2 @@', + lines: [ + { kind: 'context', content: 'line 200', oldLineNumber: 200, newLineNumber: 200, position: 1 }, + { kind: 'add', content: 'line 201', newLineNumber: 201, position: 2 }, + ], + }], + ...overrides, + }; +} + +const withContext = (review: Partial = {}) => ({ + ...defaultRepoConfig.review, + full_file_context: true, + ...review, +}); + +describe('wantsFileContext', () => { + it('is off unless the repo asks for it', () => { + expect(wantsFileContext(largeFile(), false)).toBe(false); + expect(wantsFileContext(largeFile(), true)).toBe(true); + }); + + it('is scoped to files whose diff is larger than a packable one', () => { + expect(wantsFileContext(largeFile({ lineCount: 150 }), true)).toBe(false); + expect(wantsFileContext(largeFile({ lineCount: 151 }), true)).toBe(true); + }); + + it('skips files whose content the diff already contains, or that have none', () => { + expect(wantsFileContext(largeFile({ isNew: true }), true)).toBe(false); + expect(wantsFileContext(largeFile({ isDeleted: true }), true)).toBe(false); + expect(wantsFileContext(largeFile({ isBinary: true }), true)).toBe(false); + }); + + it('does not enlarge a prompt that is being retried for being too much', () => { + expect(wantsFileContext(largeFile(), true, { compactPrompt: true })).toBe(false); + }); +}); + +describe('contentMatchesDiff', () => { + it('accepts content whose lines sit where the diff says they do', () => { + expect(contentMatchesDiff(largeFile(), SOURCE.join('\n'))).toBe(true); + }); + + it('rejects content that has shifted out from under the diff', () => { + const shifted = ['a new import', ...SOURCE].join('\n'); + expect(contentMatchesDiff(largeFile(), shifted)).toBe(false); + }); + + it('rejects a diff with nothing to check against', () => { + const deletionOnly = largeFile({ + hunks: [{ + header: '@@ -200,1 +199,0 @@', + lines: [{ kind: 'del', content: 'line 200', oldLineNumber: 200, position: 1 }], + }], + }); + expect(contentMatchesDiff(deletionOnly, SOURCE.join('\n'))).toBe(false); + }); +}); + +describe('the full-file context block', () => { + const build = (fileContext: string | null) => buildFileReviewPrompts({ + file: largeFile(), + fileContext, + prTitle: 'PR', + prDescription: null, + config: withContext(), + }); + + it('windows the file around this prompt\'s own hunks rather than pasting all of it', () => { + const { userPrompt } = build(SOURCE.join('\n')); + + expect(userPrompt).toContain('Full file after the change'); + expect(userPrompt).toContain('80\tline 80'); + expect(userPrompt).toContain('321\tline 321'); + expect(userPrompt).not.toContain('79\tline 79'); + expect(userPrompt).not.toContain('322\tline 322'); + }); + + it('bounds the block even when the window is enormous', () => { + const wide = largeFile({ + hunks: [{ + header: '@@ -1,400 +1,400 @@', + lines: [ + { kind: 'context', content: 'line 1', oldLineNumber: 1, newLineNumber: 1, position: 1 }, + { kind: 'context', content: 'line 400', oldLineNumber: 400, newLineNumber: 400, position: 2 }, + ], + }], + }); + const long = Array.from({ length: 400 }, (_, i) => `line ${i + 1}${'x'.repeat(200)}`); + const { userPrompt } = buildFileReviewPrompts({ + file: wide, + fileContext: long.join('\n'), + prTitle: 'PR', + prDescription: null, + config: withContext(), + }); + + const block = userPrompt.slice(userPrompt.indexOf('Full file after the change')); + expect(block.length).toBeLessThan(9_000); + }); + + it('tells the model the context is not evidence', () => { + const { userPrompt, systemPrompt } = build(SOURCE.join('\n')); + + expect(userPrompt).toMatch(/copied character-for-character from the UNIFIED DIFF/); + expect(userPrompt).toMatch(/counts as no evidence at all/); + expect(systemPrompt).not.toMatch(/You can see ONLY the diff below/); + expect(systemPrompt).toMatch(/cannot see the rest of the repository/); + }); + + it('changes nothing at all when no context was fetched', () => { + const { userPrompt, systemPrompt } = build(null); + + expect(userPrompt).not.toContain('Full file after the change'); + expect(userPrompt).not.toContain('counts as no evidence at all'); + expect(systemPrompt).toMatch(/You can see ONLY the diff below/); + expect(buildFileReviewSystemPromptBase()).toMatch(/ONLY the diff/); + }); +}); diff --git a/test/findings/prompts-file-review.spec.ts b/test/findings/prompts-file-review.spec.ts index c9e8854c..deb9af5d 100644 --- a/test/findings/prompts-file-review.spec.ts +++ b/test/findings/prompts-file-review.spec.ts @@ -5,6 +5,7 @@ import { buildFileReviewSystemPromptBase, buildReviewResponseSchema, generatorFindingCap, + reviewBreadth, } from '@server/prompts/file-review'; import { defaultRepoConfig } from '@codraoss/schema'; import type { FileDiff } from '@server/core/diff'; @@ -106,16 +107,21 @@ describe('the generator', () => { const _systemBase = buildFileReviewSystemPromptBase(); - // Not the posted cap: `max_comments` applies per job in finalize, this per chunk upstream of - // four remove-only filters. - it('lets the generator produce more candidates than can be posted', () => { - const maxItems = (buildReviewResponseSchema(10) as unknown as { + it('bounds the grammar by the review breadth it is handed', () => { + const maxItems = (cap: number) => (buildReviewResponseSchema(cap) as unknown as { schema: { properties: { findings: { maxItems: number } } }; }).schema.properties.findings.maxItems; - expect(maxItems).toBe(20); + expect(maxItems(reviewBreadth(defaultRepoConfig.review))).toBe(25); + expect(reviewBreadth(defaultRepoConfig.review)).toBeGreaterThan(defaultRepoConfig.review.max_comments); + expect(maxItems(0)).toBe(1); + }); + + it('falls back to the old derivation for a config snapshot with no review_breadth', () => { + const legacy = { ...defaultRepoConfig.review, review_breadth: undefined } as unknown as typeof defaultRepoConfig.review; + + expect(reviewBreadth(legacy)).toBe(generatorFindingCap(defaultRepoConfig.review.max_comments)); expect(generatorFindingCap(10)).toBe(20); - // Never zero, whatever an operator sets. expect(generatorFindingCap(1)).toBe(2); }); }); diff --git a/test/findings/prompts-intent.spec.ts b/test/findings/prompts-intent.spec.ts new file mode 100644 index 00000000..830c5edc --- /dev/null +++ b/test/findings/prompts-intent.spec.ts @@ -0,0 +1,129 @@ +import { describe, expect, it } from 'vitest'; +import { buildFileReviewPrompts, changelogExcerptFromDiff } from '@server/prompts/file-review'; +import { buildBatchReviewPrompts } from '@server/prompts/file-review'; +import { defaultRepoConfig } from '@codraoss/schema'; +import type { FileDiff } from '@server/core/diff'; + +function file(path: string, lines: Array<{ kind: 'add' | 'context'; content: string }>): FileDiff { + return { + path, + previousPath: null, + isNew: false, + isDeleted: false, + isBinary: false, + lineCount: lines.length, + hunks: [{ + header: '@@ -1,1 +1,1 @@', + lines: lines.map((line, i) => ({ ...line, newLineNumber: i + 1, position: i + 1 })), + }], + }; +} + +const codeFile = file('src/app.ts', [{ kind: 'add', content: 'const timeout = config.timeout;' }]); + +describe('the PR intent block', () => { + const build = (overrides: Partial[0]> = {}) => + buildFileReviewPrompts({ + file: codeFile, + prTitle: 'Drop the retry loop', + prDescription: 'Retries were masking a real upstream outage.', + config: defaultRepoConfig.review, + ...overrides, + }).userPrompt; + + it('states the title and description under one labelled heading', () => { + const prompt = build(); + + expect(prompt).toContain('## PR INTENT (what the author set out to do)'); + expect(prompt).toContain('Title: Drop the retry loop'); + expect(prompt).toContain('Retries were masking a real upstream outage.'); + expect(prompt.indexOf('## PR INTENT')).toBeLessThan(prompt.indexOf('Unified diff')); + }); + + it('makes the intent comparison part of the finding contract', () => { + const prompt = build(); + + expect(prompt).toMatch(/Intent check/); + expect(prompt).toMatch(/If what you are about to flag IS the stated intent, it is not a finding/); + }); + + it('still names the PR when there is no title and no description', () => { + const prompt = build({ prTitle: null, prDescription: null }); + + expect(prompt).toContain('Title: Untitled PR'); + expect(prompt).not.toContain('Description:'); + expect(prompt).not.toContain('Changelog lines added by this PR:'); + }); + + it('sits in the batch preamble without adding a file segment or a language line', () => { + const files = [codeFile, file('main.py', [{ kind: 'add', content: 'x = 1' }])]; + const { userPrompt } = buildBatchReviewPrompts({ + files, + prTitle: 'Drop the retry loop', + prDescription: 'Retries were masking a real upstream outage.', + changelogExcerpt: '- Removed the retry loop.', + config: defaultRepoConfig.review, + }); + + const segments = userPrompt.split('===== FILE '); + expect(segments).toHaveLength(files.length + 1); + expect(segments[0]).toContain('## PR INTENT'); + expect(segments[0]).toContain('- Removed the retry loop.'); + expect(userPrompt.match(/^Language: /gm)).toHaveLength(files.length); + }); +}); + +describe('changelogExcerptFromDiff', () => { + const changelog = (lines: Array<{ kind: 'add' | 'context'; content: string }>) => + file('CHANGELOG.md', lines); + + it('takes only the lines the PR added', () => { + const excerpt = changelogExcerptFromDiff([ + codeFile, + changelog([ + { kind: 'context', content: '## 1.2.0 - an older release' }, + { kind: 'add', content: '## 1.3.0' }, + { kind: 'add', content: '- Removed the retry loop; failures now surface immediately.' }, + ]), + ]); + + expect(excerpt).toBe('## 1.3.0\n- Removed the retry loop; failures now surface immediately.'); + }); + + it('recognises release-notes files by convention, not extension', () => { + for (const path of ['docs/RELEASE_NOTES.rst', 'History.md', 'packages/x/changes.txt']) { + const excerpt = changelogExcerptFromDiff([file(path, [{ kind: 'add', content: '- a change' }])]); + expect(excerpt).toBe('- a change'); + } + }); + + it('returns null when the PR touched no changelog', () => { + expect(changelogExcerptFromDiff([codeFile])).toBeNull(); + expect(changelogExcerptFromDiff([])).toBeNull(); + expect(changelogExcerptFromDiff([changelog([{ kind: 'context', content: '## 1.2.0' }])])).toBeNull(); + }); + + it('bounds a wholesale rewrite', () => { + const excerpt = changelogExcerptFromDiff([ + changelog(Array.from({ length: 200 }, (_, i) => ({ kind: 'add' as const, content: `- change number ${i}` }))), + ]); + + expect(excerpt).not.toBeNull(); + expect(excerpt!.length).toBeLessThanOrEqual(600); + expect(excerpt).toContain('- change number 0'); + expect(excerpt).not.toContain('- change number 199'); + }); + + it('reaches the prompt as its own labelled line', () => { + const prompt = buildFileReviewPrompts({ + file: codeFile, + prTitle: 'Drop the retry loop', + prDescription: null, + changelogExcerpt: '- Removed the retry loop.', + config: defaultRepoConfig.review, + }).userPrompt; + + expect(prompt).toContain('Changelog lines added by this PR:'); + expect(prompt).toContain('- Removed the retry loop.'); + }); +}); diff --git a/test/review/chunk-concurrency.spec.ts b/test/review/chunk-concurrency.spec.ts index 4f1ed005..d6149320 100644 --- a/test/review/chunk-concurrency.spec.ts +++ b/test/review/chunk-concurrency.spec.ts @@ -54,4 +54,12 @@ describe('budgetAwareFileLimit', () => { it('shrinks the chunk sooner on a long chain than a short one at a degraded budget', () => { expect(budgetAwareFileLimit(12, maxLevel, 9)).toBeLessThan(budgetAwareFileLimit(12, maxLevel, 1)); }); + + it('prices the extra content fetch without ever driving the limit to zero', () => { + expect(estimatedSubrequestsPerFile(3, true)).toBe(estimatedSubrequestsPerFile(3) + 1); + expect(estimatedSubrequestsPerFile(3, false)).toBe(estimatedSubrequestsPerFile(3)); + + const fresh = new TokenTracker().remainingSafeBudget(); + expect(budgetAwareFileLimit(fresh, maxLevel, 3, true)).toBeGreaterThanOrEqual(1); + }); }); From 0d361db6f1297fd6ee6972fd03578950ac6ea160 Mon Sep 17 00:00:00 2001 From: Devarshi Shimpi Date: Wed, 19 Aug 2026 08:37:22 +0530 Subject: [PATCH 3/7] refactor: trim verbose comments across codebase Condenses multi-line comments to one-liners (or drops redundant ones); no behavior changes. --- apps/worker/src/index.ts | 21 +- eslint.config.js | 61 +---- packages/api/src/routes/api/jobs.ts | 27 +-- .../src/internal/model-chain-progress.ts | 46 ++-- .../models/src/internal/model-rate-limits.ts | 26 +-- .../models/src/internal/model-review-chain.ts | 56 ++--- .../models/src/internal/model-review-file.ts | 36 +-- packages/models/src/internal/model-support.ts | 22 +- packages/models/src/limits.ts | 29 +-- packages/models/src/providers/google.ts | 40 ++-- packages/models/src/runner.ts | 40 ++-- packages/models/src/types.ts | 26 +-- .../models/test/model/batch-routing.spec.ts | 20 +- .../test/model/chain-progress-store.spec.ts | 22 +- .../models/test/model/chain-resume.spec.ts | 23 +- packages/models/test/model/limits.spec.ts | 23 +- .../test/model/service-fallbacks.spec.ts | 36 +-- .../model/service-grammar-rejection.spec.ts | 31 +-- packages/models/test/url-guard.spec.ts | 22 +- .../src/components/motion/smooth-scroll.tsx | 24 +- packages/ui/src/lib/prompt-diff.ts | 26 +-- scripts/outdated-rate.ts | 25 +- src/client/app.css | 216 ++++-------------- .../features/job-detail/job-chips.tsx | 22 +- src/client/components/layout/app-shell.tsx | 22 +- src/client/lib/timezone.ts | 24 +- test/db/bulk-upsert.spec.ts | 23 +- test/findings/evidence-grounding.spec.ts | 40 ++-- test/findings/prompts-file-review.spec.ts | 24 +- test/findings/review-verify.spec.ts | 27 +-- test/findings/undecidable-claims.spec.ts | 33 +-- test/jsonb-encoding.spec.ts | 23 +- test/mocks/fixtures.ts | 17 +- test/review/async-batch.spec.ts | 22 +- test/review/flow-lifecycle.spec.ts | 29 +-- test/review/pipeline-regression.spec.ts | 31 +-- test/review/quota-deferral.spec.ts | 25 +- test/review/subrequest-completion.spec.ts | 28 +-- .../workflow-finalize-fresh-instance.spec.ts | 19 +- test/timezone-format.spec.ts | 21 +- test/token-tracker.spec.ts | 19 +- 41 files changed, 362 insertions(+), 985 deletions(-) diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index 71a8425c..f63f12bf 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -23,10 +23,7 @@ export default { }, async scheduled(_controller: ScheduledController, env: AppBindings, _ctx: ExecutionContext) { - // The cron fires every 2 minutes but only does maintenance (recovering stuck jobs, finishing - // check runs). Touching Postgres every tick would keep the serverless DB awake 24/7, so gate on - // a KV flag set whenever a job is created/claimed and cleared once nothing is left to maintain - // -- when it's absent we return without ever opening a DB connection. + // Gate on KV flag: avoids waking the serverless DB every 2min tick when nothing's pending. try { const active = await env.APP_KV.get('system:active_jobs'); if (!active) { @@ -38,8 +35,7 @@ export default { return runWithDb(env, async () => { await runBestEffortJobMaintenance(env); - // Drop the flag as soon as nothing is left to maintain, so the next tick skips Postgres - // instead of waiting out the 20-minute TTL; a new job re-sets it on insert/claim. + // Clear flag early so next tick skips DB instead of waiting for TTL. try { if (!(await hasPendingMaintenanceWork(env))) { await clearSystemActive(env); @@ -58,9 +54,7 @@ export default { logger.error('Pre-batch maintenance task failed', error instanceof Error ? error : new Error(String(error))); } - // Sequential by design: each iteration creates a Workflow instance (a subrequest), and a - // batch can carry enough messages that fanning out would breach the Workers simultaneous- - // subrequest cap on the Free plan. + // Sequential: parallel fan-out could breach the Free plan subrequest cap. for (const message of batch.messages) { const parseResult = reviewJobMessageSchema.safeParse(message.body); @@ -69,9 +63,7 @@ export default { body: message.body, error: parseResult.error.flatten(), }); - // A malformed message can't be processed and retrying won't help, so ack it -- but if it - // still carries a recognizable jobId, fail that job so it doesn't sit 'queued' forever - // (lease recovery only revives 'running' rows). + // Ack (retry won't help); fail the job too, since lease recovery only revives 'running' rows. const strandedId = (message.body as { jobId?: unknown })?.jobId; if (typeof strandedId === 'string' && /^[0-9a-f-]{36}$/i.test(strandedId)) { try { @@ -87,10 +79,7 @@ export default { const { jobId, deliveryId, forceFreshInstance } = parseResult.data; try { - // Recovery re-enqueues a stuck job under its original jobId; keying the instance on jobId - // would collide with the dead instance (instance.already_exists), so recovery sets - // forceFreshInstance to key the new instance on the (fresh) deliveryId -- a UUID, - // matching workflow_instance_id's column type. + // forceFreshInstance keys on deliveryId (UUID) to avoid instance.already_exists on the dead jobId-keyed instance. const id = forceFreshInstance ? deliveryId : (jobId ?? deliveryId); if (!id) { logger.error('Message missing identifiers; dropping', { body: message.body }); diff --git a/eslint.config.js b/eslint.config.js index b23885d2..d2cd9241 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -7,11 +7,9 @@ import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescrip export default tseslint.config( { ignores: [ - // `**/` matters: a bare `dist/**` only covers the root build output, so emitted .d.ts under - // packages/*/dist was being linted as source. + // `**/` needed: bare `dist/**` misses packages/*/dist, linting emitted .d.ts as source. '**/dist/**', - '**/node_modules/**','**/.wrangler/**', - // Generated by `wrangler types`. + '**/node_modules/**','**/.wrangler/**', 'apps/worker/src/worker-env.d.ts', 'worker-configuration.d.ts', ], @@ -29,41 +27,29 @@ export default tseslint.config( ], }, rules: { - // TypeScript resolves every identifier already, and does it correctly for types, `declare`, - // and the Worker/DOM lib globals. Leaving this on means re-declaring hundreds of ambient - // globals in ESLint just to get a worse version of a check `npm run typecheck` already runs. + // TS resolves identifiers correctly (incl. types, `declare`, lib globals); this is redundant and worse. 'no-undef': 'off', - // The base rule cannot see TypeScript's type-only positions; the TS one can. 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error', { - // `catch {}` is the preferred form, but an unused binding is not worth an error. caughtErrors: 'none', argsIgnorePattern: '^_', varsIgnorePattern: '^_', }], - // `import-x/no-duplicates` and NOT the core `no-duplicate-imports`: the core rule is type-blind - // and flags the deliberate `import { Hono }` + `import type { Context }` split as a duplicate. + // Not core `no-duplicate-imports`: it's type-blind, flags `import {X}` + `import type {Y}` split as dupe. 'import-x/no-duplicates': 'error', 'import-x/no-self-import': 'error', 'import-x/no-cycle': 'error', - // 400 lines, counting neither blanks nor comments, so adding an explanation never pushes a file - // over. One file carries an explicit override below, for a stated reason -- a second should - // be a split, not a second override. 'max-lines': ['error', { max: 400, skipBlankLines: true, skipComments: true }], - // An error, not a warning: the three places whose dependency array is deliberately narrower - // than their closure now carry a line-level disable stating why. A new violation should fail. 'react-hooks/exhaustive-deps': 'error', - // Fires on the finding-title normalizer, which strips emoji and variation selectors from model - // output. Those combining characters are the point of it, and its behaviour is pinned by tests. + // Fires on emoji/variation-selector stripping in finding-title normalizer; that's intentional, test-pinned. 'no-misleading-character-class': 'off', - // `any` is used deliberately at the provider and DB boundaries, where the shape is genuinely - // unknown until it is parsed. Turning this on would mean ~100 suppressions, not better types. + // Deliberate at provider/DB boundaries with unknown shape until parsed; ~100 suppressions otherwise. '@typescript-eslint/no-explicit-any': 'off', }, }, @@ -73,8 +59,7 @@ export default tseslint.config( rules: { 'react-hooks/rules-of-hooks': 'error', - // The zone block at the bottom of this file cannot express this direction: its `files` is - // packages/** + apps/**, so a violation living in src/client is never linted by it. + // Zone block at file bottom only covers packages/**+apps/**, so it can't catch this direction. 'import-x/no-restricted-paths': ['error', { zones: [ { @@ -88,18 +73,7 @@ export default tseslint.config( }, { - // These specifiers are intercepted BY STRING in test mocks (`vi.mock('@server/db/jobs', ...)`). - // Each one is free to split into sibling files internally, but every other module must keep - // importing the barrel path -- importing a sibling directly bypasses whichever spec mocks the - // barrel, and the test keeps passing while asserting nothing. - // - // Every group MUST list the `@alias/...` form, not just `**/dir/...`. Under the tsconfig paths - // (`@shared/*` -> src/shared/*) the specifier a consumer actually writes is `@shared/schema-claims`, - // whose segments are ["@shared", "schema-claims"] -- there is no literal `shared` segment for - // `**/shared/` to match, so that pattern silently matched nothing at all. `@server/*` groups - // happen to work because `db`/`core`/`review` survive as real segments, but spell both forms out - // rather than relying on that. Probe any new pattern with: - // echo "import '';" | npx eslint --stdin --stdin-filename src/server/probe.ts + // vi.mock() intercepts these specifiers by string; list both `@alias/...` and `**/dir/...` forms since sibling imports and tsconfig-alias imports otherwise bypass it. files: ['src/**/*.{ts,tsx}', 'test/**/*.{ts,tsx}'], rules: { 'no-restricted-imports': ['error', { @@ -108,8 +82,6 @@ export default tseslint.config( { group: ['**/db/file-reviews-*', '@server/db/file-reviews-*'], message: 'Import from @server/db/file-reviews, not a sibling. (No spec mocks this one today; the rule keeps the barrel the single entry point.)' }, { group: ['**/services/model-review-*', '**/services/model-rate-limits', '**/services/model-chain-runner', '**/services/model-support', '@codraoss/models-*'], message: 'Import from @codraoss/models, not a sibling. Four specs vi.mock that specifier.' }, { group: ['**/core/github/http', '**/core/github/app-auth', '**/core/github/types', '**/core/github/diff-fetch', '**/core/github/review-post', '**/core/github/labels', '@server/core/github/http', '@server/core/github/app-auth', '@server/core/github/types', '@server/core/github/diff-fetch', '@server/core/github/review-post', '@server/core/github/labels'], message: 'Import from @server/core/github, not a sibling. One spec vi.mocks that specifier. (core/github/oauth is deliberately NOT listed: it is the dashboard OAuth flow, not part of the GitHubClient barrel, and routes/auth.ts imports it directly.)' }, - // Covers every sibling in the family, including the three the barrel re-exports publicly - // (budget, diff-cache, request) which were previously unprotected. { group: ['**/core/review/*', '@server/core/review/*', '@codraoss/core/review/*'], message: 'Import from @server/core/review, not a sibling. One spec vi.mocks that specifier and workflows/review.ts imports only runReviewJob from it.' }, { group: ['**/core/model-output/*', '@server/core/model-output/*', '@codraoss/core/model-output/*'], message: 'Import from @codraoss/core/model-output, not a sibling. (The package exports map already refuses to resolve these; the lint rule gives the error at edit time.)' }, { group: ['**/core/diff/position', '@server/core/diff/position', '@codraoss/core/diff/position'], message: 'Import from @codraoss/core/diff, not a sibling.' }, @@ -119,12 +91,7 @@ export default tseslint.config( }, }, { - // The one file still over the limit, for a stated reason. Known work, not a permanent - // carve-out -- delete the entry rather than raising `max` when it is split. - // - // test/api/auth.spec.ts (422): the review-settings suites here read-modify-write the same - // singleton `global_settings` row set and race across files once `fileParallelism` is on. See - // the DO-NOT-SPLIT header on the file itself. + // auth.spec.ts (422 lines): suites read-modify-write singleton global_settings, racing under fileParallelism (see DO-NOT-SPLIT header there). Delete entry, don't raise max, once split. files: ['test/api/auth.spec.ts'], rules: { 'max-lines': 'off', @@ -132,15 +99,11 @@ export default tseslint.config( }, { - // The barrel files themselves are the one place allowed to import their own siblings. files: [ 'src/server/db/jobs.ts', 'src/server/db/file-reviews.ts', 'src/server/services/model.ts', 'src/server/core/github/index.ts', - // core/review, core/diff and core/model-output are gone from here: they moved to @codraoss/core and - // what is left at those paths is a re-export shim with no sibling imports to exempt. ESLint does - // not warn about `files` patterns that match nothing, so a stale entry would just rot quietly. 'packages/schema/src/schema.ts', ], rules: { @@ -149,7 +112,6 @@ export default tseslint.config( }, { - // Plain-JS scripts are not covered by tsconfig, so they need their globals declared. files: ['scripts/**/*.{js,mjs}'], languageOptions: { globals: { @@ -171,10 +133,7 @@ export default tseslint.config( 'import-x/no-restricted-paths': ['error', { zones: [ { - // `src/**` in `from` is what actually holds the extraction in place. The zones below - // only ever described packages -> packages traffic, so nothing stopped a moved file from - // keeping its old `@server/db/jobs` import and quietly re-coupling the package to the - // Worker tree. Traffic goes src -> packages, through src/server/adapters, never back. + // `src/**` in `from` catches a moved file that kept its old `@server/*` import (re-coupling). target: 'packages/schema/**/*', from: ['src/**/*', 'test/**/*', 'scripts/**/*', 'packages/core/**/*', 'packages/provider-github/**/*', 'packages/db/**/*', 'packages/models/**/*', 'packages/api/**/*', 'packages/ui/**/*', 'apps/worker/**/*', 'apps/dashboard/**/*'] }, diff --git a/packages/api/src/routes/api/jobs.ts b/packages/api/src/routes/api/jobs.ts index 221a7dd4..721d9859 100644 --- a/packages/api/src/routes/api/jobs.ts +++ b/packages/api/src/routes/api/jobs.ts @@ -6,12 +6,9 @@ import { parseUnifiedDiff } from '@codraoss/core/diff'; import { buildFileReviewPrompts, changelogExcerptFromDiff, wantsFileContext } from '@codraoss/core/prompts/file-review'; import type { ApiEnv } from '../../ports'; -// Best-effort terminate; .get() throws if the instance is gone and .terminate() if already terminal, both non-fatal. +// Best-effort: .get()/.terminate() throw if instance is gone/already terminal; both non-fatal. async function terminateJobWorkflow(c: Context, job: { id: string; workflowInstanceId?: string | null }) { - // This interacts with bindings. The plan says "No binding access in a handler". - // So we need to put workflow termination behind a platform port. - // Wait! The user plan says: "call a packages/core use case". - // Let's add it to `platform` port as well. + // Goes through platform port; handlers must not touch bindings directly. await c.env.deps.platform.terminateJobWorkflow(job); } @@ -67,7 +64,7 @@ export function createJobsRouter() { return response; }); - // diff_input is not persisted; rebuilt on demand from the job's own base/head commits (not the live PR), using the KV cache while warm. + // diff_input isn't persisted; rebuilt from the job's own base/head commits (not the live PR), via KV cache. app.get('/:id/diffs', async (c) => { const job = await c.env.deps.repositories.jobs.getJobDetail(c.env as any, c.req.param('id')); if (!job) { @@ -85,22 +82,21 @@ export function createJobsRouter() { github, ); } catch (error) { - // Need logger from deps or imported directly since we moved it c.env.deps.platform.logger.warn(`Could not reconstruct diff for job ${job.id}`, error instanceof Error ? error : new Error(String(error))); return c.json({ diffs: {} }); } - // Must include the PR description: this reconstructs the prompt the model actually saw. + // Reconstructs the prompt the model actually saw, so it needs the PR description too. let prDescription: string | null = null; try { prDescription = (await github.getPullRequest(job.owner, job.repo, job.prNumber)).body ?? null; } catch (error) { - // Best-effort: a missing description degrades fidelity, never fails the view. + // Best-effort: missing description degrades fidelity, doesn't fail the view. c.env.deps.platform.logger.warn(`Could not load the PR body for job ${job.id}; prompts will omit the description`, error instanceof Error ? error : new Error(String(error))); } - // The ENTIRE PR diff, not just files with a review row, so Files-changed matches GitHub mid-review. + // Full PR diff, not just reviewed files, so file count matches GitHub mid-review. const diffs: Record = {}; const parsedFiles = parseUnifiedDiff(rawDiff, config.review); const changelogExcerpt = changelogExcerptFromDiff(parsedFiles); @@ -124,7 +120,7 @@ export function createJobsRouter() { return response; }); - // Shared by re-run and rerun-from-start; inherit=true links retryOfJobId and reuses `done` file reviews, false reviews everything. + // inherit=true links retryOfJobId and reuses done file reviews; false reviews everything. async function startReplacementJob(c: Context, rawSource: any, options: { inherit: boolean }) { const jobs = c.env.deps.repositories.jobs; const source = jobs.mapJob(rawSource); @@ -174,7 +170,6 @@ export function createJobsRouter() { return job; } - // Re-run: reuse the parent's completed reviews where the model strategy still matches. app.post('/:id/retry', async (c) => { const jobs = c.env.deps.repositories.jobs; const rawSource = await jobs.getJobForProcessing(c.env as any, c.req.param('id')); @@ -185,7 +180,7 @@ export function createJobsRouter() { return c.json({ job }, 202); }); - // Rerun from start: no inheritance. Stops the current run so two workflows cannot race. + // No inheritance; stops the current run first so two workflows can't race. app.post('/:id/rerun', async (c) => { const jobs = c.env.deps.repositories.jobs; const rawSource = await jobs.getJobForProcessing(c.env as any, c.req.param('id')); @@ -217,7 +212,7 @@ export function createJobsRouter() { return c.json({ job: updated ? jobs.mapJob(updated) : job }, 200); }); - // Human verdict on one finding: WRONG suppresses repository-wide, RIGHT suppresses nothing and is purely measurement. + // "wrong" suppresses this finding repo-wide; "right" is measurement only. app.put('/:id/findings/:fingerprint/label', async (c) => { const jobId = c.req.param('id'); const fingerprint = c.req.param('fingerprint'); @@ -233,7 +228,7 @@ export function createJobsRouter() { prNumber: target.pr_number, fingerprint, anchorHash: target.anchor_hash, - // Carried so a rejection survives the model rewording its title. + // Keeps rejection valid even if the model rewords the title. fingerprintV2: target.fingerprint_v2, jobId, labelledBy: c.get('sessionUser')?.providerUserId ? Number(c.get('sessionUser')?.providerUserId) : null, @@ -243,7 +238,7 @@ export function createJobsRouter() { return c.json({ label: parsed.data.label }, 200); }); - // Undo a label, scoped to dashboard rows so a real GitHub deletion stays recorded. + // Scoped to dashboard rows; a real GitHub deletion still stays recorded. app.delete('/:id/findings/:fingerprint/label', async (c) => { const jobId = c.req.param('id'); const fingerprint = c.req.param('fingerprint'); diff --git a/packages/models/src/internal/model-chain-progress.ts b/packages/models/src/internal/model-chain-progress.ts index c51fb072..e534337c 100644 --- a/packages/models/src/internal/model-chain-progress.ts +++ b/packages/models/src/internal/model-chain-progress.ts @@ -3,19 +3,16 @@ import type { KvStore } from '@codraoss/core/ports'; import type { TokenTracker } from '@codraoss/core/token-tracker'; import { isPlausibleTokenBucket } from './model-support'; -// Stores where each label got to in its model chain so deferred reviews resume properly. -// Stored as a single KV value per job to minimize subrequest budget overhead. - -// Outlives job continuations; key is job-scoped and dies with the job. +// One KV value per job to limit subrequests. const CHAIN_PROGRESS_TTL_SECONDS = 24 * 60 * 60; -// Allowed timeouts per model before dropping it from the job (3 = one full wave). +// 3 = one full wave. const MODEL_TIMEOUT_STRIKES = 3; -// Strikes before dropping the LAST chain candidate. Finite (6) to avoid infinite retries on dead models. +// Finite to avoid infinite retries on dead models. const LAST_CANDIDATE_TIMEOUT_STRIKES = 6; -// Max persisted cool-off (5 mins) protects against mis-parsed long delays. +// Caps mis-parsed long delays. const MAX_PERSISTED_COOLDOWN_MS = 5 * 60 * 1000; export interface ModelCooldown { @@ -39,7 +36,7 @@ function positiveInts(source: Record | undefined): Map | undefined): Map { const kept = new Map(); if (!source || typeof source !== 'object') return kept; @@ -48,7 +45,7 @@ function parseCooldowns(source: Record | undefined): Map for (const [model, value] of Object.entries(source)) { if (!value || typeof value !== 'object') continue; const until = typeof value.until === 'number' && Number.isFinite(value.until) ? value.until : 0; - // Drops implausible limit sizes to prevent indefinite model suppression. + // Drop implausible limits; avoids indefinite suppression. const limitTokens = typeof value.limitTokens === 'number' && isPlausibleTokenBucket(value.limitTokens) ? value.limitTokens @@ -61,26 +58,21 @@ function parseCooldowns(source: Record | undefined): Map function mergeCooldown(a: ModelCooldown | undefined, b: ModelCooldown): ModelCooldown { return { - // Idempotent max() merge for advancing deadlines. cooldownUntil: Math.max(a?.cooldownUntil ?? 0, b.cooldownUntil), - // Sticky limit retention on subsequent 429s. + // Sticky across repeated 429s. limitTokens: a?.limitTokens ?? b.limitTokens, }; } export class ModelChainProgressStore { private loaded: Promise> | null = null; - - // Per-model timeouts stored alongside chain progress to save subrequests. private timeouts = new Map(); - // Cleared strikes in this invocation, preventing max() merges from resurrecting them. + // Prevents max() merge from resurrecting cleared strikes. private clearedTimeouts = new Set(); - - // Persisted rate-limits to avoid re-paying 429 prompts across invocations. private cooldowns = new Map(); - // Single-flight writer prevents overlapping KV puts from dropping concurrent updates. + // Single-flight write; concurrent KV puts must not drop updates. private inFlightWrite: Promise | null = null; private dirty = false; @@ -94,7 +86,6 @@ export class ModelChainProgressStore { return this.jobId ? `jobs:${this.jobId}:chain-progress` : null; } - // Job-less reviews return 0 (nothing to resume). private load(): Promise> { if (this.loaded) return this.loaded; @@ -107,12 +98,11 @@ export class ModelChainProgressStore { const raw = rawString ? JSON.parse(rawString) : null; if (!raw || typeof raw !== 'object') return new Map(); - // Legacy support: reads bare label->index maps as files for smooth deploys. + // Legacy: bare label->index maps read as files. const stored = raw as StoredShape; const isNewShape = stored.files !== undefined || stored.timeouts !== undefined || stored.cooldowns !== undefined; this.timeouts = positiveInts(isNewShape ? stored.timeouts : undefined); - // Merge sync noteRateLimit calls. for (const [model, value] of parseCooldowns(isNewShape ? stored.cooldowns : undefined)) { this.cooldowns.set(model, mergeCooldown(this.cooldowns.get(model), value)); } @@ -137,7 +127,6 @@ export class ModelChainProgressStore { if (!this.key || nextIndex <= 0) return; const progress = await this.load(); - // Monotonic advance only. if ((progress.get(label) ?? 0) >= nextIndex) return; progress.set(label, nextIndex); this.dirty = true; @@ -145,14 +134,11 @@ export class ModelChainProgressStore { return this.flush(); } - // Ensure progress durability before deferring. private flush(): Promise { - // Join in-flight writes. if (this.inFlightWrite) return this.inFlightWrite; this.inFlightWrite = (async () => { try { - // Process mid-put advances. while (this.dirty) { this.dirty = false; await this.writeOnce(); @@ -171,7 +157,7 @@ export class ModelChainProgressStore { const progress = await this.load(); try { - // Merge against remote KV state using max() to prevent concurrent invocations from dropping labels. + // Merge via max() so concurrent invocations don't drop labels. this.tracker?.incrementSubrequests(1); const rawString = await this.kv.get(key); const raw = rawString ? JSON.parse(rawString) : null; @@ -215,13 +201,11 @@ export class ModelChainProgressStore { } } - // Clears terminal labels for clean retries. async clear(label: string): Promise { const progress = await this.load(); progress.delete(label); } - // Persist timeouts so subsequent waves can skip failing models. async noteTimeout(modelId: string): Promise { if (!this.key) return; await this.load(); @@ -230,11 +214,9 @@ export class ModelChainProgressStore { return this.flush(); } - // Reset tallies on success to avoid false permanent bans. async noteSuccess(modelId: string): Promise { if (!this.key) return; await this.load(); - // No-op for healthy models to save subrequests. if (!this.timeouts.has(modelId)) return; this.timeouts.delete(modelId); this.clearedTimeouts.add(modelId); @@ -247,26 +229,24 @@ export class ModelChainProgressStore { return (this.timeouts.get(modelId) ?? 0) >= MODEL_TIMEOUT_STRIKES; } - // For chain tails with no fallback. + // Chain tails have no fallback. async isTimingOutTerminally(modelId: string): Promise { await this.load(); return (this.timeouts.get(modelId) ?? 0) >= LAST_CANDIDATE_TIMEOUT_STRIKES; } - // Share load() promise to save KV reads. async loadCooldowns(): Promise> { await this.load(); return new Map(this.cooldowns); } - // Sync/non-flushing. Deferrals call flushPending() to coalesce writes. + // Sync; flushPending() coalesces writes. noteRateLimit(modelId: string, entry: ModelCooldown): void { if (!this.key) return; this.cooldowns.set(modelId, mergeCooldown(this.cooldowns.get(modelId), entry)); this.dirty = true; } - // Flush mutations without advancing progress (e.g. quota deferrals). flushPending(): Promise { if (!this.key || !this.dirty) return Promise.resolve(); return this.flush(); diff --git a/packages/models/src/internal/model-rate-limits.ts b/packages/models/src/internal/model-rate-limits.ts index e4434f51..16a5c473 100644 --- a/packages/models/src/internal/model-rate-limits.ts +++ b/packages/models/src/internal/model-rate-limits.ts @@ -3,31 +3,24 @@ import { ModelCallGate } from '../limits'; import type { ResolvedModelConfig } from '@codraoss/schema'; import { MAX_METERED_QUEUE_DEPTH, PROMPT_FIT_SAFETY_FACTOR, parseRateLimitFromError } from './model-support'; -// Narrow port onto whatever survives an invocation (today: the job's chain-progress KV value), so -// this class stays unit-testable without an env and the barrel surface is unchanged. export interface RateLimitPersistence { loadCooldowns(): Promise>; noteRateLimit(modelId: string, entry: { cooldownUntil: number; limitTokens?: number }): void; } -// Import from the services/model barrel, not here (four specs vi.mock it). export class ModelRateLimitBook { - // Workers allows 6 simultaneous connections per invocation; gating starts the client timeout once a slot is held, instead of while queued. + // Slot held before client timeout starts (Workers: 6 concurrent conns/invocation). private readonly callGate = new ModelCallGate(); - // Google's 429 body carries both bucket and cool-off, so parsing it keeps this adaptive with no baked-in numbers. private readonly modelRateLimits = new Map(); - // Keyed by model, not provider: keying by provider serialized every call in an all-Google chain, dropping concurrency and throughput. + // Keyed by model, not provider, so an all-Google chain doesn't serialize. private readonly tokenMeteredModels = new Map(); - // Memoized so the KV read is shared, not repeated per model per file. private hydrated: Promise | null = null; constructor(private readonly persistence?: RateLimitPersistence) {} - // Without this the book is invocation-scoped: every job continuation re-paid a full-prompt 429 to - // re-learn a cool-off the previous invocation had already been told about. private hydrate(): Promise { this.hydrated ??= (async () => { if (!this.persistence) return; @@ -38,8 +31,7 @@ export class ModelRateLimitBook { cooldownUntil: Math.max(existing?.cooldownUntil ?? 0, entry.cooldownUntil), }); - // A model with a known bucket is token-metered, so serialize it from the FIRST call rather - // than after this invocation re-earns its own 429. + // Serialize from the first call, not after re-earning our own 429. if (!this.tokenMeteredModels.has(modelName)) { this.tokenMeteredModels.set(modelName, new ModelCallGate(1)); } @@ -48,15 +40,12 @@ export class ModelRateLimitBook { return this.hydrated; } - // Deliberately does NOT wait out the cool-off: the file falls through to the next model immediately, trading share of files for wall-clock. note(resolved: ResolvedModelConfig, error: unknown) { const { limitTokens, retryAfterMs } = parseRateLimitFromError(error); const existing = this.modelRateLimits.get(resolved.modelName); const entry = { - // Sticky: a later 429 that omits the number must not erase it. - limitTokens: limitTokens ?? existing?.limitTokens, - // Default to a minute when the provider didn't say -- these buckets are per-minute. + limitTokens: limitTokens ?? existing?.limitTokens, // sticky: a later 429 without a number can't erase it cooldownUntil: Date.now() + (retryAfterMs ?? 60_000), }; this.modelRateLimits.set(resolved.modelName, entry); @@ -71,12 +60,9 @@ export class ModelRateLimitBook { } } - // Avoids re-probing a model that already said "retry in Ns" once per file, which is what produced "Too many subrequests". async skipReason(modelName: string, estimatedPromptTokens: number): Promise { - // Cool-offs learned by an earlier invocation of this job count too. await this.hydrate(); - // Never queue deeply behind a serialized model; a shallow queue sends overflow to a free model instead. const gate = this.tokenMeteredModels.get(modelName); if (gate && gate.queueDepth >= MAX_METERED_QUEUE_DEPTH) { return `${gate.queueDepth} calls already queued on it`; @@ -89,7 +75,6 @@ export class ModelRateLimitBook { return `cooling off for another ${Math.ceil((known.cooldownUntil - Date.now()) / 1000)}s`; } - // A prompt larger than the whole per-minute bucket can never succeed, however long we wait. if (known.limitTokens && estimatedPromptTokens > known.limitTokens * PROMPT_FIT_SAFETY_FACTOR) { return `prompt ~${estimatedPromptTokens} tokens exceeds its ${known.limitTokens}-token bucket`; } @@ -97,12 +82,11 @@ export class ModelRateLimitBook { return null; } - // 6-connection cap only, no per-model gate: async-batch submit/poll isn't the token-metered path. async runShared(fn: () => Promise): Promise { return this.callGate.run(fn); } - // Model gate then shared gate, in that order, to rule out deadlock. + // Model gate before shared gate, in that order, to rule out deadlock. async runGated( resolved: ResolvedModelConfig, onGateWait: ((waitedMs: number) => void) | undefined, diff --git a/packages/models/src/internal/model-review-chain.ts b/packages/models/src/internal/model-review-chain.ts index 2f6b7ce5..cc3d6ee7 100644 --- a/packages/models/src/internal/model-review-chain.ts +++ b/packages/models/src/internal/model-review-chain.ts @@ -15,37 +15,29 @@ import type { ModelChainContext } from './model-chain-runner'; import type { ModelRateLimitBook } from './model-rate-limits'; import type { ModelChainProgressStore } from './model-chain-progress'; -// Fallback chain for single/batched reviews. Import from services/model barrel. - // Past two quota failures per file burns subrequests for nothing. const MAX_QUOTA_FAILURES_PER_FILE = 2; -// Internal per-invocation chain state. export type ModelReviewContext = ModelChainContext & { aiBinding?: CloudflareAiBinding; rateLimits: ModelRateLimitBook; - // Models lacking async batching; routes subsequent files directly to synchronous. asyncUnsupportedModels: Set; - // Per-job memo of chain progress for each label. chainProgress: ModelChainProgressStore; }; -// Walks chain returning first success. `parse` errors count as model failures. export async function runModelChain(ctx: ModelReviewContext, params: { systemPrompt: string; userPrompt: string; - // Accepts both single-file and batched response schemas. responseSchema: ModelResponseSchema; timeoutMs: number; label: string; totalLineCount: number; config: RepoConfig; - // Needed output tokens; adapters clamp it, omission defaults to provider ceiling. outputBudgetTokens?: number; truncationIntolerant?: boolean; - // `isLastModel` allows parsers to reject weak answers and try better models, accepting them only as a last resort. + // isLastModel: lets parsers reject weak answers except as a last resort. parse: (rawText: string, ctx: { isLastModel: boolean }) => T; - // Keys for resume memo. Bins pass member paths so progress survives mid-batch success/de-escalation. + // Bins pass member paths so progress survives mid-batch success/de-escalation. progressLabels?: readonly string[]; }) { const { systemPrompt, userPrompt, responseSchema, label, outputBudgetTokens, truncationIntolerant } = params; @@ -57,8 +49,7 @@ export async function runModelChain(ctx: ModelReviewContext, params: { }); const wholeChain = [primary, ...fallbacks]; - // Resumes from invocation memo. Clamped to prevent empty chains on mid-job config changes. - // Takes the MINIMUM across members so no file skips a model untried due to bin-mates. + // Resume index is the min across bin members, clamped so it can't empty the chain. const recorded = await Promise.all(progressLabels.map((key) => ctx.chainProgress.startIndexFor(key))); const startIndex = Math.min(Math.min(...recorded), Math.max(wholeChain.length - 1, 0)); const modelsToTry = wholeChain.slice(startIndex); @@ -69,7 +60,6 @@ export async function runModelChain(ctx: ModelReviewContext, params: { }); } - // Guards chain head; see clampTimeoutToChainBudget. const timeoutMs = clampTimeoutToChainBudget(params.timeoutMs); const estimatedPromptTokens = estimatePromptTokens(systemPrompt, userPrompt); @@ -78,24 +68,20 @@ export async function runModelChain(ctx: ModelReviewContext, params: { let lastTransientError: unknown; let sawTransientFailure = false; let quotaFailures = 0; - // Prevents undefined lastError from failing files permanently. let attemptedAnyModel = false; - // Distinguishes rate-limit skips from timeouts for job logs. let skippedForTimeouts = false; - // Advances memo past failed models only; skips/429s never rule out the current model. let attemptedFailedThrough = 0; const chainStartedAt = Date.now(); - // Excluded from call timeout so busy gates don't manifest as slow models. let gateWaitMs = 0; const recordGateWait = (waitedMs: number) => { gateWaitMs += waitedMs; }; for (const [modelIndex, currentModel] of modelsToTry.entries()) { - // Primary guaranteed; fallbacks near 50-subrequest cap defer. + // Subrequest cap: primary always runs, fallbacks defer once near the limit. if (modelIndex > 0 && ctx.tracker?.isNearLimit()) { logger.warn(`Skipping remaining fallback models for ${label}; subrequest budget for this invocation is nearly exhausted`, { skippedModels: modelsToTry.slice(modelIndex), }); - // If no transient failures, let permanent error propagate. Avoid 'subrequest' keyword to ensure write. + // Avoid the word 'subrequest' here so the message isn't misread as a runtime refusal. if (sawTransientFailure) { lastTransientError = lastTransientError ?? lastError ?? new Error('Per-invocation request budget was nearly exhausted before trying all configured fallback models'); } @@ -115,7 +101,6 @@ export async function runModelChain(ctx: ModelReviewContext, params: { timeoutMs, skippedModels: modelsToTry.slice(modelIndex), }); - // Defer to retry on a fresh budget. sawTransientFailure = true; lastTransientError = lastTransientError ?? lastError ?? new Error(`Model fallback chain for ${label} exceeded its time budget; deferring for retry.`); break; @@ -137,7 +122,6 @@ export async function runModelChain(ctx: ModelReviewContext, params: { continue; } - // Skip models timing out consistently. Last candidates use a higher threshold to avoid "no model attempted" errors, but still cut off eventually. const isLastCandidate = modelIndex === modelsToTry.length - 1; const timingOut = isLastCandidate ? await ctx.chainProgress.isTimingOutTerminally(currentModel) @@ -150,7 +134,7 @@ export async function runModelChain(ctx: ModelReviewContext, params: { continue; } - // Hard subrequest floor for ALL models (including primary) prevents prompt transmissions into depleted invocations. + // Floor applies even to the primary model, not just fallbacks. if (ctx.tracker && !ctx.tracker.hasRemainingSubrequests(SUBREQUEST_HEADROOM_FOR_MODEL_CALL)) { logger.warn(`Deferring ${label}: not enough subrequest budget left to commit a prompt`, { subrequests: ctx.tracker.getSubrequestCount(), @@ -158,13 +142,11 @@ export async function runModelChain(ctx: ModelReviewContext, params: { skippedModels: modelsToTry.slice(modelIndex), }); sawTransientFailure = true; - // Avoid 'subrequest' keyword so error isn't mistaken for runtime refusal. lastTransientError = lastTransientError ?? lastError ?? new Error(`Per-invocation request budget was too low to attempt a model for ${label}; deferring for retry.`); break; } - // Pre-flight check against known limits. const skipReason = await ctx.rateLimits.skipReason(resolved.modelName, estimatedPromptTokens); if (skipReason) { logger.info(`Skipping ${currentModel} for ${label}: ${skipReason}`); @@ -172,7 +154,6 @@ export async function runModelChain(ctx: ModelReviewContext, params: { continue; } - // No intra-model retries here; outages defer the file or fall to next model. try { attemptedAnyModel = true; const response = await ctx.callResolvedModel( @@ -186,20 +167,17 @@ export async function runModelChain(ctx: ModelReviewContext, params: { ctx.tracker.record(response.modelUsed, response.inputTokens, response.outputTokens); } - // Parse in try-block. `isLastModel` uses absolute chain length, protecting resumed jobs from premature acceptance. + // isLastModel uses the absolute chain index so resumed jobs don't accept prematurely. const parsed = params.parse(response.rawText, { isLastModel: startIndex + modelIndex >= wholeChain.length - 1, }); - // Clear strikes. No-op on healthy paths to avoid KV writes. await ctx.chainProgress.noteSuccess(currentModel); - // Terminal success; clear memo for clean future retries. await Promise.all(progressLabels.map((key) => ctx.chainProgress.clear(key))); - // Flush 429 cool-offs from earlier models in this chain to prevent re-paying them on next invocation. await ctx.chainProgress.flushPending(); return { ...response, userPrompt, parsed }; } catch (error) { const isLastModel = startIndex + modelIndex >= wholeChain.length - 1; - // Salvage the last rung's partial answer rather than lose the file outright. + // Salvage a truncated partial from the final model rather than fail outright. const partial = isLastModel ? partialResponseOf(error) : null; if (partial) { try { @@ -215,12 +193,11 @@ export async function runModelChain(ctx: ModelReviewContext, params: { await ctx.chainProgress.flushPending(); return { ...partial, userPrompt, parsed, degraded: 'truncated' as const }; } catch { - // no-op + // intentional no-op } } lastError = error; - // Record failed wire transmission. ctx.tracker?.recordFailedAttempt( resolved.modelName, estimatedPromptTokens, @@ -234,7 +211,7 @@ export async function runModelChain(ctx: ModelReviewContext, params: { await ctx.markProviderUnavailable(resolved.providerId, error instanceof Error ? error.message : String(error)); } - // Runtime refusal (out of subrequests). Aborts immediately to prevent cascading instant failures. Not recorded as chain progress so healthy models aren't skipped on retry. + // Out of subrequests: abort now (not recorded as progress, so healthy models aren't skipped on retry). if (isSubrequestBudgetMessage(error)) { logger.warn(`Aborting the model chain for ${label}; this invocation is out of subrequests`, { skippedModels: modelsToTry.slice(modelIndex + 1), @@ -244,7 +221,6 @@ export async function runModelChain(ctx: ModelReviewContext, params: { break; } - // Persist timeouts so subsequent waves avoid doomed models. if (isTimeoutMessage(String(error instanceof Error ? error.message : error).toLowerCase())) { await ctx.chainProgress.noteTimeout(currentModel); } @@ -253,18 +229,16 @@ export async function runModelChain(ctx: ModelReviewContext, params: { if (!rateLimited) attemptedFailedThrough = startIndex + modelIndex + 1; if (rateLimited) { quotaFailures += 1; - // Extract rate limit data to protect subsequent calls. ctx.rateLimits.note(resolved, error); } - // 429 defers rather than trying fallbacks to preserve subrequests. const outOfQuotaBudget = quotaFailures >= MAX_QUOTA_FAILURES_PER_FILE; logger.warn(`Model ${currentModel} failed for ${label}`, { error: error instanceof Error ? error.message : String(error), rateLimited, quotaFailures, - // `estimatedWastedInput` over `Tokens` since logger redacts 'token'. + // Named "Input" not "Tokens": the logger redacts fields containing "token". estimatedWastedInput: estimatedPromptTokens, willTryFallback: !outOfQuotaBudget && modelIndex < modelsToTry.length - 1, }); @@ -285,21 +259,18 @@ export async function runModelChain(ctx: ModelReviewContext, params: { retryCause, ); - // Advance progress past failed models for the retry. Skipped at chain's end so file retries start clean. + // Skipped once at chain's end, so a fresh retry starts clean rather than looping. if (attemptedFailedThrough > 0 && attemptedFailedThrough < wholeChain.length) { - // Coalesced writes to minimize KV get+puts from subrequest budget. await Promise.all(progressLabels.map((key) => ctx.chainProgress.advance(key, attemptedFailedThrough))); Object.defineProperty(error, 'nextChainIndex', { value: attemptedFailedThrough, configurable: true }); } else { - // Flush cool-offs learned before the quota deferral. await ctx.chainProgress.flushPending(); } throw error; } - // No model was called. Two very different reasons land here. if (!attemptedAnyModel) { - // Throw permanent operator errors (transient wrappers obscure root cause). + // Unwrap so a permanent operator error isn't hidden behind a transient wrapper. if (lastError !== undefined) { if (isTransientModelFailure(lastError)) { throw new RetryableModelError(`Every model for ${label} failed to resolve; retrying later.`, lastError); @@ -307,7 +278,6 @@ export async function runModelChain(ctx: ModelReviewContext, params: { throw lastError; } - // All models skipped via cool-downs, timeouts, or unavailability. throw new RetryableModelError( `No configured review model was attempted for ${label} (all skipped: ${ skippedForTimeouts ? 'repeated timeouts on this job' : 'rate-limit cooldown or provider unavailable' diff --git a/packages/models/src/internal/model-review-file.ts b/packages/models/src/internal/model-review-file.ts index c0de50da..df70817e 100644 --- a/packages/models/src/internal/model-review-file.ts +++ b/packages/models/src/internal/model-review-file.ts @@ -16,10 +16,10 @@ import { logger } from '@codraoss/core/logger'; import type { RepoConfig } from '@codraoss/schema'; import type { ModelResponse } from '../types'; -// Import from the services/model barrel, not here (four specs vi.mock it). +// vi.mock targets services/model barrel; import model from there, not here. export const COMPACT_REVIEW_PROMPT_LINE_CAP = 400; -// Budget required before a chunk past BASE_CHUNKS runs, so a tail chunk only spends while another whole file still fits. +// Reserve so tail chunks only run if budget still fits another whole file. const EXTRA_CHUNK_BUDGET_RESERVE = 8; export type { ModelReviewContext }; @@ -40,10 +40,8 @@ export async function reviewFile(ctx: ModelReviewContext, params: { : configuredLineCap; let chunks = chunkFileDiff(params.file, modelLineCap); - // Pre-cap count, so wasPromptTruncated doesn't re-run chunkFileDiff. const totalChunkCount = chunks.length; - // Past BASE_CHUNKS is opportunistic, only on spare budget. const BASE_CHUNKS = 4; const MAX_CHUNKS = 8; if (chunks.length > MAX_CHUNKS) { @@ -58,13 +56,13 @@ export async function reviewFile(ctx: ModelReviewContext, params: { const { path: filePath } = params.file; for (const [chunkIndex, chunk] of chunks.entries()) { - // No new chunk when close to the 50-subrequest limit. + // isNearLimit guards the ~50-subrequest cap. if (results.length > 0 && ctx.tracker?.isNearLimit()) { logger.warn(`Stopping chunk processing for ${filePath} early due to subrequest budget limits.`); break; } - // Needs spare budget, not merely remaining: isNearLimit() only fires once in-flight files are already starved. + // Needs spare budget, not just remaining: isNearLimit only trips once already starved. if (chunkIndex >= BASE_CHUNKS) { const remaining = ctx.tracker?.remainingSafeBudget() ?? Number.POSITIVE_INFINITY; if (remaining < EXTRA_CHUNK_BUDGET_RESERVE) { @@ -83,7 +81,7 @@ export async function reviewFile(ctx: ModelReviewContext, params: { results.push(res as any); } catch (error) { if (results.length === 0) { - throw error; // First chunk failed, let it defer/fail properly + throw error; } logger.warn(`Chunk review failed for ${filePath}, returning partial results to avoid stalling the job.`, { error: error instanceof Error ? error.message : String(error) }); break; @@ -91,7 +89,7 @@ export async function reviewFile(ctx: ModelReviewContext, params: { } const combinedFindings = results.flatMap(r => r.parsed.comments); - // Most serious chunk's verdict, not the last: a clean final chunk would mask earlier findings. + // Most serious verdict, not last: a clean final chunk would mask earlier findings. const primaryResult = results.find(r => r.parsed.verdict === 'comment') ?? results[results.length - 1]; return { @@ -101,7 +99,6 @@ export async function reviewFile(ctx: ModelReviewContext, params: { parsed: { ...primaryResult.parsed, comments: combinedFindings, - // Summed across chunks, or a truncated file under-reports the "N claims withheld" note. evidenceStats: results.reduce((acc, r) => ({ total: acc.total + (r.parsed.evidenceStats?.total ?? 0), matched: acc.matched + (r.parsed.evidenceStats?.matched ?? 0), @@ -123,7 +120,6 @@ export async function reviewFile(ctx: ModelReviewContext, params: { } -// Internal to this module: reviewFile fans out to it per chunk. async function reviewFileChunk(ctx: ModelReviewContext, params: { file: any; fileContext?: string | null; @@ -142,7 +138,6 @@ async function reviewFileChunk(ctx: ModelReviewContext, params: { rejectedExemplars: params.rejectedExemplars, }); - // One figure drives three things: the room the answer gets, and now the time it gets to write it. const outputBudgetTokens = reviewOutputBudgetTokens({ findingCap: reviewBreadth(params.config.review), fileCount: 1, @@ -152,10 +147,9 @@ async function reviewFileChunk(ctx: ModelReviewContext, params: { systemPrompt, userPrompt, responseSchema: buildReviewResponseSchema(reviewBreadth(params.config.review)), - // Scales with the diff the model sees AND the answer it was asked for: small files fail over fast. timeoutMs: adaptiveModelTimeoutMs(params.file.lineCount, outputBudgetTokens), outputBudgetTokens, - // A partial review reads as "no more findings", not "ran out of room" - treat as untrusted. + // Partial output reads as "no findings left", not truncation; treat as untrusted. truncationIntolerant: true, label: params.file.path, totalLineCount: params.totalLineCount, @@ -165,10 +159,7 @@ async function reviewFileChunk(ctx: ModelReviewContext, params: { deniedClaimTypes: params.config.review.deny_claim_types, }); - // A substantive diff waved through in one sentence is not a clean verdict, it is a model declining - // to review. Thrown as UnparseableModelResponseError so the chain treats it exactly like any other - // useless response and tries the next entry -- and never on the last one, where the alternative to - // an unearned "clean" is failing the file, which is worse. + // A one-sentence reply to a substantive diff means the model declined; escalate except on the last model, where failing beats an unearned clean. if (!isLastModel && isNonAnswerReview({ rawText, file: params.file, @@ -201,7 +192,7 @@ export type BatchReviewOutcome = ModelResponse & { userPrompt: string; }; -// Caller fans the result out to per-file rows and must not record `batch.missing` as reviewed. +// Caller must not record batch.missing as reviewed when fanning out to file rows. export async function reviewFiles(ctx: ModelReviewContext, params: { files: readonly FileDiff[]; prTitle: string | null; @@ -220,13 +211,10 @@ export async function reviewFiles(ctx: ModelReviewContext, params: { rejectedExemplars: params.rejectedExemplars, }); - // The bin's total: a 400-line bin on a small-file timeout dies mid-call and takes all of it down. + // Bin's total lines; a small-file timeout on a 400-line bin kills the whole call. const binLineCount = params.files.reduce((sum, file) => sum + file.lineCount, 0); - // The bin's whole response, not one file's: every entry shares one `maxOutputTokens`, and a bin that - // overruns it comes back as a repaired prefix with its tail files looking clean. A packed bin is also - // the slowest call the system makes, and its diff line count badly under-predicts that, so the same - // figure sizes the timeout. + // Whole-bin response shares one maxOutputTokens (overrun leaves tail files looking falsely clean); same figure sizes the timeout since a packed bin is the slowest call. const outputBudgetTokens = reviewOutputBudgetTokens({ findingCap: reviewBreadth(params.config.review), fileCount: params.files.length, @@ -238,10 +226,8 @@ export async function reviewFiles(ctx: ModelReviewContext, params: { responseSchema: buildBatchReviewResponseSchema(reviewBreadth(params.config.review), params.files.length), timeoutMs: adaptiveModelTimeoutMs(binLineCount, outputBudgetTokens), outputBudgetTokens, - // Worse in a bin: a cut lands in tail entries, which then look reviewed and clean. truncationIntolerant: true, label: `${params.files.length} files (${params.files[0]?.path ?? 'unknown'} …)`, - // Per file, so progress survives the bin narrowing or exploding into singles. progressLabels: params.files.map((file) => file.path), totalLineCount: params.totalLineCount, config: params.config, diff --git a/packages/models/src/internal/model-support.ts b/packages/models/src/internal/model-support.ts index 2a34018c..d767cbc9 100644 --- a/packages/models/src/internal/model-support.ts +++ b/packages/models/src/internal/model-support.ts @@ -2,9 +2,7 @@ import { normalizeModelId } from '@codraoss/schema'; import { isTimeoutMessage, matchesAnyTransientSubstring } from '@codraoss/schema/transient-errors'; import { UnparseableModelResponseError } from '../types'; -// Model service pure helpers: aliases, prompt sizes, rate limits, errors. - -// Legacy ID rewrites (applied before resolution). Hook for future aliases. +// Hook for future legacy ID rewrites, applied before resolution. const MODEL_ALIASES: Record = {}; export function mergeCounts(sources: Array | undefined>): Record { @@ -17,52 +15,45 @@ export function mergeCounts(sources: Array | undefined>): return merged; } -// Rough 4-chars/token estimate to preempt doomed calls. Overestimating safely routes onward. +// 4 chars/token estimate; overestimating is safe. export function estimatePromptTokens(systemPrompt: string, userPrompt: string): number { return Math.ceil((systemPrompt.length + userPrompt.length) / 4); } -// Headroom required before committing prompts to metered models. export const PROMPT_FIT_SAFETY_FACTOR = 0.8; -// Minimum plausible token bucket. Rejects misparsed small numbers (like request quotas) to prevent jobs from permanently blocking valid prompts. +// Floor to reject misparsed request quotas, not real token buckets. export const MIN_PLAUSIBLE_TOKEN_BUCKET = 1_000; export function isPlausibleTokenBucket(limitTokens: number | undefined): boolean { return typeof limitTokens === 'number' && limitTokens >= MIN_PLAUSIBLE_TOKEN_BUCKET; } -// Extracted nextChainIndex from deferrals. Lets callers distinguish "progress made" from "same failures". Lives here to avoid vi.mock TypeError in specs. +// Lives here (not with callers) to avoid vi.mock TypeError in specs. export function nextChainIndexOf(error: unknown): number | null { const value = (error as { nextChainIndex?: unknown } | null)?.nextChainIndex; return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : null; } -// Detects if Gemini adapter dropped grammar before throwing. Lets callers latch the schema-dropped state. export function isSchemaDroppedError(error: unknown): boolean { return (error as { schemaDropped?: unknown } | null)?.schemaDropped === true; } -// Max serialized queue depth before routing elsewhere, avoiding budget exhaustion while waiting. export const MAX_METERED_QUEUE_DEPTH = 2; -// Extracts metric/limit pairs from 429 bodies (may contain multiple). const QUOTA_VIOLATION_PATTERN = /metric:\s*(\S+?),\s*limit:\s*(\d[\d_,]*)/gi; -// Token metrics only. Prevents parsing request quotas (e.g. limit: 15) as token buckets, which would permanently disable the model. +// Excludes request-count quotas, which would otherwise disable the model. const TOKEN_QUOTA_METRIC = /(?:input_token|output_token|token_count|_tokens)/i; -// Extracts limit/retry from 429 bodies ("limit: 16000... retry in 26.9s"). export function parseRateLimitFromError(error: unknown): { limitTokens?: number; retryAfterMs?: number } { const message = error instanceof Error ? error.message : String(error ?? ''); - // Avoid bare limits; first stated limit might be request count. let limitTokens: number | undefined; for (const [, metric, limit] of message.matchAll(QUOTA_VIOLATION_PATTERN)) { if (!TOKEN_QUOTA_METRIC.test(metric)) continue; const parsed = Number(limit.replace(/[_,]/g, '')); if (!Number.isFinite(parsed) || !isPlausibleTokenBucket(parsed)) continue; - // Smallest valid token bucket wins. if (limitTokens === undefined || parsed < limitTokens) limitTokens = parsed; } @@ -121,7 +112,7 @@ export function isGoogleRateLimitError(error: unknown) { export function isTransientModelFailure(error: unknown) { if (isRetryableModelError(error)) return true; - // Deterministic unparseable output (reasoning-only/truncated) is non-retryable. + // Unparseable output is deterministic, not transient. if (error instanceof UnparseableModelResponseError) return false; if (isCloudflareAllocationError(error)) return false; const message = error instanceof Error ? error.message : String(error); @@ -137,7 +128,6 @@ export function isTransientModelFailure(error: unknown) { lower.includes('fetch failed') || lower.includes('network') || lower.includes('temporar') || - // Upstream 5xx is transient; defer rather than failing. /\b50[0-9]\b/.test(lower) || lower.includes('internal error') ); diff --git a/packages/models/src/limits.ts b/packages/models/src/limits.ts index ada26051..ce34d90d 100644 --- a/packages/models/src/limits.ts +++ b/packages/models/src/limits.ts @@ -1,20 +1,14 @@ -// Free-plan constraints: 6 concurrent connections max, 50 subrequests per invocation. - -// Base timeout for small diffs (~1-5s response expected). export const MODEL_TIMEOUT_BASE_MS = 20_000; const MODEL_TIMEOUT_PER_LINE_MS = 100; const MODEL_TIMEOUT_FREE_LINES = 100; -// Hard ceiling (max 50s) avoids 120s `exceededCpu` runtime limits, allowing failovers. +// Ceiling stays under 120s exceededCpu limit, leaving room to fail over. export const MODEL_TIMEOUT_MAX_MS = 50_000; -// File's total fallback chain budget. Exceeding defers the file to a fresh invocation. -// Sized slightly above MODEL_TIMEOUT_MAX_MS to give big diffs the full ceiling on a single model. +// Slightly above MODEL_TIMEOUT_MAX_MS so a large diff can use the full ceiling before deferring. export const MODEL_FALLBACK_CHAIN_BUDGET_MS = 55_000; -// Scaled timeout buffer based on requested output tokens (1200ms per 1k tokens) to accommodate model generation time. export const MODEL_TIMEOUT_PER_1K_OUTPUT_MS = 1_200; -// Per-call timeout, scaled by diff size and expected output budget. export function adaptiveModelTimeoutMs( diffLineCount: number | null | undefined, outputBudgetTokens?: number | null, @@ -25,13 +19,11 @@ export function adaptiveModelTimeoutMs( const budget = typeof outputBudgetTokens === 'number' && Number.isFinite(outputBudgetTokens) ? Math.max(0, outputBudgetTokens) : 0; - // Only the room ABOVE the floor earns extra time: every caller asks for at least the floor. const answerAllowance = Math.max(0, budget - OUTPUT_TOKENS_FLOOR) / 1_000 * MODEL_TIMEOUT_PER_1K_OUTPUT_MS; return Math.min(MODEL_TIMEOUT_MAX_MS, scaled + answerAllowance); } -// Clamps timeout so single calls never exceed the chain budget and loop endlessly without running. export function clampTimeoutToChainBudget(timeoutMs: number): number { return Math.min(timeoutMs, MODEL_FALLBACK_CHAIN_BUDGET_MS); } @@ -40,7 +32,7 @@ export const MODEL_MIN_VIABLE_ATTEMPT_MS = 8_000; export const MODEL_FALLBACK_RESERVE_MS = 20_000; -// Time to grant one rung of a fallback chain; 0 means defer to a fresh invocation. +// Returns 0 to defer the file to a fresh invocation. export function chainAttemptTimeoutMs(input: { requestedMs: number; remainingChainMs: number; @@ -54,17 +46,13 @@ export function chainAttemptTimeoutMs(input: { return Math.min(requestedMs, withReserve >= MODEL_MIN_VIABLE_ATTEMPT_MS ? withReserve : remainingChainMs); } -// Max 3 calls limits connection pool (out of 6 max) to leave slots for KV/GitHub. +// 3 of the 6 pool connections reserved for KV/GitHub. export const MAX_CONCURRENT_MODEL_CALLS = 3; -// Token cost of a single finding (JSON structure). Generous to avoid silent truncations. const OUTPUT_TOKENS_PER_FINDING = 340; -// Tokens per file entry in batch response. const OUTPUT_TOKENS_PER_FILE_ENTRY = 160; -// Enough for the verify/summary paths and any caller that states no budget. export const OUTPUT_TOKENS_FLOOR = 8_192; -// Output budget sizing (excludes reasoning tokens). Driven by requested capacity (findings * files). export function reviewOutputBudgetTokens(input: { findingCap: number; fileCount: number }): number { const files = Math.max(1, input.fileCount); const findings = Math.max(1, input.findingCap) * files; @@ -74,7 +62,6 @@ export function reviewOutputBudgetTokens(input: { findingCap: number; fileCount: ); } -// Clamps output budget to provider maximums. export function resolveOutputTokenCeiling( requested: number | undefined, providerMax: number, @@ -86,25 +73,22 @@ export function resolveOutputTokenCeiling( return Math.min(providerMax, Math.max(providerDefault, Math.ceil(requested))); } -// Gemini 2.5 reasoning budget (1024-8192 bounds). Must be explicitly limited since it counts against maxOutputTokens. +// Gemini thinking budget counts against maxOutputTokens, so it must stay bounded to 1024-8192. export function geminiThinkingBudgetTokens(answerBudgetTokens: number): number { return Math.min(8_192, Math.max(1_024, Math.floor(answerBudgetTokens / 4))); } -// Actual subrequests cost per attempt (allows for adapter retries/fallback). const SUBREQUESTS_PER_MODEL_ATTEMPT = 3; -// Subrequest headroom before calling models. Multiplied by concurrency cap to ensure pool can execute. export const SUBREQUEST_HEADROOM_FOR_MODEL_CALL = SUBREQUESTS_PER_MODEL_ATTEMPT * MAX_CONCURRENT_MODEL_CALLS; -// FIFO semaphore: waits here don't eat into the caller's timeout budget. +// Queue wait time is excluded from the caller's timeout budget. export class ModelCallGate { private active = 0; private readonly waiters: Array<() => void> = []; constructor(private readonly limit = MAX_CONCURRENT_MODEL_CALLS) {} - // onAcquired tracks wait time, preventing busy queues from skewing model latency metrics. async run(fn: () => Promise, onAcquired?: (waitedMs: number) => void): Promise { const startedWaiting = Date.now(); await this.acquire(); @@ -129,7 +113,6 @@ export class ModelCallGate { } private release() { - // Fair release to next waiter. const next = this.waiters.shift(); if (next) { next(); diff --git a/packages/models/src/providers/google.ts b/packages/models/src/providers/google.ts index 50a55f7d..9be9e0f6 100644 --- a/packages/models/src/providers/google.ts +++ b/packages/models/src/providers/google.ts @@ -11,24 +11,20 @@ import { resolveOutputTokenCeiling, } from '../limits'; -/** Fallback timeout if caller omits budget. */ const GEMINI_TIMEOUT_MS = MODEL_TIMEOUT_MAX_MS; const GEMINI_MAX_RETRIES = 2; -// Output floor for low-budget tasks (verify, summary). const GEMINI_DEFAULT_OUTPUT_TOKENS = OUTPUT_TOKENS_FLOOR; -// Max output claims. 65k allows room for thinking tokens and dense multi-file bins. +// 65k leaves room for thinking tokens plus dense multi-file output. const GEMINI_MAX_OUTPUT_TOKENS = 65_536; -// Cap on retry sleeps; longer cool-offs defer files to free up gates. const GEMINI_MAX_RETRY_DELAY_MS = 5_000; const DEFAULT_GEMINI_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta'; -// 429 handled separately (only retryable if cool-off is stated). +// 429 handled separately; only retryable if a cool-off is stated. function isRetryableGeminiStatus(status: number) { return status === 408 || status === 500 || status === 502 || status === 503 || status === 504 || status === 524; } function defaultRetryDelayMs(attempt: number) { - // Exponential backoff for transient 5xx errors (clears quickly). return Math.pow(2, attempt) * 800 + Math.random() * 400; } @@ -47,7 +43,6 @@ function retryAfterDelayMs(value: string | null) { return null; } -// Extract body cool-off ("Please retry in Xs.") to avoid indefinite 429 retries. function requestedRetryDelayFromBody(message: string): number | null { const match = /retry in ([\d.]+)s/i.exec(message); if (!match) return null; @@ -55,7 +50,7 @@ function requestedRetryDelayFromBody(message: string): number | null { return Number.isFinite(seconds) ? seconds * 1000 : null; } -// Broad matcher (false positives cost 1 subrequest; false negatives break chains). +// Broad on purpose: false positive costs 1 subrequest, false negative breaks the chain. function isSchemaRejection(status: number, message: string) { if (status !== 400) return false; const lower = message.toLowerCase(); @@ -67,14 +62,14 @@ function isSchemaRejection(status: number, message: string) { lower.includes('invalid json payload') || lower.includes('unknown name') || lower.includes('schema') || - // Bare 400 catch-all to prevent permanent schema failures. Worst case: one extra failed schema-less attempt. + // Bare 400 catch-all avoids permanent schema failures. lower.includes('invalid argument') ); } function isRetryableTransportError(error: unknown) { if (!(error instanceof Error)) return false; - // Don't retry timeouts (caller grants up to 2m); defer to fallback chains. + // Skip retrying timeouts (caller already grants up to 2m); defer to fallback chain. if (error.name === 'TimeoutError' || error.message.toLowerCase().includes('timed out')) return false; if (error.message.includes('fetch failed')) return true; return error instanceof TypeError; @@ -94,11 +89,9 @@ export async function reviewWithGoogle( const responseJsonSchema = input.responseSchema ? toGeminiResponseJsonSchema(input.responseSchema.schema) : null; - // Latches to disable features on subsequent attempts if rejected. let schemaRejected = false; let thinkingRejected = false; - // Summing JSON and thinking token budgets prevents truncated prefixes. const answerBudget = resolveOutputTokenCeiling( input.outputBudgetTokens, GEMINI_MAX_OUTPUT_TOKENS, @@ -107,7 +100,6 @@ export async function reviewWithGoogle( const thinkingBudget = geminiThinkingBudgetTokens(answerBudget); let currentCeiling = Math.min(GEMINI_MAX_OUTPUT_TOKENS, answerBudget + thinkingBudget); let ceilingRaised = false; - // Mark error so caller latches schema-dropped state even on subsequent failure. const fail = (error: unknown): never => { if (schemaRejected && typeof error === 'object' && error !== null) { Object.defineProperty(error, 'schemaDropped', { value: true, configurable: true }); @@ -151,14 +143,11 @@ export async function reviewWithGoogle( { role: 'user', parts: [{ text: prompts.user }] }, ], generationConfig: { - // Required for schemas and summary path. responseMimeType: 'application/json', - // See gemini-schema.ts. ...(responseJsonSchema && !schemaRejected ? { responseJsonSchema } : {}), maxOutputTokens: currentCeiling, - // Bounded thinking budget so it doesn't consume the output ceiling. ...(thinkingRejected ? {} : { thinkingConfig: { thinkingBudget } }), - // 0.9 on Gemini's 0-2 scale. + // Gemini's temperature scale is 0-2, not 0-1. temperature: 0.9, }, }), @@ -177,7 +166,7 @@ export async function reviewWithGoogle( const errorText = await response.text(); const message = providerErrorMessage(errorText); - // Check thinking first; isSchemaRejection is broad. + // Check thinking rejection first; isSchemaRejection below is broad. if (!thinkingRejected && isThinkingRejection(response.status, message)) { thinkingRejected = true; logger.warn('Gemini rejected thinkingConfig; retrying without an explicit thinking budget', { @@ -185,20 +174,18 @@ export async function reviewWithGoogle( error: message, }); lastError = new ProviderRequestError(config.providerName ?? 'Google', response.status, message); - // Refund attempt (no sleep); latched. attempt--; continue; } if (responseJsonSchema && !schemaRejected && isSchemaRejection(response.status, message)) { schemaRejected = true; - // Inferred schema rejection; real cause thrown below if 400 recurs. + // Inferred from message; real cause surfaces below if 400 recurs. logger.warn('Gemini returned a 400 that looks like a response-grammar rejection; retrying without constrained decoding', { model, error: message, }); lastError = new ProviderRequestError(config.providerName ?? 'Google', response.status, message); - // Refund attempt (no sleep); latched. attempt--; continue; } @@ -206,7 +193,7 @@ export async function reviewWithGoogle( const requestedDelayMs = response.status === 429 ? retryAfterDelayMs(response.headers.get('retry-after')) ?? requestedRetryDelayFromBody(message) : null; - // Unstated 429s back-off for ~60s, making them unretryable here. Retry only on short, stated cool-offs. + // Unstated 429s back off ~60s, making them unretryable here; retry only short, stated cool-offs. const isRetryable = response.status === 429 ? requestedDelayMs !== null && requestedDelayMs <= GEMINI_MAX_RETRY_DELAY_MS : isRetryableGeminiStatus(response.status); @@ -221,7 +208,6 @@ export async function reviewWithGoogle( willRetry: isRetryable && attempt < maxRetries, requestedDelayMs: requestedDelayMs ?? undefined, retryDelayMs: isRetryable && attempt < maxRetries ? retryDelayMs : undefined, - // Log bounded raw body for terminal 4xx to debug unactionable "invalid argument" errors. rawBody: response.status >= 400 && response.status < 500 && !(isRetryable && attempt < maxRetries) ? errorText.slice(0, 2_000) : undefined, @@ -245,7 +231,7 @@ export async function reviewWithGoogle( usageMetadata?: { promptTokenCount?: number; candidatesTokenCount?: number; - // Billed against `maxOutputTokens`. + // Billed against maxOutputTokens. thoughtsTokenCount?: number; }; }; @@ -257,7 +243,7 @@ export async function reviewWithGoogle( if (finishReason && finishReason !== 'STOP') { logger.warn(`Gemini response for ${model} ended with finishReason=${finishReason}; output is likely incomplete`, { - // Avoid `Tokens` key name to bypass logger redaction. Sum thinking + output spend. + // Avoid a `Tokens` key name; the logger redacts it. outputSpend: (data.usageMetadata?.candidatesTokenCount ?? 0) + (data.usageMetadata?.thoughtsTokenCount ?? 0), thoughtSpend: data.usageMetadata?.thoughtsTokenCount ?? 0, outputCeiling: currentCeiling, @@ -285,14 +271,14 @@ export async function reviewWithGoogle( } if (!rawText) { - // Deterministic non-STOP (budget burn, safety) fails permanently; empty STOP is transient. + // Non-STOP finish fails permanently; empty STOP is transient. if (finishReason && finishReason !== 'STOP') { return fail(new UnparseableModelResponseError(model, `finishReason=${finishReason}`)); } return fail(new Error('Gemini returned an empty response.')); } - // Still truncated after retry; fail but attach the partial text so the chain's last model can salvage it. + // Attach partial text so a later fallback model can salvage it. if (truncated && input.truncationIntolerant) { const error = new UnparseableModelResponseError(model, 'finishReason=MAX_TOKENS'); attachPartialResponse(error, { diff --git a/packages/models/src/runner.ts b/packages/models/src/runner.ts index dcdda936..5ef91c43 100644 --- a/packages/models/src/runner.ts +++ b/packages/models/src/runner.ts @@ -20,40 +20,36 @@ import { ModelRateLimitBook } from './internal/model-rate-limits'; import { ModelChainProgressStore } from './internal/model-chain-progress'; import { type ModelChainContext, generateSummary, verifyFindings } from './internal/model-chain-runner'; import { type ModelReviewContext, reviewFile, reviewFiles } from './internal/model-review-file'; -// Re-exported so test doubles can be typed against the real batched-review shape. +// Re-exported so test doubles can be typed against the real shape. export type { BatchReviewOutcome } from './internal/model-review-file'; import { pollReviewBatch, submitReviewBatch } from './internal/model-review-batch'; -// Re-exported: core/review.ts and two specs import these from '@codraoss/models'. +// Re-exported: core/review.ts and two specs import these. export { RetryableModelError, isRetryableModelError, nextChainIndexOf } from './internal/model-support'; -// Re-exported so the batch-prompt budget test asserts against these constants, not a copy. export { PROMPT_FIT_SAFETY_FACTOR, estimatePromptTokens } from './internal/model-support'; -// Re-exported so its unit spec can reach it without a sibling import (no-restricted-imports). +// Re-exported to avoid a sibling import (no-restricted-imports). export { ModelChainProgressStore } from './internal/model-chain-progress'; -// Same reason: the 429-parsing spec asserts against the real implementation, not a copy. export { isPlausibleTokenBucket, parseRateLimitFromError } from './internal/model-support'; const PROVIDER_UNAVAILABLE_TTL_SECONDS = 24 * 60 * 60; export class ModelRunner { - // Caches the in-flight PROMISE (not just the result), so concurrent calls for a model await one request. + // Caches the in-flight promise so concurrent calls for a model share one request. private readonly resolvedModelCache = new Map>(); - // Rate-limit learning plus the connection/token gates, keyed by MODEL, not provider. - // Backed by chainProgress so learned cool-offs outlive the invocation; assigned in the constructor - // because it depends on it. + // Keyed by model, not provider; backed by chainProgress so cool-offs persist across invocations. private readonly rateLimits: ModelRateLimitBook; - // Provider-unavailable markers live in KV and can't flip set-to-unset within one invocation, so cache them per instance. + // KV can't flip unavailable to available within one invocation, so cache per instance. private readonly providerUnavailableCache = new Map>(); - // Models proven this invocation not to support async batching, so later files skip the probe. + // Confirmed unsupported for async batching this invocation; skip re-probing. private readonly asyncUnsupportedModels = new Set(); - // Same idea for constrained decoding: `(provider, model, grammar)` triples that were refused, keyed by grammar so one oversized bin doesn't disable the single-file grammar too. + // Same, for constrained decoding, keyed by grammar so one refusal doesn't disable others. private readonly schemaUnsupportedModels = new Set(); - // How far down the chain each file already got, so a deferral resumes instead of replaying. + // Per-file progress so a deferral resumes instead of replaying. private readonly chainProgress: ModelChainProgressStore; constructor( @@ -100,7 +96,6 @@ export class ModelRunner { const key = this.providerUnavailableKey(providerId); if (!key) return; - // Keep the in-invocation cache consistent with what we just wrote. this.providerUnavailableCache.set(providerId, Promise.resolve(true)); try { @@ -154,10 +149,10 @@ export class ModelRunner { const normalized = normalizeModel(model); let pending = this.resolvedModelCache.get(normalized); if (!pending) { - // Cache the DB answer, including a null "not configured", so it isn't re-queried per file. + // Cache the null "not configured" result too, so it isn't requeried. pending = this.deps.getConfig(normalized); this.resolvedModelCache.set(normalized, pending); - // Don't let a transient DB error poison the cache; drop it so the next call retries. + // Drop cache entry on error so the next call retries. pending.catch(() => this.resolvedModelCache.delete(normalized)); } const resolved = await pending; @@ -183,10 +178,10 @@ export class ModelRunner { config: ResolvedModelConfig, input: ModelInput, timeoutMs?: number, - // Reports queue time so a caller budgeting wall clock can exclude it. + // Excludes queue wait from the caller's timing budget. onGateWait?: (waitedMs: number) => void, ): Promise { - // Resolve credentials BEFORE taking a gate slot, so slow KV/crypto work never occupies one. + // Resolve credentials before the gate slot so slow work doesn't hold one. if (config.apiFormat === 'cloudflare-workers-ai') { if (!this.deps.aiBinding) { throw new Error(`Provider ${config.providerName} requires a Cloudflare AI binding, but none was provided.`); @@ -202,7 +197,7 @@ export class ModelRunner { let response: ModelResponse; try { response = await this.rateLimits.runGated(config, onGateWait, () => { - // Read inside the gate: hoisted, the opening wave would all see "not yet known" and probe. + // Read inside the gate: a hoisted read would race the opening wave. const gatedInput = this.schemaUnsupportedModels.has(schemaKey) ? { ...input, responseSchema: undefined } : input; @@ -214,8 +209,7 @@ export class ModelRunner { ); }); } catch (error) { - // Latch on failure too: the probe already proved the grammar is refused, and without this a - // schema-dropped attempt that then 429s re-pays the 400 plus a full prompt on the next call. + // Latch failure too, so a schema-dropped retry doesn't repay the 400 and full prompt. if (isSchemaDroppedError(error)) this.schemaUnsupportedModels.add(schemaKey); throw error; } @@ -269,7 +263,6 @@ export class ModelRunner { return this.callResolvedModel(await this.resolveModel(model), input, timeoutMs); } - // chainCtx() plus the review flow's extra per-invocation state. private reviewCtx(): ModelReviewContext { return { ...this.chainCtx(), @@ -284,7 +277,7 @@ export class ModelRunner { return reviewFile(this.reviewCtx(), params); } - // Several small files in one call; `batch.missing` files must not be recorded as reviewed. + // batch.missing files must not be recorded as reviewed. async reviewFiles(params: Parameters[1]) { return reviewFiles(this.reviewCtx(), params); } @@ -297,7 +290,6 @@ export class ModelRunner { return pollReviewBatch(this.reviewCtx(), params); } - // Hands the extracted flows the private model-chain surface. Built per call; holds no state. private chainCtx(): ModelChainContext { return { selectModel: (params) => this.selectModel(params), diff --git a/packages/models/src/types.ts b/packages/models/src/types.ts index 7e0868ec..1d597f46 100644 --- a/packages/models/src/types.ts +++ b/packages/models/src/types.ts @@ -1,22 +1,15 @@ -// Both live in @codraoss/core/ports now: prompts/file-review.ts builds a ModelResponseSchema, and it is -// the only reason a pure prompt module ever imported from models/. Re-exported here so the ~20 -// existing `@codraoss/models/types` importers are unaffected, and so there is exactly one definition. +// Re-exported from @codraoss/core/ports so existing @codraoss/models/types imports keep working. import type { ModelResponseSchema } from '@codraoss/core/ports'; export type { ModelResponse, ModelResponseSchema } from '@codraoss/core/ports'; import type { ModelResponse as ModelResponseShape } from '@codraoss/core/ports'; -// `responseSchema` is per-call on purpose: file review, verification, and summary each need a different output shape. export type ModelInput = { systemPrompt: string; userPrompt: string; responseSchema?: ModelResponseSchema; - // Output tokens this call needs to answer in full, from `reviewOutputBudgetTokens`. Advisory: each - // adapter clamps it to its own provider maximum and never goes BELOW its own default, so a caller - // that omits it is unaffected. Omitting it on a large batched review is what truncates the response. + // Advisory: adapters clamp to their max and never go below their own default. outputBudgetTokens?: number; - // Set by callers whose answer is only usable whole (review/verification), where a truncated JSON - // prefix would silently read as a clean result. Adapters retry once with more room on MAX_TOKENS. - // Not inferred from `responseSchema`, since ModelRunner strips that once a model has refused a grammar. + // Only for callers needing a whole answer; adapters retry once with more room on MAX_TOKENS. truncationIntolerant?: boolean; }; @@ -31,7 +24,7 @@ export class ProviderRequestError extends Error { } } -// Thrown instead of synthesizing a fake "inconclusive" pass, so the fallback chain tries the next model. Treated as PERMANENT (not transient): the outcome is deterministic, so retrying just burns quota. +// Deliberately PERMANENT (not transient): outcome is deterministic, so retry just burns quota. export class UnparseableModelResponseError extends Error { constructor(public readonly model: string, public readonly reason: string) { super(`Model ${model} produced no reviewable output (${reason}); the file review failed.`); @@ -39,8 +32,6 @@ export class UnparseableModelResponseError extends Error { } } -// Attaches the model's truncated prefix to the thrown error so a caller can't mistake it for a whole -// answer; only a fallback chain's last rung should read it, to salvage the file instead of losing it. export function attachPartialResponse(error: object, response: ModelResponseShape) { Object.defineProperty(error, 'partialResponse', { value: response, configurable: true }); } @@ -53,7 +44,6 @@ export function partialResponseOf(error: unknown): ModelResponseShape | null { return typeof rawText === 'string' && rawText.trim() ? (partial as ModelResponseShape) : null; } -// `details[].description` and `details[].fieldViolations[].description`, flattened and deduped. function errorDetailText(error: unknown): string { const details = (error as Record | null)?.details; if (!Array.isArray(details)) return ''; @@ -84,29 +74,23 @@ export function providerErrorMessage(errorText: string) { } if (typeof message === 'string' && message.trim()) { - // Gemini puts the actionable reason in `error.details`, leaving `message` as the useless - // "Request contains an invalid argument." -- which made isSchemaRejection miss a grammar - // rejection and lose the whole model instead of retrying without the schema. + // Gemini's top-level message is useless; the real reason is in error.details. const detail = errorDetailText(obj.error); return detail ? `${message.trim()} ${detail}` : message.trim(); } } } catch { - // Fall back to the provider body below. } return errorText.trim() || 'The provider returned an error.'; } -// Probed before the broad schema matcher so a thinking-config refusal isn't misread as a grammar drop. -// Shared by the Google and Vertex adapters. export function isThinkingRejection(status: number, message: string) { if (status !== 400) return false; const lower = message.toLowerCase(); return lower.includes('thinking') || lower.includes('thought'); } -// Temperature deliberately not zero: a little randomness reviews better than greedy decoding. Each adapter sits at the same relative point on its own scale (Google/Vertex/OpenAI 0-2 at 0.9; Anthropic 0-1 and Cloudflare 0-5 at 0.6); watch `droppedByVerdict` if these move. export function jsonOnlyPrompts(input: ModelInput) { return { system: `${input.systemPrompt}\n\nReturn only the JSON object. Do not include chain-of-thought, analysis, markdown, code fences, or explanatory prose.`, diff --git a/packages/models/test/model/batch-routing.spec.ts b/packages/models/test/model/batch-routing.spec.ts index 0d541c52..259809cd 100644 --- a/packages/models/test/model/batch-routing.spec.ts +++ b/packages/models/test/model/batch-routing.spec.ts @@ -57,13 +57,12 @@ describe('parseBatchReviewResponse', () => { expect(result.reviews.get('src/a.ts')!.comments[0].path).toBe('src/a.ts'); expect(result.reviews.get('src/b.ts')!.comments[0].path).toBe('src/b.ts'); expect(result.reviews.get('src/a.ts')!.fileSummary).toContain('Summary for src/a.ts'); - // Never silently approved: an omitted file has no entry and must surface for re-queueing. + // Omitted file must surface for re-queueing, not silently approved. expect(result.missing).toEqual(['src/c.ts']); expect(result.reviews.has('src/c.ts')).toBe(false); }); - // Routing tolerates loose paths, but only when unambiguous. Renames matter because renderFileDiff - // shows the old path on the header line. + // Renames matter: renderFileDiff shows the old path on the header line. it('tolerates path noise and renames, but refuses to guess', () => { for (const reported of ['./src/a.ts', 'a/src/a.ts', 'b/src/a.ts', '/src/a.ts', 'a.ts']) { const result = parseBatchReviewResponse( @@ -80,13 +79,12 @@ describe('parseBatchReviewResponse', () => { ); expect(renamed.reviews.get('src/new.ts')!.comments).toHaveLength(1); - // Two files share a basename: guessing would file findings against code they were never about. + // Shared basename: guessing would misattribute findings. const siblings = [file('src/a/index.ts', ['const alpha = 1;']), file('src/b/index.ts', ['const bravo = 2;'])]; const ambiguous = parseBatchReviewResponse(raw([entry('index.ts', 'const alpha = 1;')]), siblings); expect(ambiguous.stats.unroutableEntries).toBe(1); expect(ambiguous.reviews.size).toBe(0); - // A duplicate entry is discarded, never re-homed onto a sibling. const duplicated = parseBatchReviewResponse( raw([entry('src/a/index.ts', 'const alpha = 1;'), entry('src/a/index.ts', 'const alpha = 1;', 'Duplicate')]), siblings, @@ -96,7 +94,7 @@ describe('parseBatchReviewResponse', () => { expect(duplicated.missing).toEqual(['src/b/index.ts']); }); - // What per-file indexes miss: a misfiled finding whose quote exists in the wrong file too. + // Per-file indexes miss quotes shared across files. it('withholds only when a shared quote AND a path disagreement coincide', () => { const shared = '} catch (error) {'; const files = [file('src/a.ts', [shared, 'const uniqueToAlpha = 1;']), file('src/b.ts', [shared, 'const bravo = 2;'])]; @@ -118,19 +116,17 @@ describe('parseBatchReviewResponse', () => { expect(withheld.stats.ambiguousAcrossBin).toBe(1); expect(withheld.reviews.get('src/a.ts')!.comments).toHaveLength(0); - // Shared quote, agreeing path: ordinary, keep it. const agreeing = parseBatchReviewResponse(raw([entry('src/a.ts', shared, 'Swallowed error')]), files); expect(agreeing.stats.ambiguousAcrossBin).toBe(0); expect(agreeing.reviews.get('src/a.ts')!.comments).toHaveLength(1); - // Unique quote, disagreeing path: the enclosing entry wins, which is the point of nesting. + // Unique quote + wrong path: enclosing entry still wins. const mismatch = parseBatchReviewResponse(misfiled('const uniqueToAlpha = 1;', 'src/b.ts'), files); expect(mismatch.stats.pathMismatchFindings).toBe(1); expect(mismatch.reviews.get('src/a.ts')!.comments[0].path).toBe('src/a.ts'); }); - // Per file, not a shared pool: a shared ceiling lets one noisy file keep everything while - // its bin-mates are trimmed to nothing. + // Cap is per-file: a noisy file keeps its own cap while others are untouched. it('trims over-cap findings per file and accounts for the drop', () => { const lines = Array.from({ length: 30 }, (_, i) => `const value${i} = ${i};`); const files = [file('src/a.ts', lines), file('src/b.ts', ['const bravo = 2;'])]; @@ -159,12 +155,10 @@ describe('parseBatchReviewResponse', () => { expect(result.reviews.get('src/a.ts')!.comments).toHaveLength(10); expect(result.stats.overCap).toBe(20); expect(result.reviews.get('src/a.ts')!.fileSummary).toContain('over-cap'); - // The quiet file keeps everything -- it never competed for a shared budget. expect(result.reviews.get('src/b.ts')!.comments).toHaveLength(1); }); - // Assembly can reject one finding; under batching an uncontained throw would discard - // every other file packed alongside it. + // One bad finding must not sink the rest of the batch. it('drops an unassemblable finding without losing the rest of the bin', () => { const files = [file('src/a.ts', ['const alpha = 1;']), file('src/b.ts', ['const bravo = 2;'])]; diff --git a/packages/models/test/model/chain-progress-store.spec.ts b/packages/models/test/model/chain-progress-store.spec.ts index 20937625..bc2fba64 100644 --- a/packages/models/test/model/chain-progress-store.spec.ts +++ b/packages/models/test/model/chain-progress-store.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { ModelChainProgressStore } from '@codraoss/models'; -// KV double tracks overlapping puts (KV lacks ordering; late puts with less state can revert progress). +// KV has no ordering; a late put with less state could revert progress. function makeKV() { let value: string | null = null; let inFlight = 0; @@ -46,7 +46,6 @@ describe('ModelChainProgressStore', () => { await Promise.all([store.advance('src/a.ts', 2), store.advance('src/b.ts', 3)]); - // Order irrelevance: non-overlapping puts ensure the last one is the most complete. expect(kv.maxInFlight).toBe(1); expect(kv.stored?.files).toEqual({ 'src/a.ts': 2, 'src/b.ts': 3 }); expect(await store.startIndexFor('src/a.ts')).toBe(2); @@ -60,7 +59,6 @@ describe('ModelChainProgressStore', () => { await Promise.all([1, 2, 3, 4, 5, 6].map((n) => store.advance(`src/f${n}.ts`, n))); expect(kv.maxInFlight).toBe(1); - // Six advances but fewer writes; conserves subrequests. expect(kv.writes.length).toBeLessThan(6); expect(Object.keys(kv.stored?.files ?? {})).toHaveLength(6); }); @@ -88,7 +86,6 @@ describe('ModelChainProgressStore', () => { expect(await store.startIndexFor('src/a.ts')).toBe(3); }); - // Persisted tally lets the next concurrent wave avoid models the first wave timed out on. it('drops a model after a full wave of timeouts, and remembers across invocations', async () => { const kv = makeKV(); const store = new ModelChainProgressStore(kv.kv, 'job-slow'); @@ -101,33 +98,29 @@ describe('ModelChainProgressStore', () => { await store.noteTimeout('vertex-ai:gemini-2.5-pro'); expect(await store.isTimingOut('vertex-ai:gemini-2.5-pro')).toBe(true); - // Fresh store mimics next invocation. const next = new ModelChainProgressStore(kv.kv, 'job-slow'); expect(await next.isTimingOut('vertex-ai:gemini-2.5-pro')).toBe(true); - // Scoped to the failing model. expect(await next.isTimingOut('vertex-ai:gemini-2.5-flash')).toBe(false); }); - // Tail candidates use a higher strike threshold rather than exemption, to prevent infinite looping. + // Tail gets higher strike threshold, not exemption, to avoid infinite looping. it('holds the last candidate to a higher strike count before dropping it too', async () => { const kv = makeKV(); const store = new ModelChainProgressStore(kv.kv, 'job-tail'); for (let i = 0; i < 3; i += 1) await store.noteTimeout('cf:glm-4.7-flash'); - // Drops mid-chain, but preserves the tail. expect(await store.isTimingOut('cf:glm-4.7-flash')).toBe(true); expect(await store.isTimingOutTerminally('cf:glm-4.7-flash')).toBe(false); for (let i = 0; i < 3; i += 1) await store.noteTimeout('cf:glm-4.7-flash'); expect(await store.isTimingOutTerminally('cf:glm-4.7-flash')).toBe(true); - // Durable across invocations. const next = new ModelChainProgressStore(kv.kv, 'job-tail'); expect(await next.isTimingOutTerminally('cf:glm-4.7-flash')).toBe(true); }); describe('noteSuccess', () => { - // Resets prevent cumulative tallies from condemning a model for the job's entire 24h life. + // Reset prevents lifetime tally from condemning a model for the whole job. it('restarts the tally, so a slow patch cannot condemn a working model', async () => { const kv = makeKV(); const store = new ModelChainProgressStore(kv.kv, 'job-recovered'); @@ -139,7 +132,7 @@ describe('ModelChainProgressStore', () => { expect(await store.isTimingOut('vertex-ai:gemini-2.5-pro')).toBe(false); }); - // writeOnce's max() merge must not resurrect pre-success counts from KV. + // max() merge must not resurrect pre-success counts from KV. it('survives the merge against what another invocation stored', async () => { const kv = makeKV(); await kv.kv.put('k', JSON.stringify({ timeouts: { 'vertex-ai:gemini-2.5-pro': 5 } })); @@ -158,7 +151,6 @@ describe('ModelChainProgressStore', () => { await store.noteSuccess('vertex-ai:gemini-2.5-pro'); - // Healthy paths don't incur KV writes to save subrequests. expect(kv.writes).toHaveLength(0); }); }); @@ -187,7 +179,6 @@ describe('ModelChainProgressStore', () => { expect(await store.isTimingOut('anything')).toBe(false); }); - // Persisted rate-limits prevent continuation jobs from re-paying for known cool-offs. describe('rate-limit cool-offs', () => { it('carries a learned cool-off and bucket size to the next invocation', async () => { const kv = makeKV(); @@ -200,7 +191,6 @@ describe('ModelChainProgressStore', () => { const next = new ModelChainProgressStore(kv.kv, 'job-cooldown'); const loaded = await next.loadCooldowns(); expect(loaded.get('google:gemini-2.5-flash')).toEqual({ cooldownUntil: until, limitTokens: 16000 }); - // Cool-offs scope per-model bucket. expect(loaded.has('google:gemini-2.5-flash-lite')).toBe(false); }); @@ -211,7 +201,6 @@ describe('ModelChainProgressStore', () => { store.noteRateLimit('google:gemini-2.5-flash', { cooldownUntil: Date.now() + 30_000 }); expect(kv.writes).toHaveLength(0); - // Made durable by the subsequent deferral. await store.flushPending(); expect(kv.writes.length).toBeGreaterThan(0); }); @@ -230,7 +219,7 @@ describe('ModelChainProgressStore', () => { expect(kv.stored?.cooldowns?.['google:m']).toEqual({ until: later, limitTokens: 16000 }); }); - // Protects against sticky, misparsed request counts crippling the model for 24h. + // Guards against misparsed counts crippling the model for 24h. it('discards a stored bucket too small to be a token quota', async () => { const kv = makeKV(); const until = Date.now() + 30_000; @@ -239,7 +228,6 @@ describe('ModelChainProgressStore', () => { const store = new ModelChainProgressStore(kv.kv, 'job-poisoned-bucket'); const entry = (await store.loadCooldowns()).get('google:m'); - // Retains valid cool-off while discarding nonsense bucket size. expect(entry?.cooldownUntil).toBe(until); expect(entry?.limitTokens).toBeUndefined(); }); diff --git a/packages/models/test/model/chain-resume.spec.ts b/packages/models/test/model/chain-resume.spec.ts index 84328332..3b049cbd 100644 --- a/packages/models/test/model/chain-resume.spec.ts +++ b/packages/models/test/model/chain-resume.spec.ts @@ -4,9 +4,7 @@ import { defaultRepoConfig } from '@codraoss/schema'; import { TokenTracker } from '@codraoss/core/token-tracker'; import { createTestEnv, saveTestProviderApiKey, createTestModelRunner } from '../../../../test/helpers'; -// One invocation only affords ~55s of model calls, so a chain whose head is slow never reaches its -// tail. These pin that a deferral records where it got to and the next attempt resumes there -- -// without which entries past the first two are unreachable no matter how often a job retries. +// ~55s per invocation: a slow head never reaches the tail, so resume must pick up where it left off. describe('model chain resume', () => { afterEach(() => { vi.restoreAllMocks(); @@ -26,8 +24,7 @@ describe('model chain resume', () => { ...defaultRepoConfig, model: { main: 'gemini-3.1-pro-preview', - // Three entries, all configured in the test env: the memo only records progress while the - // chain still has somewhere to go, so a two-entry chain would never write one. + // 3 entries: the memo only records progress when there's still somewhere left to go. fallbacks: ['gemini-2.5-pro', 'gemini-3.1-flash-lite'], size_overrides: [], }, @@ -40,7 +37,6 @@ describe('model chain resume', () => { candidates: [{ content: { parts: [{ text: '{"findings":[],"overall_correctness":"patch is correct","overall_explanation":"ok","overall_confidence_score":0.9}' }] } }], usageMetadata: { promptTokenCount: 1, candidatesTokenCount: 1 }, }); - // 503, not 500: a transient failure is what produces a deferral rather than a hard failure. const unavailable = () => gemini(503, { error: { code: 503, message: 'The model is overloaded.', status: 'UNAVAILABLE' } }); const rateLimited = () => gemini(429, { error: { code: 429, message: 'Resource exhausted. limit: 16000, model: gemini. Please retry in 30s.', status: 'RESOURCE_EXHAUSTED' } }); @@ -51,16 +47,13 @@ describe('model chain resume', () => { prDescription: null, config: chainConfig, totalLineCount: 1, - // Every model gets one shot, so the deferral arrives without a long inline retry ladder. } as Parameters[0]); } it('resumes at the model after the ones that already failed, instead of replaying them', async () => { const env = createTestEnv(); await saveTestProviderApiKey(env); - // Same jobId across both services: the memo is job-scoped KV, exactly as across invocations. - // Near the subrequest cap, so the chain stops after the primary -- the real shape of the - // problem, where a breaker ends the walk with models still untried. + // Near the subrequest cap, so the breaker ends the chain after the primary, matching prod. const tracker = new TokenTracker(); tracker.incrementSubrequests(40); const first = createTestModelRunner(env, tracker, { jobId: 'job-chain-resume' }); @@ -71,12 +64,10 @@ describe('model chain resume', () => { expect(walked.every((url) => url.includes('gemini-3.1-pro-preview'))).toBe(true); vi.restoreAllMocks(); - // A fresh service stands in for the next invocation; it reads the memo back out of KV. const second = createTestModelRunner(env, undefined, { jobId: 'job-chain-resume' }); const secondFetch = vi.spyOn(globalThis, 'fetch').mockImplementation(async () => ok()); await review(second); - // The head of the chain is not retried: it was already ruled out for this file. const retried = secondFetch.mock.calls.map((call) => String(call[0])); expect(retried.every((url) => !url.includes('gemini-3.1-pro-preview'))).toBe(true); expect(retried.length).toBeGreaterThan(0); @@ -87,15 +78,13 @@ describe('model chain resume', () => { await saveTestProviderApiKey(env); const service = createTestModelRunner(env, undefined, { jobId: 'job-chain-429' }); - // A 429 means "same model, later" -- advancing past it would skip a healthy model for good. + // 429 means "same model, later"; advancing past it would skip a healthy model for good. vi.spyOn(globalThis, 'fetch').mockImplementation(async () => rateLimited()); const failure = await review(service).catch((error) => error); expect(nextChainIndexOf(failure)).toBeNull(); }); - // Observed in production: pro timed out, the invocation ran out of subrequests, and the chain then - // walked all 8 remaining entries for 2 files -- 16 refusals -- before failing the chunk outright. it('stops the chain the moment the invocation runs out of subrequests', async () => { const env = createTestEnv(); await saveTestProviderApiKey(env); @@ -106,12 +95,8 @@ describe('model chain resume', () => { ); const failure = await review(service).catch((error) => error); - // One attempt, not one per configured model: the runtime refused the call, so nothing about the - // next model could make it succeed. expect(fetchMock).toHaveBeenCalledTimes(1); expect(String(failure?.message)).toMatch(/retrying later/); - // And no progress recorded: those models never ran, so marking them tried would make the resume - // memo skip healthy models for the rest of the job. expect(nextChainIndexOf(failure)).toBeNull(); }); diff --git a/packages/models/test/model/limits.spec.ts b/packages/models/test/model/limits.spec.ts index 15231967..27353a7f 100644 --- a/packages/models/test/model/limits.spec.ts +++ b/packages/models/test/model/limits.spec.ts @@ -13,8 +13,7 @@ import { } from '../../src/limits'; import { generatorFindingCap } from '@codraoss/core/prompts/file-review'; -// The whole point of these: a bin that overruns `maxOutputTokens` comes back as a repaired JSON prefix -// with its tail files silently empty, which is indistinguishable from "those files are clean". +// Overrun output repairs to a JSON prefix, silently emptying tail files: indistinguishable from clean. describe('reviewOutputBudgetTokens', () => { it('never asks for less than the floor', () => { expect(reviewOutputBudgetTokens({ findingCap: 1, fileCount: 1 })).toBe(OUTPUT_TOKENS_FLOOR); @@ -23,13 +22,12 @@ describe('reviewOutputBudgetTokens', () => { it('grows with the number of findings the prompt asked for', () => { const one = reviewOutputBudgetTokens({ findingCap: generatorFindingCap(10), fileCount: 1 }); const bin = reviewOutputBudgetTokens({ findingCap: generatorFindingCap(10), fileCount: 6 }); - // Six files at the same per-file cap need more room than one. expect(bin).toBeGreaterThan(one); expect(bin).toBeGreaterThan(OUTPUT_TOKENS_FLOOR); }); it('covers the bin ask that the old flat ceiling could not', () => { - // The regression: 6 files x 20 findings each, requested inside a flat 8192. + // Regression: 6 files x 20 findings exceeded the old flat 8192 ceiling. expect(reviewOutputBudgetTokens({ findingCap: 20, fileCount: 6 })).toBeGreaterThan(8_192); }); }); @@ -37,7 +35,6 @@ describe('reviewOutputBudgetTokens', () => { describe('resolveOutputTokenCeiling', () => { it('falls back to the provider default when no budget is stated', () => { expect(resolveOutputTokenCeiling(undefined, 65_536, 8_192)).toBe(8_192); - // A caller that omits it must be unaffected by a raised provider max. expect(resolveOutputTokenCeiling(0, 65_536, 8_192)).toBe(8_192); expect(resolveOutputTokenCeiling(Number.NaN, 65_536, 8_192)).toBe(8_192); }); @@ -46,29 +43,26 @@ describe('resolveOutputTokenCeiling', () => { expect(resolveOutputTokenCeiling(1_000, 65_536, 8_192)).toBe(8_192); expect(resolveOutputTokenCeiling(20_000, 65_536, 8_192)).toBe(20_000); expect(resolveOutputTokenCeiling(999_999, 65_536, 8_192)).toBe(65_536); - // A provider whose max is below the shared default still gets a request it accepts. expect(resolveOutputTokenCeiling(20_000, 4_096, 8_192)).toBe(4_096); }); }); describe('geminiThinkingBudgetTokens', () => { - // Thinking bills against the SAME maxOutputTokens the JSON must fit in, so raising the ceiling has to - // buy answer rather than more thinking. + // Thinking shares maxOutputTokens with JSON output, so a higher ceiling must mostly buy answer room. it('stays a minority of the ceiling', () => { expect(geminiThinkingBudgetTokens(32_768)).toBeLessThan(32_768 / 3); expect(geminiThinkingBudgetTokens(8_192)).toBeLessThan(8_192 / 3); }); it('stays inside the band every Gemini 2.5 model accepts', () => { - // Never 0 (the Pro models refuse it outright) and never above 8192 (Flash's own ceiling is lower). + // Above 0 (Pro rejects it) and under Flash's 8192 ceiling. expect(geminiThinkingBudgetTokens(1_024)).toBeGreaterThanOrEqual(1_024); expect(geminiThinkingBudgetTokens(65_536)).toBeLessThanOrEqual(8_192); }); }); describe('generatorFindingCap', () => { - // Bin size deliberately does NOT divide this; see the note on generatorFindingCap. Measured output was - // ~3% of the ceiling, so the cap has never been the limit and lowering it only removes headroom. + // Bin size intentionally doesn't divide this cap; measured output stays ~3% of ceiling. it('is 2x max_comments regardless of how many files share the call', () => { expect(generatorFindingCap(10)).toBe(20); expect(generatorFindingCap(1)).toBe(2); @@ -84,7 +78,6 @@ describe('adaptiveModelTimeoutMs', () => { }); it('scales with diff size beyond the free-line allowance', () => { - // Use line counts that stay below the MAX cap so the linear scaling is observable. expect(adaptiveModelTimeoutMs(200)).toBe(MODEL_TIMEOUT_BASE_MS + 100 * 100); expect(adaptiveModelTimeoutMs(250)).toBeGreaterThan(adaptiveModelTimeoutMs(150)); }); @@ -96,13 +89,11 @@ describe('adaptiveModelTimeoutMs', () => { describe('clampTimeoutToChainBudget', () => { it('leaves every budget the adaptive ceiling can produce untouched', () => { - // A big bin is meant to spend a whole invocation on one model and get the full ceiling. expect(clampTimeoutToChainBudget(MODEL_TIMEOUT_MAX_MS)).toBe(MODEL_TIMEOUT_MAX_MS); expect(clampTimeoutToChainBudget(MODEL_TIMEOUT_BASE_MS)).toBe(MODEL_TIMEOUT_BASE_MS); }); - // The invariant it exists to hold: the head of a chain is exempt from the budget check, so a per-call - // budget above the chain budget would let a call start that can never finish inside it. + // Chain head is exempt from the budget check, so ceiling must stay <= chain budget. it('holds the ceiling under the chain budget', () => { expect(MODEL_TIMEOUT_MAX_MS).toBeLessThanOrEqual(MODEL_FALLBACK_CHAIN_BUDGET_MS); expect(clampTimeoutToChainBudget(MODEL_FALLBACK_CHAIN_BUDGET_MS + 10_000)).toBe(MODEL_FALLBACK_CHAIN_BUDGET_MS); @@ -120,7 +111,6 @@ describe('ModelCallGate', () => { gate.run(async () => { active++; peak = Math.max(peak, active); - // Yield a couple of microtasks so tasks genuinely overlap. await Promise.resolve(); await Promise.resolve(); active--; @@ -140,7 +130,6 @@ describe('ModelCallGate', () => { throw new Error('boom'); })).rejects.toThrow('boom'); - // The slot must be free again for the next caller. const result = await gate.run(async () => 'ok'); expect(result).toBe('ok'); }); diff --git a/packages/models/test/model/service-fallbacks.spec.ts b/packages/models/test/model/service-fallbacks.spec.ts index 369015a2..2f79d41d 100644 --- a/packages/models/test/model/service-fallbacks.spec.ts +++ b/packages/models/test/model/service-fallbacks.spec.ts @@ -6,8 +6,7 @@ import { createTestEnv, saveTestProviderApiKey, createTestModelRunner } from '.. import { defaultRepoConfig } from '@codraoss/schema'; import { TokenTracker } from '@codraoss/core/token-tracker'; -// Walking the model chain: fallback, the two subrequest-budget breakers, and marking a provider -// unavailable. The inline retry ladder lives in service-retries.spec.ts. +// Chain fallback, budget breakers, provider availability. Inline retry ladder: service-retries.spec.ts. describe('ModelRunner: chain fallback, budget breakers and provider availability', () => { afterEach(() => { vi.restoreAllMocks(); @@ -20,7 +19,7 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability JSON.stringify({ error: { code: 500, message: 'Internal error encountered.', status: 'INTERNAL' } }), { status: 500, headers: { 'content-type': 'application/json' } }, ); - // The primary makes 3 attempts before failing over; the fallback succeeds on the 4th call. + // Primary fails 3x, fallback succeeds on the 4th call. const fetchMock = vi.spyOn(globalThis, 'fetch') .mockResolvedValueOnce(gemini500()) .mockResolvedValueOnce(gemini500()) @@ -85,7 +84,7 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability expect(response.modelUsed).toBe('gemini-2.5-pro'); }); - // Parse lives inside the per-model try: an unreadable 200 is that model's failure. + // Unparseable 200 counts as that model's own failure (parse is inside the per-model try). it('falls through to the next model when the primary returns an unparseable body', async () => { const geminiText = (text: string) => new Response( JSON.stringify({ @@ -117,8 +116,7 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability expect(response.modelUsed).toBe('gemini-2.5-pro'); }); - // Three `continue` paths can leave the loop with `lastError` undefined, which matches no retry - // predicate and fails the file permanently. + // Three `continue` paths can leave `lastError` undefined, matching no retry predicate. it('defers rather than throwing undefined when every model is skipped', async () => { const fetchMock = vi.spyOn(globalThis, 'fetch'); const env = createTestEnv(); @@ -143,15 +141,13 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability expect(fetchMock).not.toHaveBeenCalled(); }); - // Regression: the tail of the chain used to be exempt from the timeout breaker entirely, so a model - // that had never once answered on a job still cost every unit a full per-call budget -- 20 batches - // and 15 minutes of wall clock in production, all of it spent to re-learn the tally's verdict. + // Regression: the tail used to be exempt from the timeout breaker, wasting a full budget per unit. it('drops even the last candidate once it has never answered on this job', async () => { const fetchMock = vi.spyOn(globalThis, 'fetch'); const env = createTestEnv(); await saveTestProviderApiKey(env); - // Six strikes: past the tail's higher bar, which a merely-slow model does not reach. + // Six strikes exceeds the tail's higher bar. await env.APP_KV.put( 'jobs:job-tail-drop:chain-progress', JSON.stringify({ timeouts: { 'gemini-3.1-pro-preview': 6, 'gemini-2.5-pro': 6 } }), @@ -169,15 +165,12 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability totalLineCount: 1, }); - // Deferred, and the message says which of the two skip reasons applied. await expect(promise).rejects.toThrow(/No configured review model was attempted.*repeated timeouts/); await promise.catch((error) => expect(isRetryableModelError(error)).toBe(true)); - // The whole point: not one call was paid for. expect(fetchMock).not.toHaveBeenCalled(); }); - // The other side of the same rule: a merely-slow tail still gets its shot, because deferring with no - // model attempted is the worse outcome when the model does sometimes answer. + // Same rule, other side: a merely-slow tail still gets its shot since deferring untried is worse. it('still tries the last candidate when it is only mid-chain slow', async () => { const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce( new Response( @@ -209,7 +202,6 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability totalLineCount: 1, }); - // The struck primary is skipped, the tail is attempted anyway, and it answers. expect(fetchMock).toHaveBeenCalledTimes(1); expect(String(fetchMock.mock.calls[0][0])).toContain('/models/gemini-2.5-pro:generateContent'); expect(response.modelUsed).toBe('gemini-2.5-pro'); @@ -248,7 +240,7 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability const env = createTestEnv(); await saveTestProviderApiKey(env); const tracker = new TokenTracker(); - tracker.incrementSubrequests(40); // above the near-limit threshold (MAX_SUBREQUESTS 50 - SAFE_MARGIN 25) + tracker.incrementSubrequests(40); // above near-limit threshold (50 - 25 margin) const service = createTestModelRunner(env, tracker); const response = await service.reviewFile({ @@ -278,9 +270,7 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability expect(response.modelUsed).toBe('gemini-3.1-pro-preview'); }); - // The counterpart to the test above: the primary gets its shot at a merely-tight budget, but not at - // one that cannot cover the call. Previously it transmitted the prompt regardless and the runtime - // refused it, losing the unit AND the prompt -- three files' worth in one observed invocation. + // Counterpart: primary is skipped only when budget truly can't cover the call, not merely tight. it('will not commit a prompt when the budget cannot cover the call', async () => { const fetchMock = vi.spyOn(globalThis, 'fetch'); const env = createTestEnv(); @@ -304,13 +294,11 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability // Deferred, not failed: a fresh invocation has a fresh budget. await expect(promise).rejects.toThrow(/retrying later/); await promise.catch((error) => expect(isRetryableModelError(error)).toBe(true)); - // The whole point -- nothing went over the wire. expect(fetchMock).not.toHaveBeenCalled(); }); it('skips remaining fallback models (instead of spending more of the shared budget) once near the subrequest limit', async () => { - // The primary retries internally, so return a fresh Response per call (a body reads once). - // 503, not 500: only a genuinely transient failure produces a retryable deferral. + // Fresh Response per call (body reads once); 503 not 500 keeps the failure retryable. const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async () => new Response( JSON.stringify({ error: { code: 503, message: 'The model is overloaded and currently unavailable.', status: 'UNAVAILABLE' } }), @@ -320,7 +308,7 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability const env = createTestEnv(); await saveTestProviderApiKey(env); const tracker = new TokenTracker(); - tracker.incrementSubrequests(40); // above the near-limit threshold (MAX_SUBREQUESTS 50 - SAFE_MARGIN 25) + tracker.incrementSubrequests(40); // above near-limit threshold (50 - 25 margin) const service = createTestModelRunner(env, tracker); await expect( @@ -348,8 +336,6 @@ describe('ModelRunner: chain fallback, budget breakers and provider availability }), ).rejects.toSatisfy(isRetryableModelError); - // Only the primary model was attempted; the fallback was skipped rather than risking tipping - // the shared invocation over Cloudflare's subrequest cap, deferring the file for a later retry. expect(fetchMock.mock.calls.length).toBeGreaterThan(0); for (const call of fetchMock.mock.calls) { expect(String(call[0])).toContain('/models/gemini-3.1-pro-preview:generateContent'); diff --git a/packages/models/test/model/service-grammar-rejection.spec.ts b/packages/models/test/model/service-grammar-rejection.spec.ts index 2d0d8562..0c14f72f 100644 --- a/packages/models/test/model/service-grammar-rejection.spec.ts +++ b/packages/models/test/model/service-grammar-rejection.spec.ts @@ -5,9 +5,7 @@ import { buildReviewResponseSchema } from '@codraoss/core/prompts/file-review'; import { createTestEnv, saveTestProviderApiKey, createTestModelRunner } from '../../../../test/helpers'; import { defaultRepoConfig } from '@codraoss/schema'; -// Split out of service-retries.spec.ts: a 400 matches no transient pattern, so grammar rejection is -// its own ladder rung -- drop responseJsonSchema, retry once, latch it off -- not part of the -// transient-failure ladder those specs cover. +// Split out of service-retries.spec.ts: a non-transient 400 gets its own ladder rung here. describe('ModelRunner: response-grammar rejection', () => { afterEach(() => { vi.restoreAllMocks(); @@ -32,9 +30,7 @@ describe('ModelRunner: response-grammar rejection', () => { const withGrammar = { systemPrompt: 'system', userPrompt: 'user', responseSchema: buildReviewResponseSchema(10) }; - // Google sometimes 400s with nothing but "Request contains an invalid argument." and no - // `error.details`, so none of the specific schema markers can fire. That used to fail the file on its - // first 400 -- permanently, since a 400 is not transient -- with no grammar probe and no fallback. + // Bare 400, no error.details: used to fail the file permanently with no probe or fallback. it('drops the response grammar and retries on a 400 that explains nothing', async () => { const bareInvalidArgument = () => new Response( @@ -64,12 +60,10 @@ describe('ModelRunner: response-grammar rejection', () => { ); expect(fetchMock).toHaveBeenCalledTimes(2); - // The first attempt carried the grammar and the retry did not. const firstBody = JSON.parse(String((fetchMock.mock.calls[0][1] as RequestInit).body)); const retryBody = JSON.parse(String((fetchMock.mock.calls[1][1] as RequestInit).body)); expect(firstBody.generationConfig.responseJsonSchema).toBeDefined(); expect(retryBody.generationConfig.responseJsonSchema).toBeUndefined(); - // Still asks for JSON, or the schema-less attempt returns prose. expect(retryBody.generationConfig.responseMimeType).toBe('application/json'); expect(response.rawText).toContain('"findings"'); }); @@ -85,10 +79,10 @@ describe('ModelRunner: response-grammar rejection', () => { expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body)).generationConfig.responseJsonSchema).toBeDefined(); expect(JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body)).generationConfig.responseJsonSchema).toBeUndefined(); expect(response.rawText).toContain('"findings"'); - // Surfaced so the "Test connection" preflight cannot call a grammar-incapable endpoint working. + // Preflight ("Test connection") depends on this surfacing. expect(response.degraded).toBe('schema-dropped'); - // The probe is not spent on an unrelated 400, nor when there was no grammar to drop. + // Probe must not fire on an unrelated 400, nor when there was no grammar to drop. for (const [message, input] of [ ['API key not valid. Please pass a valid API key.', withGrammar], ['Invalid value at generation_config.schema.', { systemPrompt: 'system', userPrompt: 'user' }], @@ -102,13 +96,10 @@ describe('ModelRunner: response-grammar rejection', () => { } }); - // The latch used to be set only when the schema-less retry SUCCEEDED. If that retry then 429'd, - // the next call re-probed with the grammar -- a wasted 400 plus a second full prompt, every call. + // Latch used to set only when the schema-less retry succeeded; a later 429 would re-probe every call. it('latches the grammar off even when the schema-less retry itself fails', async () => { const fetchMock = vi.spyOn(globalThis, 'fetch') - // Grammar rejected, then the schema-less probe fails for an unrelated reason. Deliberately - // not a 429: that would cool the model off and the second review would skip it entirely, - // masking whether the latch held. + // Not a 429 deliberately: that would cool the model and skip call 2, hiding whether the latch held. .mockResolvedValueOnce(gemini400('Unknown name "responseJsonSchema" at \'generation_config\'.')) .mockResolvedValueOnce(gemini400('API key not valid. Please pass a valid API key.')) .mockResolvedValue(geminiOk()); @@ -138,9 +129,7 @@ describe('ModelRunner: response-grammar rejection', () => { expect(fetchMock.mock.calls.length).toBe(callsAfterFirstReview + 1); }); - // Observed in production: Gemini 3.x sends a generic top-level message and puts the real reason - // in `details`. Without reading it the grammar rejection looked like an unrelated 400 and the model - // was dropped from the chain entirely. + // Gemini 3.x puts the real reason in error.details; without reading it this looked like an unrelated 400. it('reads the rejection reason out of error.details, not just the message', async () => { const fetchMock = vi.spyOn(globalThis, 'fetch') .mockResolvedValueOnce(new Response( @@ -169,8 +158,7 @@ describe('ModelRunner: response-grammar rejection', () => { }); it('gives the attempt back for the probe, but only once', async () => { - // The probe isn't a transient rung: without the give-back, a ladder spent on 5xx could never - // drop the schema. The latch stops it looping. + // Without the give-back, a ladder spent on 5xx could never drop the schema. const gemini500 = () => new Response(JSON.stringify({ error: { code: 500, message: 'Internal error encountered.' } }), { status: 500, headers: { 'content-type': 'application/json' } }); const ladderSpent = vi.spyOn(globalThis, 'fetch') .mockResolvedValueOnce(gemini500()) @@ -184,7 +172,7 @@ describe('ModelRunner: response-grammar rejection', () => { expect(JSON.parse(String(ladderSpent.mock.calls[3]?.[1]?.body)).generationConfig.responseJsonSchema).toBeUndefined(); expect(response.degraded).toBe('schema-dropped'); - // mockImplementation, not mockResolvedValue: a retried call cannot re-read one Response body. + // mockImplementation: a retried call can't reread one Response body. vi.restoreAllMocks(); const persistent = vi.spyOn(globalThis, 'fetch') .mockImplementation(async () => gemini400('Invalid JSON payload received. Unknown name "responseJsonSchema".')); @@ -193,7 +181,6 @@ describe('ModelRunner: response-grammar rejection', () => { reviewWithGoogle({ apiKey: 'test-key' }, 'gemini-3.1-pro-preview', withGrammar), ).rejects.toThrow(/400/); - // Two, not three and not unbounded: one with the grammar, one without, then throw. expect(persistent).toHaveBeenCalledTimes(2); }); }); diff --git a/packages/models/test/url-guard.spec.ts b/packages/models/test/url-guard.spec.ts index e0e42af0..d5ec2e06 100644 --- a/packages/models/test/url-guard.spec.ts +++ b/packages/models/test/url-guard.spec.ts @@ -2,12 +2,7 @@ import { describe, expect, it } from 'vitest'; import { assertPublicBaseUrl, isPrivateHost, isValidPublicUrl } from '../src/url-guard'; import { ProviderRequestError } from '../src/types'; -// A provider's base URL comes from the dashboard and is then fetched server-side, so an unguarded -// adapter turns that form into an SSRF primitive. -// -// The guard existed in the Google and OpenAI adapters and was simply missing from Anthropic, which -// fetched `config.baseUrl` unchecked - the copy-paste is why nobody noticed. These tests cover the -// shared module so all three are protected by the same assertions. +// Guards SSRF via user-supplied provider base URLs (was missing from Anthropic adapter). describe('provider base URL guard', () => { it('rejects loopback, link-local and RFC1918 hosts', () => { const blocked = [ @@ -17,17 +12,14 @@ describe('provider base URL guard', () => { 'http://192.168.1.1/v1', 'http://172.16.0.1/v1', 'http://172.31.255.255/v1', - // 169.254.0.0/16 is where the AWS/Azure metadata service lives. - 'http://169.254.169.254/latest/meta-data', + 'http://169.254.169.254/latest/meta-data', // cloud metadata range ]; for (const url of blocked) { expect(isValidPublicUrl(url), url).toBe(false); } }); - // The original guard carried `/^::1$/`, which never matched: `URL.hostname` returns an IPv6 - // literal with its brackets ("[::1]"). IPv6 loopback and the unique-local/link-local ranges were - // therefore reachable in both adapters that had a guard at all. + // hostname includes brackets, e.g. "[::1]"; a bare /^::1$/ regex would miss it it('rejects IPv6 private ranges, brackets and all', () => { const blocked = [ 'http://[::1]/v1', @@ -40,12 +32,9 @@ describe('provider base URL guard', () => { for (const url of blocked) { expect(isValidPublicUrl(url), url).toBe(false); } - // A genuinely public IPv6 address must still pass. expect(isValidPublicUrl('http://[2606:4700::1111]/v1')).toBe(true); }); - // Public-looking names that resolve only from inside a cloud instance, so the range checks alone - // would let them through. it('rejects cloud metadata endpoints by name', () => { expect(isValidPublicUrl('http://metadata.google.internal/computeMetadata/v1')).toBe(false); expect(isValidPublicUrl('http://100.100.100.200/latest/meta-data')).toBe(false); @@ -62,8 +51,7 @@ describe('provider base URL guard', () => { expect(isValidPublicUrl('https://api.anthropic.com/v1')).toBe(true); expect(isValidPublicUrl('https://generativelanguage.googleapis.com/v1beta')).toBe(true); expect(isValidPublicUrl('https://api.openai.com/v1')).toBe(true); - // 172.32 is outside the private 172.16-172.31 block, so the range regex must not over-match. - expect(isValidPublicUrl('http://172.32.0.1/v1')).toBe(true); + expect(isValidPublicUrl('http://172.32.0.1/v1')).toBe(true); // outside 172.16-172.31 block }); it('classifies hosts without needing a full URL', () => { @@ -83,8 +71,6 @@ describe('provider base URL guard', () => { } }); - // Every adapter defaults to its own vendor URL when none is configured, so an absent base URL - // must pass rather than throw. it('accepts an absent base URL', () => { expect(() => assertPublicBaseUrl(null, 'Anthropic')).not.toThrow(); expect(() => assertPublicBaseUrl(undefined, 'Google')).not.toThrow(); diff --git a/packages/ui/src/components/motion/smooth-scroll.tsx b/packages/ui/src/components/motion/smooth-scroll.tsx index b13a03a8..052eda8b 100644 --- a/packages/ui/src/components/motion/smooth-scroll.tsx +++ b/packages/ui/src/components/motion/smooth-scroll.tsx @@ -11,8 +11,7 @@ import { useRef, } from 'react'; -// Lenis' own expo-out curve, kept as a named fn (not a lib/ease token): tokens are bezier control -// points for the motion lib, while Lenis needs a (t) => number easing fn. +// Named fn, not a lib/ease token: Lenis needs a (t) => number easing fn, not bezier points. const EASE_SCROLL = (t: number) => Math.min(1, 1.001 - 2 ** (-10 * t)); export type ScrollTarget = number | string | HTMLElement; @@ -24,15 +23,13 @@ export type ScrollToOptions = { }; export type SmoothScrollApi = { - /** Underlying Lenis instance, or null on the reduced-motion / native path. */ + /** Null on the reduced-motion / native path. */ lenis: Lenis | null; - /** Current scroll offset in px. */ scrollY: MotionValue; - /** Scroll position as 0..1 of the scrollable height. */ progress: MotionValue; - /** Signed scroll velocity (px/frame). */ + /** px/frame. */ velocity: MotionValue; - /** Programmatic smooth scroll. Respects reduced motion (jumps instantly). */ + /** Jumps instantly under reduced motion. */ scrollTo: (target: ScrollTarget, options?: ScrollToOptions) => void; }; @@ -40,15 +37,14 @@ const SmoothScrollContext = createContext(null); export interface SmoothScrollProps { children: ReactNode; - /** Drive the page (window) when true, or a contained scroll area when false. */ + /** True drives window scroll; false scrolls a contained area. */ root?: boolean; - /** Smoothing factor; lower is smoother and heavier. */ + /** Lower = smoother, heavier. */ lerp?: number; - /** Wheel / programmatic ease duration in seconds. */ duration?: number; orientation?: 'vertical' | 'horizontal'; wheelMultiplier?: number; - /** Off by default - native touch momentum is already good on mobile. */ + /** Off by default: native touch momentum is already good on mobile. */ touch?: boolean; className?: string; } @@ -87,7 +83,6 @@ function resolveTop( return el.offsetTop + offset; } -/** Pushes Lenis' live scroll state into the shared motion values. */ function LenisBridge({ scrollY, progress, @@ -113,7 +108,6 @@ function LenisBridge({ return null; } -/** Native scroll listener for the reduced-motion path and the no-provider fallback. */ function useNativeScrollSync( enabled: boolean, getTarget: () => ScrollSource | null, @@ -184,7 +178,6 @@ export function SmoothScroll({ [reduce, nativeSource], ); - // Reduced motion drives the native listener; otherwise LenisBridge feeds the values instead. useNativeScrollSync(!!reduce, nativeSource, scrollY, progress, velocity); const api = useMemo( @@ -215,8 +208,7 @@ export function SmoothScroll({ smoothWheel: true, syncTouch: touch, easing: EASE_SCROLL, - // Without this, Lenis preventDefault()s every wheel event, blocking nested scrollable - // elements (log panes, drawers, option lists). Escape hatch: `data-lenis-prevent`. + // Else Lenis preventDefault()s every wheel event, blocking nested scroll areas. allowNestedScroll: true, }} > diff --git a/packages/ui/src/lib/prompt-diff.ts b/packages/ui/src/lib/prompt-diff.ts index 7702d95b..6a0bfd61 100644 --- a/packages/ui/src/lib/prompt-diff.ts +++ b/packages/ui/src/lib/prompt-diff.ts @@ -1,14 +1,4 @@ -/** - * Parsing for the rendered review prompt shown in the diff viewer. - * - * NOT `parseUnifiedDiff` from `@server/core/diff`: that parses real git output for the review - * pipeline; this reads the padded, gutter-prefixed form the prompt renders for the model. - */ - -// Codra renders each file's diff body as 4-wide padded number columns: -// " " e.g. " 615 615 const x = 1" -// We read those embedded line numbers directly and fall back to standard git-diff lines for -// anything else. Only content inside a hunk is parsed, so the prompt preamble is ignored. +// Parses the prompt's padded gutter diff ("NNNN MMMM Pcontent"), not raw git output (see parseUnifiedDiff in @server/core/diff). export interface DiffRow { kind: 'add' | 'del' | 'ctx' | 'hunk'; @@ -19,7 +9,6 @@ export interface DiffRow { const HUNK_RE = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/; -/** Parse a padded body line ("NNNN MMMM Pcontent"); null if it isn't one. */ function parsePaddedLine(line: string) { if (line.length < 11 || line[4] !== ' ' || line[9] !== ' ') return null; const prefix = line[10]; @@ -33,7 +22,7 @@ function parsePaddedLine(line: string) { export function parsePromptDiff(diff: string): DiffRow[] { const rows: DiffRow[] = []; - let started = false; // inside a hunk + let started = false; let oldNo = 0; let newNo = 0; @@ -46,10 +35,10 @@ export function parsePromptDiff(diff: string): DiffRow[] { rows.push({ kind: 'hunk', oldNo: null, newNo: null, text: line }); continue; } - if (!started) continue; // skip prompt preamble before the first hunk + if (!started) continue; // preamble before first hunk if (line.startsWith('diff --git')) { started = false; continue; } - if (line.startsWith('\\')) continue; // "\ No newline at end of file" - if (line.startsWith('[NOTE')) continue; // truncation note + if (line.startsWith('\\')) continue; // no-newline marker + if (line.startsWith('[NOTE')) continue; const padded = parsePaddedLine(line); if (padded) { @@ -63,21 +52,20 @@ export function parsePromptDiff(diff: string): DiffRow[] { continue; } - // Standard git-diff fallback. const p = line[0]; if (p === '+') rows.push({ kind: 'add', oldNo: null, newNo: newNo++, text: line.slice(1) }); else if (p === '-') rows.push({ kind: 'del', oldNo: oldNo++, newNo: null, text: line.slice(1) }); else if (p === ' ') rows.push({ kind: 'ctx', oldNo: oldNo++, newNo: newNo++, text: line.slice(1) }); } - // Drop a single trailing blank context row left behind by the final newline. + // drop trailing blank row from final newline const last = rows[rows.length - 1]; if (last && last.kind === 'ctx' && last.text === '') rows.pop(); return rows; } -/** Cheap line scan (no row objects) so collapsed panels never pay for a full parse. */ +// line-only scan; avoids full parse for collapsed panels export function diffStats(diff: string | null) { if (!diff) return { adds: 0, dels: 0, total: 0 }; let adds = 0; diff --git a/scripts/outdated-rate.ts b/scripts/outdated-rate.ts index 1d9eaeb4..c7549f36 100644 --- a/scripts/outdated-rate.ts +++ b/scripts/outdated-rate.ts @@ -1,17 +1,5 @@ -/** - * Outdated Rate: the share of flagged lines a developer actually modified afterwards. - * - * npx vite-node scripts/outdated-rate.ts -- --repo devarshishimpi/codra - * - * Chosen over precision because it needs ZERO human annotation. It is also the retirement signal: a - * rule that is right but never acted on is a rule worth deleting. - * - * For a job A that posted findings, find the next job B on the same PR at a DIFFERENT commit, diff - * the two heads, and check whether a changed line hashes to one of A's anchor hashes. - * - * Deliberately NOT the pure-SQL alternative ("did B re-derive the same fingerprint_v2?"): that - * cannot separate "the line was fixed" from "the model was flaky". - */ +/** Outdated Rate: share of flagged lines later modified. No annotation needed; a retirement + * signal for unacted rules. npx vite-node scripts/outdated-rate.ts -- --repo devarshishimpi/codra */ import { readFileSync } from 'node:fs'; import postgres from 'postgres'; import { buildUnifiedDiffFromFiles, parseUnifiedDiff } from '@server/core/diff'; @@ -35,8 +23,8 @@ type Candidate = { rule_ids: (string | null)[]; }; -/** Unauthenticated compare, same as the fixture recorder. Rebuilt from the JSON file list because - * the unified-diff media type 406s past 20,000 lines. */ +// Diffs job A vs next job B on same PR (not pure SQL: can't tell fixed from flaky). Rebuilt from +// JSON file list since unified-diff media type 406s past 20,000 lines. async function changedLineHashes(base: string, head: string): Promise> { const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/compare/${base}...${head}`, { headers: { accept: 'application/vnd.github+json' }, @@ -50,7 +38,6 @@ async function changedLineHashes(base: string, head: string): Promise) -───────────────────────────────────────────────────── */ +/* DARK MODE (.dark class on ) */ .dark { - /* Surfaces - Deep & Layered */ - /* Surfaces - Ultra-Dark */ --background: #000000; --foreground: oklch(98% 0.005 115); --card: #09090b; @@ -136,11 +103,9 @@ --popover: #09090b; --popover-foreground: oklch(98% 0.005 115); - --primary: oklch(94% 0.23 115); /* #E0FE56 */ + --primary: oklch(94% 0.23 115); --primary-foreground: oklch(12% 0.04 115); - /* Primary buttons: bright lime text/border on a faint lime fill. Solid - accent (tab underlines, progress bars) goes back to the bright neon here. */ --btn-primary-bg: #CCE800; --btn-primary-fg: #CCE800; --btn-primary-border: color-mix(in oklab, #CCE800 50%, transparent); @@ -158,7 +123,7 @@ --destructive: oklch(60% 0.220 25); --destructive-foreground: oklch(10% 0.015 115); - --border: oklch(22% 0.02 115); /* #1A1A1A */ + --border: oklch(22% 0.02 115); --input: oklch(22% 0.02 115); --ring: oklch(94% 0.23 115); @@ -179,28 +144,22 @@ --shadow-md: 0 4px 12px oklch(0% 0 0 / 0.45), 0 1px 4px oklch(0% 0 0 / 0.25); --shadow-lg: 0 12px 24px -4px oklch(0% 0 0 / 0.5), 0 4px 12px -2px oklch(0% 0 0 / 0.3); - /* Code Blocks (Zinc) */ --code-bg: oklch(20.5% 0.005 286.3); --code-fg: oklch(86.5% 0.005 286.3); --code-border: oklch(27.4% 0.006 286.3); - /* Diff add/del - green/red bands on the near-black diff panel. Dark. */ --diff-add-bg: oklch(30% 0.06 150); --diff-add-fg: oklch(82% 0.15 150); --diff-del-bg: oklch(31% 0.08 27); --diff-del-fg: oklch(80% 0.16 27); } -/* ───────────────────────────────────────────────────── - Tailwind v4 theme tokens (@theme inline = dynamic) -───────────────────────────────────────────────────── */ +/* Tailwind v4 theme tokens (@theme inline = dynamic) */ @theme inline { --font-sans: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif; --font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace; - /* Surface palette → Tailwind utilities. Reference the raw --ui-* vars - (defined per-theme in :root/.dark) so the utilities flip with `.dark`. - Brand tracks Codra's lime `--primary`. */ + /* References raw --ui-* vars so utilities flip with .dark. */ --color-ui-base: var(--ui-base); --color-ui-canvas: var(--ui-canvas); --color-ui-line: var(--ui-line); @@ -246,16 +205,13 @@ --color-info-bg: var(--info-bg); --color-info-border: var(--info-border); - /* Common radius scale - one system across the app. Surfaces (rounded-lg / - rounded-xl / `.surface`) match the dashboard stat cards; controls - (rounded-md: inputs, buttons, dropdown items) sit a touch tighter. */ - --radius-sm: 0.3125rem; /* 5px */ - --radius-md: 0.4375rem; /* 7px - controls */ - --radius-lg: 0.6875rem; /* 11px - cards, panels, dropdown menus */ - --radius-xl: 0.6875rem; /* 11px - large surfaces (`.surface`) */ - --radius-2xl: 0.875rem; /* 14px */ + /* radius-lg == radius-xl intentionally: cards and .surface share one size. */ + --radius-sm: 0.3125rem; + --radius-md: 0.4375rem; + --radius-lg: 0.6875rem; + --radius-xl: 0.6875rem; + --radius-2xl: 0.875rem; - /* Fluid Typography Scale (Ratio: 1.25) */ --text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem; @@ -266,7 +222,6 @@ --text-4xl: clamp(2.5rem, 10vw, 6rem); --text-display: clamp(3rem, 12vw, 9rem); - /* Spacing Scale (Fluid) */ --space-xs: clamp(0.5rem, 1vw, 0.75rem); --space-sm: clamp(1rem, 2vw, 1.5rem); --space-md: clamp(1.5rem, 4vw, 3rem); @@ -274,9 +229,6 @@ --space-xl: clamp(6rem, 12vw, 10rem); } -/* ───────────────────────────────────────────────────── - Base resets -───────────────────────────────────────────────────── */ @layer base { *, *::before, *::after { box-sizing: border-box; } @@ -305,7 +257,6 @@ color 0.3s var(--ease-out-expo); } - /* High-Contrast Background Glows */ body::before { content: ''; pointer-events: none; @@ -316,7 +267,6 @@ transition: opacity 0.4s; } - /* Dark mode: high contrast green glow */ .dark body::before { background: radial-gradient(ellipse 60% 45% at 0% 0%, oklch(20% 0.15 115 / 0.15), transparent 60%), @@ -328,9 +278,6 @@ pre, code { font-family: var(--font-mono); } } -/* ───────────────────────────────────────────────────── - Keyframes -───────────────────────────────────────────────────── */ @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } @@ -390,7 +337,6 @@ to { transform: rotate(360deg); } } -/* Radix & UI Animations */ @utility animate-in { animation-duration: 200ms; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); @@ -485,9 +431,7 @@ } } -/* ───────────────────────────────────────────────────── - Scroll-reveal utility (toggled by JS IntersectionObserver) -───────────────────────────────────────────────────── */ +/* Toggled by JS IntersectionObserver. */ @utility reveal-on-scroll { opacity: 0; transform: translateY(24px); @@ -507,30 +451,17 @@ } } -/* Stagger helpers used on siblings */ @utility reveal-delay-1 { transition-delay: 80ms !important; } @utility reveal-delay-2 { transition-delay: 160ms !important; } @utility reveal-delay-3 { transition-delay: 240ms !important; } @utility reveal-delay-4 { transition-delay: 320ms !important; } -/* ───────────────────────────────────────────────────── - Scrollbars - ONE treatment everywhere. - • Neutral (default grey), not the brand colour. - • Auto-hiding: the thumb is invisible until the container is actively - scrolling. A global scroll listener (app-shell.tsx) sets `data-scrolling` - on whatever just scrolled and clears it ~700ms after scrolling stops. - • Applied via `*` (Firefox doesn't inherit scrollbar-* ) and the global - WebKit pseudo-elements, so every scroll container in the app matches - without any per-element opt-in class. - Track space is always reserved while an element is scrollable, so revealing - the thumb never shifts layout. -───────────────────────────────────────────────────── */ +/* Scrollbars: neutral grey, auto-hidden via app-shell.tsx toggling data-scrolling. */ * { scrollbar-width: thin; scrollbar-color: transparent transparent; } -/* Firefox: reveal a neutral thumb only while scrolling. */ [data-scrolling] { scrollbar-color: oklch(0% 0 0 / 0.32) transparent; } @@ -538,8 +469,7 @@ scrollbar-color: oklch(100% 0 0 / 0.3) transparent; } -/* WebKit (Chrome, Safari, Edge). Transparent border + padding-box clip insets - the visible thumb so it reads as a slim rounded bar. */ +/* Transparent border + padding-box clip insets the thumb into a slim bar. */ ::-webkit-scrollbar { width: 10px; height: 10px; @@ -567,9 +497,6 @@ background-color: oklch(100% 0 0 / 0.45); } -/* ───────────────────────────────────────────────────── - Reduced Motion: global safety net -───────────────────────────────────────────────────── */ @media (prefers-reduced-motion: reduce) { *, *::before, @@ -581,9 +508,6 @@ } } -/* ───────────────────────────────────────────────────── - Surface & Utilities -───────────────────────────────────────────────────── */ @utility surface { @apply bg-card border border-border rounded-xl; box-shadow: var(--shadow-md); @@ -631,8 +555,7 @@ @apply animate-[shimmer_1.8s_linear_infinite] rounded-sm; } -/* Unlayered `.skeleton` shimmer, kept at this specificity so it can't be - beaten by any later utility. */ +/* Unlayered, kept at this specificity so no later utility can beat it. */ .skeleton { background: linear-gradient( @@ -645,8 +568,7 @@ animation: shimmer 1.8s linear infinite !important; } -/* Recolour a indicator to the info/blue token (model-call bars), - overriding the default lime brand fill. */ +/* Model-call bars: override default lime fill with info/blue. */ .meter-indicator-info { background-image: none !important; background-color: var(--info) !important; @@ -707,9 +629,6 @@ @apply w-[7px] h-[7px] rounded-full bg-info inline-block; } -/* ───────────────────────────────────────────────────── - Recharts overrides -───────────────────────────────────────────────────── */ .recharts-default-tooltip { background: var(--card) !important; border: 1px solid var(--border) !important; @@ -721,23 +640,18 @@ box-shadow: 0 8px 32px oklch(0% 0 0 / 0.5) !important; } -/* Sidebar is flat against the page canvas; its chrome (nav rows, controls, - text) is styled with the app's ui-* / --primary tokens via Tailwind classes - in app-shell.tsx, so it reads as the same system as the rest of the UI. */ - .app-shell-content { - --background: oklch(97.8% 0.002 286.3); /* lighter zinc - between zinc-100 and white */ - --card: oklch(100% 0 0); /* #ffffff */ - --muted: oklch(90.9% 0.004 286.3); /* #e4e4e7 */ + --background: oklch(97.8% 0.002 286.3); + --card: oklch(100% 0 0); + --muted: oklch(90.9% 0.004 286.3); --popover: oklch(100% 0 0); - --secondary: oklch(88.5% 0.004 286.3); /* #e2e2e6 */ - --border: oklch(90.9% 0.004 286.3); /* #e4e4e7 */ - --input: oklch(90.9% 0.004 286.3); /* #e4e4e7 */ + --secondary: oklch(88.5% 0.004 286.3); + --border: oklch(90.9% 0.004 286.3); + --input: oklch(90.9% 0.004 286.3); } .dark .app-shell-content { - /* Zinc, matching the light block's hue: a cool neutral (hue 286.3, low - chroma) rather than the warm olive cast that hue 115 gave the card. */ + /* Cool neutral hue 286.3, not hue 115 which gave the card a warm olive cast. */ --background: oklch(18% 0.006 286.3); --card: oklch(18% 0.006 286.3); --muted: oklch(22% 0.006 286.3); @@ -747,17 +661,14 @@ --input: oklch(22% 0.006 286.3); } -/* Sidebar rows: the SharedLayoutBg pill is the sole hover affordance - the - row itself never transforms on hover/focus/active. Active rows paint their - own neutral fill (Tailwind classes in app-shell.tsx). */ +/* SharedLayoutBg pill is the sole hover affordance; row itself never transforms. */ .dashboard-sidebar-action:hover, .dashboard-sidebar-action:focus-visible, .dashboard-sidebar-action:active { transform: none !important; } -/* Skimmer shine on the selected row: a light beam parked off-screen that - sweeps across once when the active row is hovered/focused. */ +/* Light beam parked off-screen, sweeps across once on hover/focus. */ .dashboard-sidebar-shine { transform: skew(-13deg) translateX(-130%); transition: transform 0ms linear; @@ -791,9 +702,7 @@ @apply text-2xl md:text-3xl lg:text-[2.25rem] font-bold tracking-[-0.04em] leading-none text-foreground tabular-nums; } -/* Fonts for the KPI-card surface. Geist for the sans (same family as - Geist Mono), scoped here because the global @theme sets --font-sans / - --font-mono to the app's default families. */ +/* Geist, scoped locally since global @theme sets --font-sans/mono to app defaults. */ .ui-font-sans { font-family: 'Geist', ui-sans-serif, system-ui, sans-serif; } @@ -802,10 +711,7 @@ font-feature-settings: 'tnum' 1; } -/* Shared panel chrome matching the dashboard stat cards: white/black surface, - ui-line border (deeper in dark), Geist, 11px radius. Use for table cards and - section panels so everything reads as one system. `.ui-well` is the matching - recessed strip (table header rows, footers) - the stat cards' inner panel. */ +/* Matches dashboard stat-card chrome; .ui-well is its recessed inner panel. */ .ui-panel { font-family: 'Geist', ui-sans-serif, system-ui, sans-serif; border-radius: var(--radius-lg); @@ -817,17 +723,14 @@ border-color: oklch(0.27 0 0); } .ui-well { - /* Slightly lighter + warmer than pure gray so recessed panels (table - headers, stat-card inner wells) read as a soft tint, not a flat slab. */ background: oklch(97.8% 0.002 286.3); } .dark .ui-well { background: oklch(19% 0 0); } -/* Diff-viewer syntax tokens (sugar-high, see src/client/lib/highlight.tsx). Muted, - theme-aware hues so highlighted code stays readable on the add/del tints. - sugar-high emits `color: var(--sh-)` per token. */ +/* Syntax tokens for sugar-high (src/client/lib/highlight.tsx); it emits + color: var(--sh-) per token. */ :root { --sh-keyword: oklch(48% 0.19 305); --sh-string: oklch(46% 0.12 150); @@ -851,13 +754,11 @@ } .sh__token--comment { font-style: italic; } -/* Diff row tints + add/del number colors (theme vars set above). */ .diff-add { background-color: var(--diff-add-bg); } .diff-del { background-color: var(--diff-del-bg); } .diff-add-fg { color: var(--diff-add-fg); } .diff-del-fg { color: var(--diff-del-fg); } -/* Late-arriving header/meta content eases in instead of popping. */ @keyframes ui-fade-in { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } @@ -866,15 +767,12 @@ animation: ui-fade-in 0.25s ease-out both; } -/* Diff-viewer file tree: indent guide lines + connector ticks on nested - levels, and an animated expand/collapse (grid-rows 0fr ↔ 1fr). */ .diff-tree ul { list-style: none; margin: 0; padding: 0; } .diff-tree ul ul { - /* Tight per-level indent (18px) so deeply nested paths keep usable width. */ margin-left: 10px; padding-left: 8px; border-left: 1px solid var(--ui-line); @@ -894,8 +792,7 @@ } .diff-tree-children { display: grid; - /* The implicit column would size to content (auto), leaving nested rows narrow; - pin it to full width so file rows stretch edge-to-edge like the top level. */ + /* Implicit column would size to content (auto); pin full width so rows stretch edge-to-edge. */ grid-template-columns: minmax(0, 1fr); grid-template-rows: 1fr; transition: grid-template-rows 0.25s ease-in-out; @@ -908,14 +805,9 @@ min-width: 0; } -/* `.thin-scroll` and `.auto-hide-scroll` are retained as no-op aliases: the - neutral, auto-hiding treatment is now global (see the Scrollbars block - above), so these classes need no rules of their own. Existing markup that - references them keeps working and matches everything else. */ +/* .thin-scroll / .auto-hide-scroll kept as no-op aliases: the treatment is + now global (Scrollbars block above); existing markup referencing them still works. */ -/* The diff-tree scroller only needs its functional traits; colour + auto-hide - come from the global rules. `scrollbar-gutter: stable` keeps it clear of the - panel's rounded corners. */ .diff-tree-scroll { overscroll-behavior: contain; scrollbar-gutter: stable; @@ -959,18 +851,14 @@ & hr { @apply border-border my-[1.5em]; } } -/* ───────────────────────────────────────────────────── - Sonner Toast - Premium overrides -───────────────────────────────────────────────────── */ +/* Sonner toast overrides */ -/* ── Outer list / viewport ───────────────────────── */ [data-sonner-toaster] { --offset: 1.25rem !important; --width: min(22rem, calc(100vw - 2rem)) !important; font-family: var(--font-sans) !important; } -/* ── Base toast shell ─────────────────────────────── */ .codra-toast { display: flex !important; align-items: flex-start !important; @@ -986,11 +874,9 @@ 0 1px 4px oklch(0% 0 0 / 0.06), inset 0 1px 0 oklch(100% 0 0 / 0.05) !important; - /* light defaults (overridden per-variant below) */ background: oklch(99.5% 0.004 115) !important; color: oklch(15% 0.02 115) !important; - /* smooth entrance */ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1) !important; } @@ -1003,7 +889,6 @@ inset 0 1px 0 oklch(100% 0 0 / 0.04) !important; } -/* ── Title ────────────────────────────────────────── */ .codra-toast-title { font-size: 0.8125rem !important; font-weight: 600 !important; @@ -1011,7 +896,6 @@ line-height: 1.35 !important; } -/* ── Description ──────────────────────────────────── */ .codra-toast-description { font-size: 0.74rem !important; font-weight: 400 !important; @@ -1020,13 +904,11 @@ line-height: 1.5 !important; } -/* ── Icon wrapper ─────────────────────────────────── */ .codra-toast-icon { margin-top: 0.05rem !important; flex-shrink: 0 !important; } -/* ── Close button ─────────────────────────────────── */ .codra-toast-close { top: 0.55rem !important; right: 0.55rem !important; @@ -1054,16 +936,11 @@ background: oklch(28% 0.022 115) !important; } -/* ── SUCCESS / ERROR / LOADING ───────────────────── - Use the default toast text color instead of a - status tint (icon color already conveys status). */ - -/* spinner inherits accent color */ +/* Status color comes from the icon; text stays the default toast color. */ .codra-toast-loader svg { color: var(--primary) !important; } -/* ── WARNING ─────────────────────────────────────── */ .codra-toast-warning { color: oklch(35% 0.12 65) !important; } @@ -1072,7 +949,6 @@ color: oklch(82% 0.14 65) !important; } -/* ── INFO ────────────────────────────────────────── */ .codra-toast-info { color: oklch(30% 0.12 250) !important; } diff --git a/src/client/components/features/job-detail/job-chips.tsx b/src/client/components/features/job-detail/job-chips.tsx index f50f9155..6dcf87ba 100644 --- a/src/client/components/features/job-detail/job-chips.tsx +++ b/src/client/components/features/job-detail/job-chips.tsx @@ -1,7 +1,4 @@ -/** - * Row vocabulary shared by the job detail page, mirroring the jobs table so the two read as one - * product: status dot + word + duration, bordered verdict pills, muted icon chips. - */ +// Row vocabulary shared with the job detail page, mirroring the jobs table. import { useState, type ReactNode } from 'react'; import { CheckCircle2, MessageSquare, type LucideIcon } from 'lucide-react'; import { cn } from '@codraoss/ui/utils'; @@ -10,7 +7,6 @@ import { STATUS_DOT, jobDuration, statusLabel } from '@client/lib/job-format'; import type { JobDetail, JobSummary } from '@codraoss/schema'; -/** Status dot alone, for rows that render their own label. */ export function StatusDot({ status, className }: { status: string; className?: string }) { return ( ; } -/** Verdict pill: the border stays neutral so only the leading icon carries colour. */ +// Border stays neutral; only the icon carries colour. export function VerdictPill({ verdict }: { verdict: NonNullable }) { const approved = verdict === 'approve'; const Icon = approved ? CheckCircle2 : MessageSquare; @@ -70,7 +65,6 @@ export function VerdictPill({ verdict }: { verdict: NonNullable.png - * redirect hop can fail, and falls back to an initial. No `loading="lazy"`: intersection - * detection is unreliable inside the app's scroll containers. - */ +// Hits avatars.githubusercontent.com directly: the github.com/.png redirect can fail. +// No loading="lazy": intersection detection is unreliable in this app's scroll containers. export function AuthorAvatar({ login, size = 20 }: { login: string | null; size?: number }) { const [failed, setFailed] = useState(false); const box = { width: size, height: size }; @@ -166,7 +157,6 @@ export function EmptyValue() { return -; } -/** File path in the table's mono style: the directory recedes, the basename carries the weight. */ export function MonoPath({ path, className }: { path: string; className?: string }) { const slash = path.lastIndexOf('/'); const dir = slash === -1 ? '' : path.slice(0, slash + 1); diff --git a/src/client/components/layout/app-shell.tsx b/src/client/components/layout/app-shell.tsx index 9b9c6568..73b52e32 100644 --- a/src/client/components/layout/app-shell.tsx +++ b/src/client/components/layout/app-shell.tsx @@ -33,7 +33,7 @@ export function AppShell() { return () => { cancelled = true; }; }, []); - // Scroll events don't bubble, so listen in the capture phase at the document level and flag whatever scrolled with `data-scrolling` (global CSS keys off it), clearing it ~700ms after scrolling stops. + // Scroll doesn't bubble: listen in capture phase, flag scrolled el with data-scrolling for CSS, clear after 700ms idle. useEffect(() => { const timers = new WeakMap(); const onScroll = (e: Event) => { @@ -54,9 +54,7 @@ export function AppShell() {
{mobileMenuOpen && ( - /* A button so the tap target is a real control, but hidden from the keyboard and the - a11y tree: the drawer already has a focusable "Close menu" X, and a full-viewport - scrim as a tab stop would be an invisible focus target announced twice. */ + /* Hidden from a11y tree: drawer's X is the real focusable close; scrim as a tab stop would double-announce. */