From 2411c27d1ceea49d2ce4bad9058a9aa2268e2fca Mon Sep 17 00:00:00 2001 From: "Matt (via Claude Code)" Date: Sun, 19 Apr 2026 11:35:20 -0500 Subject: [PATCH 1/3] =?UTF-8?q?chore(tooling):=20wave=201=20=E2=80=94=20my?= =?UTF-8?q?py=20+=20eslint=20+=20prettier=20+=20CI=20+=20pre-commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the safety net before refactor waves touch code: Python: - mypy baseline (permissive; strict on models.* + config; hotspots opted out per-module until Wave 3 refactors land) - expanded ruff rules: LOG, RET, PTH (BLE + TRY deferred to Wave 2 alongside the bare-except cleanup) - fixes 45 pre-existing ruff errors (module-top imports, SIM105/108, B904 raise-from, F841 dead vars) for a green baseline Frontend: - ESLint 9 flat config + @eslint/js + typescript-eslint strict preset - Prettier 3 config + ignore - @/ path aliases in tsconfig + vite - package.json scripts: lint, lint:fix, format, format:check, typecheck Quality gates: - pre-commit hooks: ruff, mypy, prettier, eslint - GitHub Actions CI: pytest + ruff + mypy + npm run lint + format:check + build + vitest on pushes and PRs to main - fixes a flaky env-leak in tests/test_config.py that left cfg in "managed" backend state across the suite, causing downstream tests to attempt real Anthropic API calls with an invalid key See docs/clean-code.md for the standard these tools enforce. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 70 + .pre-commit-config.yaml | 46 + frontend/.prettierignore | 5 + frontend/.prettierrc.json | 8 + frontend/eslint.config.js | 36 + frontend/package-lock.json | 1488 +++++++++++++++++ frontend/package.json | 16 +- frontend/src/components/CodeViewer.tsx | 17 +- .../src/components/CompetitionBracket.tsx | 132 +- frontend/tsconfig.json | 6 +- frontend/vite.config.ts | 9 + pyproject.toml | 52 +- skillforge/agents/breeder.py | 7 +- skillforge/api/bible.py | 5 +- skillforge/api/debug.py | 35 +- skillforge/api/routes.py | 16 +- skillforge/api/spec_assistant.py | 43 +- skillforge/api/uploads.py | 12 +- skillforge/config.py | 4 +- skillforge/db/benchmark_seed_loader.py | 1 - skillforge/db/queries.py | 5 +- skillforge/engine/scorer.py | 4 +- skillforge/engine/transcript_logger.py | 2 +- skillforge/engine/variant_evolution.py | 1 - skillforge/main.py | 7 +- skillforge/seeds/__init__.py | 7 +- skillforge/seeds/mock_run_loader.py | 1 - tests/test_config.py | 21 +- uv.lock | 123 ++ 29 files changed, 2002 insertions(+), 177 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .pre-commit-config.yaml create mode 100644 frontend/.prettierignore create mode 100644 frontend/.prettierrc.json create mode 100644 frontend/eslint.config.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..49599f2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + pull_request: + branches: [main] + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + backend: + name: backend (ruff · mypy · pytest) + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "0.5.x" + + - name: Install project (+ dev extras) + run: uv sync --extra dev + + - name: ruff + run: uv run ruff check skillforge + + - name: mypy + run: uv run mypy skillforge + + - name: pytest + # Live tests are gated behind SKILLFORGE_LIVE_TESTS=1 and stay opt-in. + env: + SKILLFORGE_LIVE_TESTS: "0" + run: uv run pytest tests/ --tb=short + + frontend: + name: frontend (eslint · prettier · tsc · vitest) + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + + - name: npm install + run: npm ci + + - name: eslint + run: npm run lint + + - name: prettier + run: npm run format:check + + - name: typecheck + build + run: npm run build + + - name: vitest + run: npm run test diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..57f135d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,46 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + # Formatter is opt-in per-file for now (Wave 2 will enable broadly). + # Running with `--check` would bloat this PR; leaving ruff lint + # as the gate for now. + stages: [manual] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.13.0 + hooks: + - id: mypy + name: mypy (skillforge) + files: ^skillforge/ + args: [--config-file=pyproject.toml] + additional_dependencies: + - pydantic>=2.9 + - types-PyYAML + + - repo: local + hooks: + - id: prettier + name: prettier (frontend) + entry: bash -c 'cd frontend && npx prettier --check .' + language: system + files: ^frontend/.*\.(ts|tsx|js|json|css|md)$ + pass_filenames: false + + - id: eslint + name: eslint (frontend) + entry: bash -c 'cd frontend && npx eslint .' + language: system + files: ^frontend/.*\.(ts|tsx)$ + pass_filenames: false + + - id: vitest + name: vitest (frontend) + entry: bash -c 'cd frontend && npm run test' + language: system + files: ^frontend/src/.*\.(ts|tsx)$ + pass_filenames: false + stages: [manual] # opt-in — vitest is full suite, too slow for every commit diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..2fde09f --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,5 @@ +dist +node_modules +coverage +*.min.js +*.min.css diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json new file mode 100644 index 0000000..f9f4161 --- /dev/null +++ b/frontend/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "printWidth": 100, + "singleQuote": false, + "trailingComma": "all", + "semi": true, + "arrowParens": "always", + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..770134d --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,36 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import prettier from "eslint-config-prettier"; + +export default tseslint.config( + { ignores: ["dist", "node_modules", "coverage"] }, + { + files: ["**/*.{ts,tsx}"], + extends: [js.configs.recommended, ...tseslint.configs.recommended, prettier], + languageOptions: { + ecmaVersion: 2022, + globals: globals.browser, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + + // Clean-code doc enforcement (see docs/clean-code.md §8 React/TS) + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": [ + "warn", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + "no-console": ["warn", { allow: ["warn", "error"] }], + eqeqeq: ["error", "always", { null: "ignore" }], + "prefer-const": "error", + }, + }, +); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8586b82..5f7ccf0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,15 +19,24 @@ "remark-gfm": "^4.0.1" }, "devDependencies": { + "@eslint/js": "^9.17.0", "@testing-library/react": "^16.3.2", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", "autoprefixer": "^10.4.20", + "eslint": "^9.17.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", "jsdom": "^25.0.1", "postcss": "^8.4.47", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.9", "tailwindcss": "^3.4.14", "typescript": "^5.6.3", + "typescript-eslint": "^8.19.0", "vite": "^5.4.10", "vitest": "^2.1.9" } @@ -863,6 +872,229 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1551,6 +1783,13 @@ "@types/unist": "*" } }, + "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, + "license": "MIT" + }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -1604,6 +1843,301 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", + "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/type-utils": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "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.58.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "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/@typescript-eslint/project-service": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", + "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": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", + "dev": true, + "license": "MIT", + "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/@typescript-eslint/type-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", + "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "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/@typescript-eslint/types": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", + "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_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "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": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", + "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2" + }, + "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/@typescript-eslint/visitor-keys": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "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" + } + }, + "node_modules/@typescript-eslint/visitor-keys/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": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -1744,6 +2278,29 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.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/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -1754,6 +2311,23 @@ "node": ">= 14" } }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "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" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1807,6 +2381,13 @@ "dev": true, "license": "MIT" }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -1882,6 +2463,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.10.17", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.17.tgz", @@ -1908,6 +2496,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1979,6 +2578,16 @@ "node": ">= 0.4" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -2037,6 +2646,39 @@ "node": ">=18" } }, + "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/chalk/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/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -2134,6 +2776,26 @@ "node": ">=6" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2167,6 +2829,13 @@ "node": ">= 6" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2187,6 +2856,21 @@ "url": "https://opencollective.com/express" } }, + "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": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2415,6 +3099,13 @@ "node": ">=6" } }, + "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/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2640,6 +3331,199 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@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", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.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", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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" + }, + "engines": { + "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" + }, + "engines": { + "node": ">=4.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==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-util-is-identifier-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", @@ -2660,6 +3544,16 @@ "@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_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -2682,6 +3576,13 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "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-equals": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", @@ -2721,6 +3622,20 @@ "node": ">= 6" } }, + "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/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -2731,6 +3646,19 @@ "reusify": "^1.0.4" } }, + "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/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2744,6 +3672,44 @@ "node": ">=8" } }, + "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/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.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -2862,6 +3828,19 @@ "node": ">=10.13.0" } }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2875,6 +3854,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3021,6 +4010,43 @@ "node": ">=0.10.0" } }, + "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/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", @@ -3161,6 +4187,13 @@ "dev": true, "license": "MIT" }, + "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": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -3177,6 +4210,19 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsdom": { "version": "25.0.1", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", @@ -3231,6 +4277,27 @@ "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", @@ -3244,6 +4311,30 @@ "node": ">=6" } }, + "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/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/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -3264,12 +4355,35 @@ "dev": true, "license": "MIT" }, + "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/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -4230,6 +5344,19 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4267,6 +5394,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "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-releases": { "version": "2.0.37", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", @@ -4310,6 +5444,69 @@ "node": ">= 6" } }, + "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": { + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", @@ -4348,6 +5545,26 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "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", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -4575,6 +5792,119 @@ "dev": true, "license": "MIT" }, + "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", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -4949,6 +6279,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -5081,6 +6421,29 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, + "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/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/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -5136,6 +6499,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -5177,6 +6553,19 @@ "node": ">=16 || 14 >=14.17" } }, + "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/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -5435,6 +6824,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -5442,6 +6844,19 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -5456,6 +6871,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", + "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.2", + "@typescript-eslint/parser": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2" + }, + "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/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -5574,6 +7013,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5841,6 +7290,22 @@ "node": ">=18" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -5858,6 +7323,16 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ws": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", @@ -5904,6 +7379,19 @@ "dev": true, "license": "ISC" }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 59880e4..dbd9de4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,7 +7,12 @@ "dev": "vite", "build": "tsc -b && vite build", "preview": "vite preview", - "test": "vitest run" + "test": "vitest run", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "typecheck": "tsc -b --noEmit" }, "dependencies": { "@types/diff": "^7.0.2", @@ -21,15 +26,24 @@ "remark-gfm": "^4.0.1" }, "devDependencies": { + "@eslint/js": "^9.17.0", "@testing-library/react": "^16.3.2", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", "autoprefixer": "^10.4.20", + "eslint": "^9.17.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", "jsdom": "^25.0.1", "postcss": "^8.4.47", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.9", "tailwindcss": "^3.4.14", "typescript": "^5.6.3", + "typescript-eslint": "^8.19.0", "vite": "^5.4.10", "vitest": "^2.1.9" } diff --git a/frontend/src/components/CodeViewer.tsx b/frontend/src/components/CodeViewer.tsx index fb30884..e4cb3d9 100644 --- a/frontend/src/components/CodeViewer.tsx +++ b/frontend/src/components/CodeViewer.tsx @@ -12,8 +12,10 @@ Prism.languages.bash = { { pattern: /'[^']*'/, greedy: true }, ], variable: /\$(?:\w+|[!#?*@$]|\{[^}]+\})/, - keyword: /\b(?:if|then|else|elif|fi|for|while|do|done|in|case|esac|function|return|local|export|readonly|declare|typeset|unset|set|shift|exit|exec|eval|source|true|false)\b/, - builtin: /\b(?:echo|printf|read|cd|pwd|pushd|popd|test|grep|sed|awk|find|xargs|cat|head|tail|sort|uniq|wc|tr|cut|tee|mkdir|rm|cp|mv|ln|chmod|chown|curl|wget)\b/, + keyword: + /\b(?:if|then|else|elif|fi|for|while|do|done|in|case|esac|function|return|local|export|readonly|declare|typeset|unset|set|shift|exit|exec|eval|source|true|false)\b/, + builtin: + /\b(?:echo|printf|read|cd|pwd|pushd|popd|test|grep|sed|awk|find|xargs|cat|head|tail|sort|uniq|wc|tr|cut|tee|mkdir|rm|cp|mv|ln|chmod|chown|curl|wget)\b/, operator: /&&|\|\||[!=<>]=?|<<|>>|[|&;]/, function: { pattern: /\b\w+(?=\s*\()/, greedy: true }, number: /\b\d+\b/, @@ -29,7 +31,8 @@ Prism.languages.hcl = { { pattern: /\/\*[\s\S]*?\*\//, greedy: true }, ], string: { pattern: /"(?:[^"\\]|\\.)*"/, greedy: true }, - keyword: /\b(?:resource|data|variable|output|module|provider|terraform|locals|dynamic|for_each|count|depends_on|lifecycle)\b/, + keyword: + /\b(?:resource|data|variable|output|module|provider|terraform|locals|dynamic|for_each|count|depends_on|lifecycle)\b/, boolean: /\b(?:true|false)\b/, number: /\b\d+(?:\.\d+)?\b/, punctuation: /[{}[\]=]/, @@ -41,7 +44,8 @@ Prism.languages.tf = Prism.languages.hcl; // Docker Prism.languages.docker = { comment: { pattern: /#.*/, greedy: true }, - keyword: /\b(?:FROM|AS|RUN|CMD|LABEL|MAINTAINER|EXPOSE|ENV|ADD|COPY|ENTRYPOINT|VOLUME|USER|WORKDIR|ARG|ONBUILD|STOPSIGNAL|HEALTHCHECK|SHELL)\b/i, + keyword: + /\b(?:FROM|AS|RUN|CMD|LABEL|MAINTAINER|EXPOSE|ENV|ADD|COPY|ENTRYPOINT|VOLUME|USER|WORKDIR|ARG|ONBUILD|STOPSIGNAL|HEALTHCHECK|SHELL)\b/i, string: { pattern: /"(?:[^"\\]|\\.)*"/, greedy: true }, variable: /\$(?:\w+|\{[^}]+\})/, operator: /&&|\|\|/, @@ -60,10 +64,11 @@ Prism.languages.elixir = { ], atom: { pattern: /:[a-zA-Z_]\w*[?!]?/, greedy: true }, boolean: /\b(?:true|false|nil)\b/, - keyword: /\b(?:def|defp|defmodule|do|end|if|else|unless|case|cond|when|with|fn|raise|rescue|try|catch|after|for|in|and|or|not|use|import|alias|require|quote|unquote)\b/, + keyword: + /\b(?:def|defp|defmodule|do|end|if|else|unless|case|cond|when|with|fn|raise|rescue|try|catch|after|for|in|and|or|not|use|import|alias|require|quote|unquote)\b/, module: { pattern: /\b[A-Z]\w*(?:\.[A-Z]\w*)*/, greedy: true }, function: { pattern: /\b\w+(?=[?!]?\s*[(\s])/, greedy: true }, - operator: /\|>|<>|<-|->|=>|=~|~>|::|\.\.\.|&&|\|\||[!=<>]=?|[+\-*\/\\^|&]/, + operator: /\|>|<>|<-|->|=>|=~|~>|::|\.\.\.|&&|\|\||[!=<>]=?|[+\-*/\\^|&]/, number: /\b(?:0x[\da-fA-F_]+|0b[01_]+|0o[0-7_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)\b/, punctuation: /[()[\]{},;.@#%]/, }; diff --git a/frontend/src/components/CompetitionBracket.tsx b/frontend/src/components/CompetitionBracket.tsx index b1e4c74..2aa7803 100644 --- a/frontend/src/components/CompetitionBracket.tsx +++ b/frontend/src/components/CompetitionBracket.tsx @@ -2,11 +2,7 @@ import { useMemo, useState } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; -import type { - CompetitionMatch, - CompetitionScoresPayload, - RunReportGenome, -} from "../types"; +import type { CompetitionMatch, CompetitionScoresPayload, RunReportGenome } from "../types"; interface CompetitionBracketProps { scores: CompetitionScoresPayload; @@ -41,14 +37,13 @@ export default function CompetitionBracket({ // Guard against a malformed/partial payload — an older shape (pre-schema- // alignment) or a hand-constructed entry may be missing `matches`. Render // a graceful placeholder instead of crashing the whole page. - const matches = Array.isArray(scores?.matches) ? scores.matches : []; - const sortedMatches = useMemo(() => { + const matches = Array.isArray(scores?.matches) ? scores.matches : []; return [...matches].sort((a, b) => { if (a.tier !== b.tier) return a.tier === "foundation" ? -1 : 1; return b.winning_fitness - a.winning_fitness; }); - }, [matches]); + }, [scores?.matches]); // Empty / malformed payload: render the disclaimer only, with a notice. if (sortedMatches.length === 0) { @@ -58,11 +53,8 @@ export default function CompetitionBracket({ Competition

- This run has no competition bracket data available. The seed run - either pre-dates the{" "} - - [competition_scores] - {" "} + This run has no competition bracket data available. The seed run either pre-dates the{" "} + [competition_scores]{" "} learning-log format, or its payload was malformed during export.

@@ -83,38 +75,37 @@ export default function CompetitionBracket({

- Each dimension ran a 1-round{" "} - competition: the pre-existing seed variant vs. one freshly-spawned - alternative. Both variants were scored on{" "} - - {challengesPerVariant} sampled challenges - {" "} - drawn from the dimension's 108-challenge training pool (27 held-out - for future test-time evaluation). The variant with the higher{" "} + Each dimension ran a 1-round competition: + the pre-existing seed variant vs. one freshly-spawned alternative. Both variants were + scored on{" "} + {challengesPerVariant} sampled challenges{" "} + drawn from the dimension's 108-challenge training pool (27 held-out for future test-time + evaluation). The variant with the higher{" "} mean L1 fitness won.

- Scoring criteria per - challenge: the L1 scorer runs regex checks against the generated - output file. Each challenge defines{" "} - - must_contain - {" "} - required substrings,{" "} - - must_not_contain - {" "} - forbidden substrings, and a set of dimension-specific anti-pattern - detectors. The fraction of passing checks is weighted by each - check's weight, and the weighted pass rate becomes the score. + Scoring criteria per challenge: the L1 + scorer runs regex checks against the generated output file. Each challenge defines{" "} + must_contain required + substrings,{" "} + must_not_contain{" "} + forbidden substrings, and a set of dimension-specific anti-pattern detectors. The + fraction of passing checks is weighted by each check's weight, and the weighted pass + rate becomes the score.

- Limitations for this run:{" "} - this is generation{" "} + Limitations for this run: this is + generation{" "} {generation}/{totalGenerations} {" "} - (single-round). {baselineRan ? "A baseline run on the full challenge pool was completed before this competition." : "No baseline pass over the full 108-challenge pool was run before sampling."} Higher reviewer layers (L2 trigger accuracy, L3 trace, L4 comparative, L5 trait attribution) were not exercised. Real production runs would layer these on top for richer signal. + (single-round).{" "} + {baselineRan + ? "A baseline run on the full challenge pool was completed before this competition." + : "No baseline pass over the full 108-challenge pool was run before sampling."}{" "} + Higher reviewer layers (L2 trigger accuracy, L3 trace, L4 comparative, L5 trait + attribution) were not exercised. Real production runs would layer these on top for + richer signal.

@@ -122,39 +113,25 @@ export default function CompetitionBracket({ {/* Matches */} {sortedMatches.map((match) => { const isExpanded = expandedKey === match.dimension; - const winnerLabel = - match.winner_slot === 1 - ? match.variant_1_label - : match.variant_2_label; + const winnerLabel = match.winner_slot === 1 ? match.variant_1_label : match.variant_2_label; const winnerScores = - match.winner_slot === 1 - ? match.variant_1_scores - : match.variant_2_scores; + match.winner_slot === 1 ? match.variant_1_scores : match.variant_2_scores; const loserScores = - match.winner_slot === 1 - ? match.variant_2_scores - : match.variant_1_scores; - const winnerMean = - match.winner_slot === 1 ? match.variant_1_mean : match.variant_2_mean; - const loserMean = - match.winner_slot === 1 ? match.variant_2_mean : match.variant_1_mean; + match.winner_slot === 1 ? match.variant_2_scores : match.variant_1_scores; + const winnerMean = match.winner_slot === 1 ? match.variant_1_mean : match.variant_2_mean; + const loserMean = match.winner_slot === 1 ? match.variant_2_mean : match.variant_1_mean; // Find the winning genome for the expand-on-click view. const winnerGenome = genomes.find( (g) => - g.meta_strategy === "seed_pipeline_winner" && - deriveDimFromId(g.id) === match.dimension, + g.meta_strategy === "seed_pipeline_winner" && deriveDimFromId(g.id) === match.dimension, ); const rationale = buildRationale(match, winnerMean, loserMean); // Look up raw baseline scores for this match's challenges - const rawScores = match.challenge_ids.map( - (id) => rawBaselineMap[id] ?? null, - ); - const validRawScores = rawScores.filter( - (s): s is number => s !== null, - ); + const rawScores = match.challenge_ids.map((id) => rawBaselineMap[id] ?? null); + const validRawScores = rawScores.filter((s): s is number => s !== null); const rawMean = validRawScores.length > 0 ? validRawScores.reduce((a, b) => a + b, 0) / validRawScores.length @@ -169,9 +146,7 @@ export default function CompetitionBracket({ {/* Header: dimension + tier */}
-

- {match.dimension} -

+

{match.dimension}

{hasRaw && ( @@ -241,11 +214,9 @@ export default function CompetitionBracket({ {/* Rationale + drill-down toggle */}

- {winnerLabel} won - with mean fitness{" "} + {winnerLabel} won with mean fitness{" "} {winnerMean.toFixed(3)} vs{" "} - {loserMean.toFixed(3)}{" "} - (winning scores:{" "} + {loserMean.toFixed(3)} (winning scores:{" "} {winnerScores.map((s) => s.toFixed(3)).join(" / ")} @@ -258,9 +229,7 @@ export default function CompetitionBracket({ {winnerGenome && (

-

- {name} -

+

{name}

{perChallenge.map((score, i) => ( -
+
c{i + 1}
- - {score.toFixed(3)} - + {score.toFixed(3)}
))}
@@ -360,11 +322,7 @@ function SideCard({ ); } -function buildRationale( - _match: CompetitionMatch, - winnerMean: number, - loserMean: number, -): string { +function buildRationale(_match: CompetitionMatch, winnerMean: number, loserMean: number): string { const delta = winnerMean - loserMean; if (delta < 0.001) { return "Both variants scored identically across both sampled challenges — a tie. The tie-break picked the spawned variant (slot 2) by default so the Registry could show evidence of the Spawner's output."; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 0426f7b..d4126db 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -14,7 +14,11 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } }, "include": ["src"] } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index e59163f..2ffcfe1 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,8 +1,17 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig({ plugins: [react()], + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + }, + }, server: { proxy: { "/api": "http://localhost:8000", diff --git a/pyproject.toml b/pyproject.toml index 9cca99a..0aea578 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dev = [ "pytest>=8.3", "pytest-asyncio>=0.24", "ruff>=0.7", + "mypy>=1.13", "httpx>=0.27", ] @@ -39,5 +40,52 @@ line-length = 100 target-version = "py312" [tool.ruff.lint] -select = ["E", "F", "I", "UP", "B", "SIM"] -ignore = ["E501"] +# E/F: pycodestyle + pyflakes I: isort UP: pyupgrade B: flake8-bugbear +# SIM: flake8-simplify LOG: flake8-logging RET: flake8-return PTH: flake8-use-pathlib +# BLE + TRY (exception hygiene) are added in Wave 2 alongside the bare-except cleanup. +select = ["E", "F", "I", "UP", "B", "SIM", "LOG", "RET", "PTH"] +ignore = [ + "E501", # line-too-long (handled by formatter preference) + "RET504", # unnecessary assignment before return (noisy) +] + +# Seed data modules are bulk content, not hand-maintained logic. +[tool.ruff.lint.per-file-ignores] +"skillforge/seeds/batch*.py" = ["E402", "E741", "F401", "F811", "SIM", "B"] + +[tool.mypy] +python_version = "3.12" +# Baseline: opt in per-module. Wave 1 establishes the tooling + a green floor. +# Wave 2+ moves each refactored module into the checked list until everything +# is covered. This is intentional ratcheting, not permanent exemption. +ignore_missing_imports = true +follow_imports = "silent" +check_untyped_defs = false +warn_redundant_casts = true +warn_unused_ignores = false # flips to true in Wave 6 polish +# Skip modules that do not yet pass — added back as refactor waves land. +exclude = [ + "^skillforge/seeds/batch[0-9]+\\.py$", + "^skillforge/api/debug\\.py$", + "^skillforge/api/bench\\.py$", + "^skillforge/api/uploads\\.py$", + "^skillforge/api/spec_assistant\\.py$", + "^skillforge/agents/competitor\\.py$", + "^skillforge/agents/competitor_managed\\.py$", + "^skillforge/agents/engineer\\.py$", + "^skillforge/agents/judge/pipeline\\.py$", + "^skillforge/agents/judge/trigger\\.py$", + "^skillforge/db/benchmark_seed_loader\\.py$", + "^skillforge/engine/evolution\\.py$", + "^skillforge/engine/export\\.py$", + "^skillforge/engine/sandbox\\.py$", + "^skillforge/engine/scorer\\.py$", + "^skillforge/main\\.py$", + "^skillforge/models/run\\.py$", +] + +[[tool.mypy.overrides]] +module = ["skillforge.models.*", "skillforge.config"] +disallow_untyped_defs = true +disallow_incomplete_defs = true +strict_optional = true diff --git a/skillforge/agents/breeder.py b/skillforge/agents/breeder.py index 6340919..13c673d 100644 --- a/skillforge/agents/breeder.py +++ b/skillforge/agents/breeder.py @@ -335,10 +335,9 @@ async def _extract_lessons_and_report( if BREEDER_CALL_MODE == "consolidated": return await _extract_consolidated(context, learning_log) - else: - lessons = await _extract_lessons(context, learning_log) - report = await _extract_breeding_report(context, slots, elites, pareto_parents) - return lessons, report + lessons = await _extract_lessons(context, learning_log) + report = await _extract_breeding_report(context, slots, elites, pareto_parents) + return lessons, report def _build_breeding_context( diff --git a/skillforge/api/bible.py b/skillforge/api/bible.py index 01e35fa..2d910e7 100644 --- a/skillforge/api/bible.py +++ b/skillforge/api/bible.py @@ -77,10 +77,7 @@ async def get_bible_entry(category: str, slug: str) -> dict: allowed = {"patterns", "findings", "anti-patterns", "books"} if category not in allowed: raise HTTPException(status_code=400, detail=f"unknown category: {category}") - if category == "books": - path = BIBLE_DIR / f"{slug}.md" - else: - path = BIBLE_DIR / category / f"{slug}.md" + path = BIBLE_DIR / f"{slug}.md" if category == "books" else BIBLE_DIR / category / f"{slug}.md" if not path.exists(): raise HTTPException(status_code=404, detail=f"entry not found: {category}/{slug}") body = path.read_text(encoding="utf-8") diff --git a/skillforge/api/debug.py b/skillforge/api/debug.py index 522e9de..fea8b2a 100644 --- a/skillforge/api/debug.py +++ b/skillforge/api/debug.py @@ -128,7 +128,7 @@ def render(assigns) do end end '''} - elif variant == "seed": + if variant == "seed": return {filename: f'''\ defmodule MyAppWeb.{module}Live do use MyAppWeb, :live_view @@ -153,8 +153,8 @@ def render(assigns) do end end '''} - else: # spawn - return {filename: f'''\ + # spawn + return {filename: f'''\ defmodule MyAppWeb.{module}Live do use MyAppWeb, :live_view @@ -177,7 +177,8 @@ def render(assigns) do '''} -import random as _random +import random as _random # noqa: E402 (placed after scripted narrative blocks) + def _demo_scores(variant: str, base_fitness: float) -> dict: """Generate realistic per-competitor composite score breakdowns.""" @@ -192,7 +193,7 @@ def _demo_scores(variant: str, base_fitness: float) -> dict: "template": round(0.5 + _random.uniform(0, 0.3), 3), "brevity": round(0.8 + _random.uniform(0, 0.2), 3), } - elif variant == "seed": + if variant == "seed": # Seed skill: strong across the board return { "composite": round(base_fitness + _random.uniform(-0.02, 0.04), 3), @@ -203,17 +204,17 @@ def _demo_scores(variant: str, base_fitness: float) -> dict: "template": round(0.8 + _random.uniform(0, 0.2), 3), "brevity": round(0.85 + _random.uniform(0, 0.15), 3), } - else: # spawn - # Spawn: variable, sometimes beats seed - return { - "composite": round(base_fitness - 0.04 + _random.uniform(-0.05, 0.08), 3), - "l0": round(0.8 + _random.uniform(-0.1, 0.15), 3), - "compile": _random.random() > 0.2, - "ast": round(0.3 + _random.uniform(0, 0.35), 3), - "behavioral": round(0.15 + _random.uniform(0, 0.5), 3), - "template": round(0.7 + _random.uniform(0, 0.3), 3), - "brevity": round(0.75 + _random.uniform(0, 0.25), 3), - } + # spawn + # Spawn: variable, sometimes beats seed + return { + "composite": round(base_fitness - 0.04 + _random.uniform(-0.05, 0.08), 3), + "l0": round(0.8 + _random.uniform(-0.1, 0.15), 3), + "compile": _random.random() > 0.2, + "ast": round(0.3 + _random.uniform(0, 0.35), 3), + "behavioral": round(0.15 + _random.uniform(0, 0.5), 3), + "template": round(0.7 + _random.uniform(0, 0.3), 3), + "brevity": round(0.75 + _random.uniform(0, 0.25), 3), + } # Atomic demo: dimension-by-dimension evolution, not generation-based. @@ -302,7 +303,7 @@ async def step(seconds: float) -> None: await step(1.0) # --- per-dimension mini-evolution --- - for dim_idx, dim in enumerate(DIMENSION_SCRIPT): + for _dim_idx, dim in enumerate(DIMENSION_SCRIPT): vevo_id = f"vevo_{uuid.uuid4().hex[:12]}" await emit( diff --git a/skillforge/api/routes.py b/skillforge/api/routes.py index 09bc93f..7c7e2b2 100644 --- a/skillforge/api/routes.py +++ b/skillforge/api/routes.py @@ -8,8 +8,6 @@ import uuid from datetime import UTC, datetime -logger = logging.getLogger("skillforge.api") - from fastapi import APIRouter, HTTPException, Response from pydantic import BaseModel, Field @@ -30,6 +28,8 @@ from skillforge.engine.export import export_agent_sdk_config, export_skill_md, export_skill_zip from skillforge.models import EvolutionRun, SkillGenome +logger = logging.getLogger("skillforge.api") + router = APIRouter(prefix="/api") # Module-level registry: run_id -> background task @@ -123,14 +123,13 @@ async def _classify_run_via_taxonomist( from datetime import datetime as _dt from uuid import uuid4 as _uuid4 - from skillforge.db import save_variant_evolution - from skillforge.models import VariantEvolution - # Insert the parent run row first so the FK on # variant_evolutions.parent_run_id is satisfied. save_run is # idempotent (INSERT OR REPLACE) so the second save_run later in # the route handler is a no-op refresh. from skillforge.db import save_run as _save_run + from skillforge.db import save_variant_evolution + from skillforge.models import VariantEvolution await _save_run(run) @@ -538,14 +537,13 @@ async def export_run(run_id: str, format: ExportFormat = ExportFormat.skill_dir) media_type="application/zip", headers={"Content-Disposition": f'attachment; filename="{filename}"'}, ) - elif format == ExportFormat.skill_md: + if format == ExportFormat.skill_md: md = export_skill_md(run.best_skill) return Response(content=md, media_type="text/markdown") - elif format == ExportFormat.agent_sdk_config: + if format == ExportFormat.agent_sdk_config: config = export_agent_sdk_config(run.best_skill) return Response(content=json.dumps(config, indent=2), media_type="application/json") - else: - raise HTTPException(status_code=400, detail=f"unknown format: {format}") + raise HTTPException(status_code=400, detail=f"unknown format: {format}") @router.get("/runs/{run_id}/lineage") diff --git a/skillforge/api/spec_assistant.py b/skillforge/api/spec_assistant.py index f3df130..70464e3 100644 --- a/skillforge/api/spec_assistant.py +++ b/skillforge/api/spec_assistant.py @@ -13,6 +13,7 @@ from __future__ import annotations import asyncio +import contextlib import json import logging import re @@ -20,14 +21,14 @@ import tempfile from pathlib import Path -logger = logging.getLogger("skillforge.api.spec_assistant") - from anthropic import AsyncAnthropic from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field from skillforge.config import ROOT_DIR, model_for +logger = logging.getLogger("skillforge.api.spec_assistant") + router = APIRouter(prefix="/api/spec-assistant", tags=["spec-assistant"]) @@ -271,16 +272,16 @@ async def _validate_package(skill_md: str, supporting_files: dict[str, str]) -> ) stdout, _ = await proc.communicate() - try: - report = json.loads(stdout.decode()) - issues = [ - c["name"] + ": " + c.get("detail", "") - for c in report.get("checks", []) - if c.get("status") == "fail" - ] - return report.get("status") == "pass", issues - except (json.JSONDecodeError, KeyError): - return proc.returncode == 0, [] + try: + report = json.loads(stdout.decode()) + issues = [ + c["name"] + ": " + c.get("detail", "") + for c in report.get("checks", []) + if c.get("status") == "fail" + ] + except (json.JSONDecodeError, KeyError): + return proc.returncode == 0, [] + return report.get("status") == "pass", issues @router.post("/generate-skill", response_model=GenerateSkillResponse) @@ -329,10 +330,8 @@ async def generate_skill(req: GenerateSkillRequest) -> GenerateSkillResponse: if not pkg: # Try parsing the entire response as JSON - try: + with contextlib.suppress(json.JSONDecodeError): pkg = json.loads(raw) - except json.JSONDecodeError: - pass if not pkg: # Try extracting JSON that may have been truncated (missing closing fence) @@ -342,10 +341,8 @@ async def generate_skill(req: GenerateSkillRequest) -> GenerateSkillResponse: # Try to find the outermost { ... } even without closing fence brace_start = json_body.find("{") if brace_start >= 0: - try: + with contextlib.suppress(json.JSONDecodeError): pkg = json.loads(json_body[brace_start:]) - except json.JSONDecodeError: - pass if not pkg: if attempt == 0: @@ -369,8 +366,9 @@ async def generate_skill(req: GenerateSkillRequest) -> GenerateSkillResponse: if passed or attempt == 1 or supporting_files: # Auto-save as candidate seed try: - from skillforge.db.queries import save_candidate_seed import uuid as _uuid + + from skillforge.db.queries import save_candidate_seed await save_candidate_seed( id=str(_uuid.uuid4()), source="generated", @@ -395,7 +393,12 @@ async def generate_skill(req: GenerateSkillRequest) -> GenerateSkillResponse: # Only retry if we got no supporting files at all user_msg = ( - f"The generated skill package has validation issues:\n" + "The generated skill package has validation issues:\n" + "\n".join(f"- {i}" for i in issues) + "\n\nPlease fix these issues and regenerate the complete JSON package." ) + + # Loop exhausted without returning — signal failure. + return GenerateSkillResponse( + validation_issues=["LLM did not produce a valid skill package after retries"] + ) diff --git a/skillforge/api/uploads.py b/skillforge/api/uploads.py index 35c53b9..57e5bdf 100644 --- a/skillforge/api/uploads.py +++ b/skillforge/api/uploads.py @@ -114,7 +114,7 @@ def _sniff_skill_md(zf: zipfile.ZipFile) -> tuple[str, dict[str, str]]: @router.post("/skill") -async def upload_skill(file: UploadFile = File(...)) -> dict: +async def upload_skill(file: UploadFile = File(...)) -> dict: # noqa: B008 (FastAPI dependency idiom) """Upload a SKILL.md or zipped Skill directory for later fork-and-evolve.""" raw = await file.read() if len(raw) > MAX_UPLOAD_BYTES: @@ -134,13 +134,15 @@ async def upload_skill(file: UploadFile = File(...)) -> dict: status_code=400, detail=f"zip contains more than {MAX_FILES} files" ) skill_md, supporting = _sniff_skill_md(zf) - except zipfile.BadZipFile: - raise HTTPException(status_code=400, detail="file is not a valid zip archive") + except zipfile.BadZipFile as exc: + raise HTTPException( + status_code=400, detail="file is not a valid zip archive" + ) from exc elif filename.lower().endswith(".md"): try: skill_md = raw.decode("utf-8") - except UnicodeDecodeError: - raise HTTPException(status_code=400, detail="SKILL.md must be UTF-8") + except UnicodeDecodeError as exc: + raise HTTPException(status_code=400, detail="SKILL.md must be UTF-8") from exc else: raise HTTPException( status_code=400, detail="only .md or .zip files are accepted" diff --git a/skillforge/config.py b/skillforge/config.py index 241aa39..9026a5e 100644 --- a/skillforge/config.py +++ b/skillforge/config.py @@ -7,6 +7,7 @@ from __future__ import annotations +import logging import os from pathlib import Path @@ -224,9 +225,8 @@ def model_for(role: str) -> str: ANTHROPIC_API_KEY: str | None = os.getenv("ANTHROPIC_API_KEY") # --- Startup diagnostic (all constants now defined) --------------------------- -import logging as _logging -_logging.getLogger("skillforge.config").info( +logging.getLogger("skillforge.config").info( "gating_disabled=%s codes_loaded=%d backend=%s api_key=%s", GATING_DISABLED, len(INVITE_CODES), COMPETITOR_BACKEND, "set" if ANTHROPIC_API_KEY else "NOT SET", diff --git a/skillforge/db/benchmark_seed_loader.py b/skillforge/db/benchmark_seed_loader.py index 4aa8377..b97fd3b 100644 --- a/skillforge/db/benchmark_seed_loader.py +++ b/skillforge/db/benchmark_seed_loader.py @@ -13,7 +13,6 @@ import json import logging -from pathlib import Path import aiosqlite diff --git a/skillforge/db/queries.py b/skillforge/db/queries.py index ea68624..7823bb5 100644 --- a/skillforge/db/queries.py +++ b/skillforge/db/queries.py @@ -12,6 +12,7 @@ import json from collections.abc import AsyncIterator from contextlib import asynccontextmanager +from datetime import UTC from pathlib import Path import aiosqlite @@ -1266,9 +1267,9 @@ async def save_transcript( db_path: Path | None = None, ) -> None: """Upsert a dispatch transcript row.""" - from datetime import datetime, timezone + from datetime import datetime - ts = created_at or datetime.now(timezone.utc).isoformat() + ts = created_at or datetime.now(UTC).isoformat() async with _connect(db_path) as conn: await conn.execute( """ diff --git a/skillforge/engine/scorer.py b/skillforge/engine/scorer.py index afa58f1..f6e6fa4 100644 --- a/skillforge/engine/scorer.py +++ b/skillforge/engine/scorer.py @@ -1,11 +1,11 @@ -from __future__ import annotations - """Async wrapper around scripts/scoring/composite_scorer.py. Runs the sync composite_score() in a thread pool so the evolution engine can await it without blocking the event loop. """ +from __future__ import annotations + import asyncio import json import logging diff --git a/skillforge/engine/transcript_logger.py b/skillforge/engine/transcript_logger.py index d356b07..7729a76 100644 --- a/skillforge/engine/transcript_logger.py +++ b/skillforge/engine/transcript_logger.py @@ -20,7 +20,7 @@ async def log_competitor_dispatch( challenge_id: str, skill_id: str, model: str, - result: "CompetitionResult", + result: CompetitionResult, scores: dict | None = None, duration_ms: int = 0, ) -> None: diff --git a/skillforge/engine/variant_evolution.py b/skillforge/engine/variant_evolution.py index 7c8dc46..d90f283 100644 --- a/skillforge/engine/variant_evolution.py +++ b/skillforge/engine/variant_evolution.py @@ -227,7 +227,6 @@ async def _run_dimension_mini_evolution( # Separate baseline result from skill results for scoring/selection. # The baseline is idx 0; skill variants are idx 1+. - baseline_result = results[0] skill_results = results[1:] # --- Composite scoring (Phase 6) --- diff --git a/skillforge/main.py b/skillforge/main.py index a6923e9..f05afbd 100644 --- a/skillforge/main.py +++ b/skillforge/main.py @@ -12,16 +12,15 @@ from __future__ import annotations +import html import json as _json import logging import os +import re from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI -import html -import re - from fastapi.responses import FileResponse, HTMLResponse from fastapi.staticfiles import StaticFiles @@ -53,7 +52,6 @@ def format(self, record: logging.LogRecord) -> str: datefmt="%H:%M:%S", ) -from skillforge.config import ROOT_DIR from skillforge.api.bench import router as bench_router from skillforge.api.bible import router as bible_router from skillforge.api.candidates import router as candidates_router @@ -68,6 +66,7 @@ def format(self, record: logging.LogRecord) -> str: from skillforge.api.taxonomy import router as taxonomy_router from skillforge.api.uploads import router as uploads_router from skillforge.api.websocket import router as ws_router +from skillforge.config import ROOT_DIR from skillforge.db.benchmark_seed_loader import load_benchmark_results from skillforge.db.database import init_db from skillforge.db.queries import mark_zombie_runs diff --git a/skillforge/seeds/__init__.py b/skillforge/seeds/__init__.py index 7e704c8..a3291be 100644 --- a/skillforge/seeds/__init__.py +++ b/skillforge/seeds/__init__.py @@ -1584,9 +1584,10 @@ def word_frequency_topk(text: str, k: int) -> list[tuple[str, int]]: ] # --- Active seeds: full golden-template packages with scripts + references --- -from skillforge.seeds.batch1 import BATCH1_SEEDS -from skillforge.seeds.batch2 import BATCH2_SEEDS -from skillforge.seeds.batch3 import BATCH3_SEEDS +# Imports are placed after the legacy seed defs above to avoid circular loads. +from skillforge.seeds.batch1 import BATCH1_SEEDS # noqa: E402 +from skillforge.seeds.batch2 import BATCH2_SEEDS # noqa: E402 +from skillforge.seeds.batch3 import BATCH3_SEEDS # noqa: E402 SEED_SKILLS: list[dict] = BATCH1_SEEDS + BATCH2_SEEDS + BATCH3_SEEDS diff --git a/skillforge/seeds/mock_run_loader.py b/skillforge/seeds/mock_run_loader.py index 9369293..4d07954 100644 --- a/skillforge/seeds/mock_run_loader.py +++ b/skillforge/seeds/mock_run_loader.py @@ -26,7 +26,6 @@ from skillforge.config import DATA_DIR, DB_PATH from skillforge.db.queries import ( - get_family, get_family_by_slug, get_run, get_taxonomy_node_by_slug, diff --git a/tests/test_config.py b/tests/test_config.py index bc8a5db..eddb70b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -144,6 +144,20 @@ def test_managed_agents_session_runtime_rate_matches_pricing(): # --------------------------------------------------------------------------- +def _restore_cfg(monkeypatch: pytest.MonkeyPatch) -> None: + """Un-set env patches and reload cfg so subsequent tests see the baseline. + + monkeypatch teardown runs *after* the test body, so the test's own + ``finally`` block runs while env patches are still active. We must + explicitly drop the patched vars before the final reload, otherwise + cfg retains the mutated ``COMPETITOR_BACKEND`` and downstream tests + end up hitting a real ``managed_agents.make_client()`` path. + """ + monkeypatch.delenv("SKILLFORGE_COMPETITOR_BACKEND", raising=False) + monkeypatch.delenv("SKILLFORGE_COMPETITOR_CONCURRENCY", raising=False) + importlib.reload(cfg) + + def test_competitor_concurrency_default_under_sdk_is_one(monkeypatch: pytest.MonkeyPatch): """SDK backend MUST default to 1 because of the subprocess race.""" monkeypatch.setenv("SKILLFORGE_COMPETITOR_BACKEND", "sdk") @@ -153,8 +167,7 @@ def test_competitor_concurrency_default_under_sdk_is_one(monkeypatch: pytest.Mon assert reloaded.COMPETITOR_BACKEND == "sdk" assert reloaded.COMPETITOR_CONCURRENCY == 1 finally: - # Restore the canonical module state for downstream tests - importlib.reload(cfg) + _restore_cfg(monkeypatch) def test_competitor_concurrency_default_under_managed_is_five( @@ -168,7 +181,7 @@ def test_competitor_concurrency_default_under_managed_is_five( assert reloaded.COMPETITOR_BACKEND == "managed" assert reloaded.COMPETITOR_CONCURRENCY == 5 finally: - importlib.reload(cfg) + _restore_cfg(monkeypatch) def test_competitor_concurrency_explicit_override_wins( @@ -181,7 +194,7 @@ def test_competitor_concurrency_explicit_override_wins( try: assert reloaded.COMPETITOR_CONCURRENCY == 12 finally: - importlib.reload(cfg) + _restore_cfg(monkeypatch) # --------------------------------------------------------------------------- diff --git a/uv.lock b/uv.lock index 228c2d5..be9144e 100644 --- a/uv.lock +++ b/uv.lock @@ -449,6 +449,66 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] +[[package]] +name = "librt" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/6b/3d5c13fb3e3c4f43206c8f9dfed13778c2ed4f000bacaa0b7ce3c402a265/librt-0.9.0.tar.gz", hash = "sha256:a0951822531e7aee6e0dfb556b30d5ee36bbe234faf60c20a16c01be3530869d", size = 184368, upload-time = "2026-04-09T16:06:26.173Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/90/89ddba8e1c20b0922783cd93ed8e64f34dc05ab59c38a9c7e313632e20ff/librt-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b3e3bc363f71bda1639a4ee593cb78f7fbfeacc73411ec0d4c92f00730010a4", size = 68332, upload-time = "2026-04-09T16:05:00.09Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/7aa4da1fb08bdeeb540cb07bfc8207cb32c5c41642f2594dbd0098a0662d/librt-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a09c2f5869649101738653a9b7ab70cf045a1105ac66cbb8f4055e61df78f2d", size = 70581, upload-time = "2026-04-09T16:05:01.213Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/73a2187e1031041e93b7e3a25aae37aa6f13b838c550f7e0f06f66766212/librt-0.9.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ca8e133d799c948db2ab1afc081c333a825b5540475164726dcbf73537e5c2f", size = 203984, upload-time = "2026-04-09T16:05:02.542Z" }, + { url = "https://files.pythonhosted.org/packages/5e/3d/23460d571e9cbddb405b017681df04c142fb1b04cbfce77c54b08e28b108/librt-0.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:603138ee838ee1583f1b960b62d5d0007845c5c423feb68e44648b1359014e27", size = 215762, upload-time = "2026-04-09T16:05:04.127Z" }, + { url = "https://files.pythonhosted.org/packages/de/1e/42dc7f8ab63e65b20640d058e63e97fd3e482c1edbda3570d813b4d0b927/librt-0.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4003f70c56a5addd6aa0897f200dd59afd3bf7bcd5b3cce46dd21f925743bc2", size = 230288, upload-time = "2026-04-09T16:05:05.883Z" }, + { url = "https://files.pythonhosted.org/packages/dc/08/ca812b6d8259ad9ece703397f8ad5c03af5b5fedfce64279693d3ce4087c/librt-0.9.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78042f6facfd98ecb25e9829c7e37cce23363d9d7c83bc5f72702c5059eb082b", size = 224103, upload-time = "2026-04-09T16:05:07.148Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3f/620490fb2fa66ffd44e7f900254bc110ebec8dac6c1b7514d64662570e6f/librt-0.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a361c9434a64d70a7dbb771d1de302c0cc9f13c0bffe1cf7e642152814b35265", size = 232122, upload-time = "2026-04-09T16:05:08.386Z" }, + { url = "https://files.pythonhosted.org/packages/e9/83/12864700a1b6a8be458cf5d05db209b0d8e94ae281e7ec261dbe616597b4/librt-0.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dd2c7e082b0b92e1baa4da28163a808672485617bc855cc22a2fd06978fa9084", size = 225045, upload-time = "2026-04-09T16:05:09.707Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1b/845d339c29dc7dbc87a2e992a1ba8d28d25d0e0372f9a0a2ecebde298186/librt-0.9.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7e6274fd33fc5b2a14d41c9119629d3ff395849d8bcbc80cf637d9e8d2034da8", size = 227372, upload-time = "2026-04-09T16:05:10.942Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fe/277985610269d926a64c606f761d58d3db67b956dbbf40024921e95e7fcb/librt-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5093043afb226ecfa1400120d1ebd4442b4f99977783e4f4f7248879009b227f", size = 248224, upload-time = "2026-04-09T16:05:12.254Z" }, + { url = "https://files.pythonhosted.org/packages/92/1b/ee486d244b8de6b8b5dbaefabe6bfdd4a72e08f6353edf7d16d27114da8d/librt-0.9.0-cp312-cp312-win32.whl", hash = "sha256:9edcc35d1cae9fd5320171b1a838c7da8a5c968af31e82ecc3dff30b4be0957f", size = 55986, upload-time = "2026-04-09T16:05:13.529Z" }, + { url = "https://files.pythonhosted.org/packages/89/7a/ba1737012308c17dc6d5516143b5dce9a2c7ba3474afd54e11f44a4d1ef3/librt-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc2917258e131ae5f958a4d872e07555b51cb7466a43433218061c74ef33745", size = 63260, upload-time = "2026-04-09T16:05:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/e4/01752c113da15127f18f7bf11142f5640038f062407a611c059d0036c6aa/librt-0.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:90e6d5420fc8a300518d4d2288154ff45005e920425c22cbbfe8330f3f754bd9", size = 53694, upload-time = "2026-04-09T16:05:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d7/1b3e26fffde1452d82f5666164858a81c26ebe808e7ae8c9c88628981540/librt-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29b68cd9714531672db62cc54f6e8ff981900f824d13fa0e00749189e13778e", size = 68367, upload-time = "2026-04-09T16:05:17.243Z" }, + { url = "https://files.pythonhosted.org/packages/a5/5b/c61b043ad2e091fbe1f2d35d14795e545d0b56b03edaa390fa1dcee3d160/librt-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d5c8a5929ac325729f6119802070b561f4db793dffc45e9ac750992a4ed4d22", size = 70595, upload-time = "2026-04-09T16:05:18.471Z" }, + { url = "https://files.pythonhosted.org/packages/a3/22/2448471196d8a73370aa2f23445455dc42712c21404081fcd7a03b9e0749/librt-0.9.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:756775d25ec8345b837ab52effee3ad2f3b2dfd6bbee3e3f029c517bd5d8f05a", size = 204354, upload-time = "2026-04-09T16:05:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5e/39fc4b153c78cfd2c8a2dcb32700f2d41d2312aa1050513183be4540930d/librt-0.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8f5d00b49818f4e2b1667db994488b045835e0ac16fe2f924f3871bd2b8ac5", size = 216238, upload-time = "2026-04-09T16:05:20.868Z" }, + { url = "https://files.pythonhosted.org/packages/d7/42/bc2d02d0fa7badfa63aa8d6dcd8793a9f7ef5a94396801684a51ed8d8287/librt-0.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c81aef782380f0f13ead670aae01825eb653b44b046aa0e5ebbb79f76ed4aa11", size = 230589, upload-time = "2026-04-09T16:05:22.305Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7b/e2d95cc513866373692aa5edf98080d5602dd07cabfb9e5d2f70df2f25f7/librt-0.9.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66b58fed90a545328e80d575467244de3741e088c1af928f0b489ebec3ef3858", size = 224610, upload-time = "2026-04-09T16:05:23.647Z" }, + { url = "https://files.pythonhosted.org/packages/31/d5/6cec4607e998eaba57564d06a1295c21b0a0c8de76e4e74d699e627bd98c/librt-0.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e78fb7419e07d98c2af4b8567b72b3eaf8cb05caad642e9963465569c8b2d87e", size = 232558, upload-time = "2026-04-09T16:05:25.025Z" }, + { url = "https://files.pythonhosted.org/packages/95/8c/27f1d8d3aaf079d3eb26439bf0b32f1482340c3552e324f7db9dca858671/librt-0.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c3786f0f4490a5cd87f1ed6cefae833ad6b1060d52044ce0434a2e85893afd0", size = 225521, upload-time = "2026-04-09T16:05:26.311Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d8/1e0d43b1c329b416017619469b3c3801a25a6a4ef4a1c68332aeaa6f72ca/librt-0.9.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8494cfc61e03542f2d381e71804990b3931175a29b9278fdb4a5459948778dc2", size = 227789, upload-time = "2026-04-09T16:05:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/2c/b4/d3d842e88610fcd4c8eec7067b0c23ef2d7d3bff31496eded6a83b0f99be/librt-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:07cf11f769831186eeac424376e6189f20ace4f7263e2134bdb9757340d84d4d", size = 248616, upload-time = "2026-04-09T16:05:29.181Z" }, + { url = "https://files.pythonhosted.org/packages/ec/28/527df8ad0d1eb6c8bdfa82fc190f1f7c4cca5a1b6d7b36aeabf95b52d74d/librt-0.9.0-cp313-cp313-win32.whl", hash = "sha256:850d6d03177e52700af605fd60db7f37dcb89782049a149674d1a9649c2138fd", size = 56039, upload-time = "2026-04-09T16:05:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a7/413652ad0d92273ee5e30c000fc494b361171177c83e57c060ecd3c21538/librt-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:a5af136bfba820d592f86c67affcef9b3ff4d4360ac3255e341e964489b48519", size = 63264, upload-time = "2026-04-09T16:05:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0a/92c244309b774e290ddb15e93363846ae7aa753d9586b8aad511c5e6145b/librt-0.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:4c4d0440a3a8e31d962340c3e1cc3fc9ee7febd34c8d8f770d06adb947779ea5", size = 53728, upload-time = "2026-04-09T16:05:33.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c1/184e539543f06ea2912f4b92a5ffaede4f9b392689e3f00acbf8134bee92/librt-0.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:3f05d145df35dca5056a8bc3838e940efebd893a54b3e19b2dda39ceaa299bcb", size = 67830, upload-time = "2026-04-09T16:05:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/23399bdcb7afca819acacdef31b37ee59de261bd66b503a7995c03c4b0dc/librt-0.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1c587494461ebd42229d0f1739f3aa34237dd9980623ecf1be8d3bcba79f4499", size = 70280, upload-time = "2026-04-09T16:05:35.649Z" }, + { url = "https://files.pythonhosted.org/packages/9f/0b/4542dc5a2b8772dbf92cafb9194701230157e73c14b017b6961a23598b03/librt-0.9.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0a2040f801406b93657a70b72fa12311063a319fee72ce98e1524da7200171f", size = 201925, upload-time = "2026-04-09T16:05:36.739Z" }, + { url = "https://files.pythonhosted.org/packages/31/d4/8ee7358b08fd0cfce051ef96695380f09b3c2c11b77c9bfbc367c921cce5/librt-0.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f38bc489037eca88d6ebefc9c4d41a4e07c8e8b4de5188a9e6d290273ad7ebb1", size = 212381, upload-time = "2026-04-09T16:05:38.043Z" }, + { url = "https://files.pythonhosted.org/packages/f2/94/a2025fe442abedf8b038038dab3dba942009ad42b38ea064a1a9e6094241/librt-0.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3fd278f5e6bf7c75ccd6d12344eb686cc020712683363b66f46ac79d37c799f", size = 227065, upload-time = "2026-04-09T16:05:39.394Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e9/b9fcf6afa909f957cfbbf918802f9dada1bd5d3c1da43d722fd6a310dc3f/librt-0.9.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fcbdf2a9ca24e87bbebb47f1fe34e531ef06f104f98c9ccfc953a3f3344c567a", size = 221333, upload-time = "2026-04-09T16:05:40.999Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7c/ba54cd6aa6a3c8cd12757a6870e0c79a64b1e6327f5248dcff98423f4d43/librt-0.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e306d956cfa027fe041585f02a1602c32bfa6bb8ebea4899d373383295a6c62f", size = 229051, upload-time = "2026-04-09T16:05:42.605Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4b/8cfdbad314c8677a0148bf0b70591d6d18587f9884d930276098a235461b/librt-0.9.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:465814ab157986acb9dfa5ccd7df944be5eefc0d08d31ec6e8d88bc71251d845", size = 222492, upload-time = "2026-04-09T16:05:43.842Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d1/2eda69563a1a88706808decdce035e4b32755dbfbb0d05e1a65db9547ed1/librt-0.9.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:703f4ae36d6240bfe24f542bac784c7e4194ec49c3ba5a994d02891649e2d85b", size = 223849, upload-time = "2026-04-09T16:05:45.054Z" }, + { url = "https://files.pythonhosted.org/packages/04/44/b2ed37df6be5b3d42cfe36318e0598e80843d5c6308dd63d0bf4e0ce5028/librt-0.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3be322a15ee5e70b93b7a59cfd074614f22cc8c9ff18bd27f474e79137ea8d3b", size = 245001, upload-time = "2026-04-09T16:05:46.34Z" }, + { url = "https://files.pythonhosted.org/packages/47/e7/617e412426df89169dd2a9ed0cc8752d5763336252c65dbf945199915119/librt-0.9.0-cp314-cp314-win32.whl", hash = "sha256:b8da9f8035bb417770b1e1610526d87ad4fc58a2804dc4d79c53f6d2cf5a6eb9", size = 51799, upload-time = "2026-04-09T16:05:47.738Z" }, + { url = "https://files.pythonhosted.org/packages/24/ed/c22ca4db0ca3cbc285e4d9206108746beda561a9792289c3c31281d7e9df/librt-0.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:b8bd70d5d816566a580d193326912f4a76ec2d28a97dc4cd4cc831c0af8e330e", size = 59165, upload-time = "2026-04-09T16:05:49.198Z" }, + { url = "https://files.pythonhosted.org/packages/24/56/875398fafa4cbc8f15b89366fc3287304ddd3314d861f182a4b87595ace0/librt-0.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:fc5758e2b7a56532dc33e3c544d78cbaa9ecf0a0f2a2da2df882c1d6b99a317f", size = 49292, upload-time = "2026-04-09T16:05:50.362Z" }, + { url = "https://files.pythonhosted.org/packages/4c/61/bc448ecbf9b2d69c5cff88fe41496b19ab2a1cbda0065e47d4d0d51c0867/librt-0.9.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f24b90b0e0c8cc9491fb1693ae91fe17cb7963153a1946395acdbdd5818429a4", size = 70175, upload-time = "2026-04-09T16:05:51.564Z" }, + { url = "https://files.pythonhosted.org/packages/60/f2/c47bb71069a73e2f04e70acbd196c1e5cc411578ac99039a224b98920fd4/librt-0.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fe56e80badb66fdcde06bef81bbaa5bfcf6fbd7aefb86222d9e369c38c6b228", size = 72951, upload-time = "2026-04-09T16:05:52.699Z" }, + { url = "https://files.pythonhosted.org/packages/29/19/0549df59060631732df758e8886d92088da5fdbedb35b80e4643664e8412/librt-0.9.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:527b5b820b47a09e09829051452bb0d1dd2122261254e2a6f674d12f1d793d54", size = 225864, upload-time = "2026-04-09T16:05:53.895Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f8/3b144396d302ac08e50f89e64452c38db84bc7b23f6c60479c5d3abd303c/librt-0.9.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d429bdd4ac0ab17c8e4a8af0ed2a7440b16eba474909ab357131018fe8c7e71", size = 241155, upload-time = "2026-04-09T16:05:55.191Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ce/ee67ec14581de4043e61d05786d2aed6c9b5338816b7859bcf07455c6a9f/librt-0.9.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7202bdcac47d3a708271c4304a474a8605a4a9a4a709e954bf2d3241140aa938", size = 252235, upload-time = "2026-04-09T16:05:56.549Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fa/0ead15daa2b293a54101550b08d4bafe387b7d4a9fc6d2b985602bae69b6/librt-0.9.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0d620e74897f8c2613b3c4e2e9c1e422eb46d2ddd07df540784d44117836af3", size = 244963, upload-time = "2026-04-09T16:05:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/29/68/9fbf9a9aa704ba87689e40017e720aced8d9a4d2b46b82451d8142f91ec9/librt-0.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d69fc39e627908f4c03297d5a88d9284b73f4d90b424461e32e8c2485e21c283", size = 257364, upload-time = "2026-04-09T16:05:59.686Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8d/9d60869f1b6716c762e45f66ed945b1e5dd649f7377684c3b176ae424648/librt-0.9.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c2640e23d2b7c98796f123ffd95cf2022c7777aa8a4a3b98b36c570d37e85eee", size = 247661, upload-time = "2026-04-09T16:06:00.938Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/a5c365093962310bfdb4f6af256f191085078ffb529b3f0cbebb5b33ebe2/librt-0.9.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:451daa98463b7695b0a30aa56bf637831ea559e7b8101ac2ef6382e8eb15e29c", size = 248238, upload-time = "2026-04-09T16:06:02.537Z" }, + { url = "https://files.pythonhosted.org/packages/a0/3c/2d34365177f412c9e19c0a29f969d70f5343f27634b76b765a54d8b27705/librt-0.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:928bd06eca2c2bbf4349e5b817f837509b0604342e65a502de1d50a7570afd15", size = 269457, upload-time = "2026-04-09T16:06:03.833Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/de45b239ea3bdf626f982a00c14bfcf2e12d261c510ba7db62c5969a27cd/librt-0.9.0-cp314-cp314t-win32.whl", hash = "sha256:a9c63e04d003bc0fb6a03b348018b9a3002f98268200e22cc80f146beac5dc40", size = 52453, upload-time = "2026-04-09T16:06:05.229Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f9/bfb32ae428aa75c0c533915622176f0a17d6da7b72b5a3c6363685914f70/librt-0.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f162af66a2ed3f7d1d161a82ca584efd15acd9c1cff190a373458c32f7d42118", size = 60044, upload-time = "2026-04-09T16:06:06.398Z" }, + { url = "https://files.pythonhosted.org/packages/aa/47/7d70414bcdbb3bc1f458a8d10558f00bbfdb24e5a11740fc8197e12c3255/librt-0.9.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a4b25c6c25cac5d0d9d6d6da855195b254e0021e513e0249f0e3b444dc6e0e61", size = 50009, upload-time = "2026-04-09T16:06:07.995Z" }, +] + [[package]] name = "mcp" version = "1.27.0" @@ -474,6 +534,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/46/f6b4ad632c67ef35209a66127e4bddc95759649dd595f71f13fba11bdf9a/mcp-1.27.0-py3-none-any.whl", hash = "sha256:5ce1fa81614958e267b21fb2aa34e0aea8e2c6ede60d52aba45fd47246b4d741", size = 215967, upload-time = "2026-04-02T14:48:07.24Z" }, ] +[[package]] +name = "mypy" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/3d/5b373635b3146264eb7a68d09e5ca11c305bbb058dfffbb47c47daf4f632/mypy-1.20.1.tar.gz", hash = "sha256:6fc3f4ecd52de81648fed1945498bf42fa2993ddfad67c9056df36ae5757f804", size = 3815892, upload-time = "2026-04-13T02:46:51.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/1b/75a7c825a02781ca10bc2f2f12fba2af5202f6d6005aad8d2d1f264d8d78/mypy-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:36ee2b9c6599c230fea89bbd79f401f9f9f8e9fcf0c777827789b19b7da90f51", size = 14494077, upload-time = "2026-04-13T02:45:55.085Z" }, + { url = "https://files.pythonhosted.org/packages/b0/54/5e5a569ea5c2b4d48b729fb32aa936eeb4246e4fc3e6f5b3d36a2dfbefb9/mypy-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fba3fb0968a7b48806b0c90f38d39296f10766885a94c83bd21399de1e14eb28", size = 13319495, upload-time = "2026-04-13T02:45:29.674Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a4/a1945b19f33e91721b59deee3abb484f2fa5922adc33bb166daf5325d76d/mypy-1.20.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef1415a637cd3627d6304dfbeddbadd21079dafc2a8a753c477ce4fc0c2af54f", size = 13696948, upload-time = "2026-04-13T02:46:15.006Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c6/75e969781c2359b2f9c15b061f28ec6d67c8b61865ceda176e85c8e7f2de/mypy-1.20.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef3461b1ad5cd446e540016e90b5984657edda39f982f4cc45ca317b628f5a37", size = 14706744, upload-time = "2026-04-13T02:46:00.482Z" }, + { url = "https://files.pythonhosted.org/packages/a8/6e/b221b1de981fc4262fe3e0bf9ec272d292dfe42394a689c2d49765c144c4/mypy-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:542dd63c9e1339b6092eb25bd515f3a32a1453aee8c9521d2ddb17dacd840237", size = 14949035, upload-time = "2026-04-13T02:45:06.021Z" }, + { url = "https://files.pythonhosted.org/packages/ca/4b/298ba2de0aafc0da3ff2288da06884aae7ba6489bc247c933f87847c41b3/mypy-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:1d55c7cd8ca22e31f93af2a01160a9e95465b5878de23dba7e48116052f20a8d", size = 10883216, upload-time = "2026-04-13T02:45:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f9/5e25b8f0b8cb92f080bfed9c21d3279b2a0b6a601cdca369a039ba84789d/mypy-1.20.1-cp312-cp312-win_arm64.whl", hash = "sha256:f5b84a79070586e0d353ee07b719d9d0a4aa7c8ee90c0ea97747e98cbe193019", size = 9814299, upload-time = "2026-04-13T02:45:21.934Z" }, + { url = "https://files.pythonhosted.org/packages/21/e8/ef0991aa24c8f225df10b034f3c2681213cb54cf247623c6dec9a5744e70/mypy-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f3886c03e40afefd327bd70b3f634b39ea82e87f314edaa4d0cce4b927ddcc1", size = 14500739, upload-time = "2026-04-13T02:46:05.442Z" }, + { url = "https://files.pythonhosted.org/packages/23/73/416ebec3047636ed89fa871dc8c54bf05e9e20aa9499da59790d7adb312d/mypy-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e860eb3904f9764e83bafd70c8250bdffdc7dde6b82f486e8156348bf7ceb184", size = 13314735, upload-time = "2026-04-13T02:46:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/10/1e/1505022d9c9ac2e014a384eb17638fb37bf8e9d0a833ea60605b66f8f7ba/mypy-1.20.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4b5aac6e785719da51a84f5d09e9e843d473170a9045b1ea7ea1af86225df4b", size = 13704356, upload-time = "2026-04-13T02:45:19.773Z" }, + { url = "https://files.pythonhosted.org/packages/98/91/275b01f5eba5c467a3318ec214dd865abb66e9c811231c8587287b92876a/mypy-1.20.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f37b6cd0fe2ad3a20f05ace48ca3523fc52ff86940e34937b439613b6854472e", size = 14696420, upload-time = "2026-04-13T02:45:24.205Z" }, + { url = "https://files.pythonhosted.org/packages/a1/57/b3779e134e1b7250d05f874252780d0a88c068bc054bcff99ca20a3a2986/mypy-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4bbb0f6b54ce7cc350ef4a770650d15fa70edd99ad5267e227133eda9c94218", size = 14936093, upload-time = "2026-04-13T02:45:32.087Z" }, + { url = "https://files.pythonhosted.org/packages/be/33/81b64991b0f3f278c3b55c335888794af190b2d59031a5ad1401bcb69f1e/mypy-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:c3dc20f8ec76eecd77148cdd2f1542ed496e51e185713bf488a414f862deb8f2", size = 10889659, upload-time = "2026-04-13T02:46:02.926Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fd/7adcb8053572edf5ef8f3db59599dfeeee3be9cc4c8c97e2d28f66f42ac5/mypy-1.20.1-cp313-cp313-win_arm64.whl", hash = "sha256:a9d62bbac5d6d46718e2b0330b25e6264463ed832722b8f7d4440ff1be3ca895", size = 9815515, upload-time = "2026-04-13T02:46:32.103Z" }, + { url = "https://files.pythonhosted.org/packages/40/cd/db831e84c81d57d4886d99feee14e372f64bbec6a9cb1a88a19e243f2ef5/mypy-1.20.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:12927b9c0ed794daedcf1dab055b6c613d9d5659ac511e8d936d96f19c087d12", size = 14483064, upload-time = "2026-04-13T02:45:26.901Z" }, + { url = "https://files.pythonhosted.org/packages/d5/82/74e62e7097fa67da328ac8ece8de09133448c04d20ddeaeba251a3000f01/mypy-1.20.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:752507dd481e958b2c08fc966d3806c962af5a9433b5bf8f3bdd7175c20e34fe", size = 13335694, upload-time = "2026-04-13T02:46:12.514Z" }, + { url = "https://files.pythonhosted.org/packages/74/c4/97e9a0abe4f3cdbbf4d079cb87a03b786efeccf5bf2b89fe4f96939ab2e6/mypy-1.20.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c614655b5a065e56274c6cbbe405f7cf7e96c0654db7ba39bc680238837f7b08", size = 13726365, upload-time = "2026-04-13T02:45:17.422Z" }, + { url = "https://files.pythonhosted.org/packages/d7/aa/a19d884a8d28fcd3c065776323029f204dbc774e70ec9c85eba228b680de/mypy-1.20.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c3f6221a76f34d5100c6d35b3ef6b947054123c3f8d6938a4ba00b1308aa572", size = 14693472, upload-time = "2026-04-13T02:46:41.253Z" }, + { url = "https://files.pythonhosted.org/packages/84/44/cc9324bd21cf786592b44bf3b5d224b3923c1230ec9898d508d00241d465/mypy-1.20.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4bdfc06303ac06500af71ea0cdbe995c502b3c9ba32f3f8313523c137a25d1b6", size = 14919266, upload-time = "2026-04-13T02:46:28.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dc/779abb25a8c63e8f44bf5a336217fa92790fa17e0c40e0c725d10cb01bbd/mypy-1.20.1-cp314-cp314-win_amd64.whl", hash = "sha256:0131edd7eba289973d1ba1003d1a37c426b85cdef76650cd02da6420898a5eb3", size = 11049713, upload-time = "2026-04-13T02:45:57.673Z" }, + { url = "https://files.pythonhosted.org/packages/28/08/4172be2ad7de9119b5a92ca36abbf641afdc5cb1ef4ae0c3a8182f29674f/mypy-1.20.1-cp314-cp314-win_arm64.whl", hash = "sha256:33f02904feb2c07e1fdf7909026206396c9deeb9e6f34d466b4cfedb0aadbbe4", size = 9999819, upload-time = "2026-04-13T02:46:35.039Z" }, + { url = "https://files.pythonhosted.org/packages/2d/af/af9e46b0c8eabbce9fc04a477564170f47a1c22b308822282a59b7ff315f/mypy-1.20.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:168472149dd8cc505c98cefd21ad77e4257ed6022cd5ed2fe2999bed56977a5a", size = 15547508, upload-time = "2026-04-13T02:46:25.588Z" }, + { url = "https://files.pythonhosted.org/packages/a7/cd/39c9e4ad6ba33e069e5837d772a9e6c304b4a5452a14a975d52b36444650/mypy-1.20.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eb674600309a8f22790cca883a97c90299f948183ebb210fbef6bcee07cb1986", size = 14399557, upload-time = "2026-04-13T02:46:10.021Z" }, + { url = "https://files.pythonhosted.org/packages/83/c1/3fd71bdc118ffc502bf57559c909927bb7e011f327f7bb8e0488e98a5870/mypy-1.20.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef2b2e4cc464ba9795459f2586923abd58a0055487cbe558cb538ea6e6bc142a", size = 15045789, upload-time = "2026-04-13T02:45:10.81Z" }, + { url = "https://files.pythonhosted.org/packages/8e/73/6f07ff8b57a7d7b3e6e5bf34685d17632382395c8bb53364ec331661f83e/mypy-1.20.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee461d396dd46b3f0ed5a098dbc9b8860c81c46ad44fa071afcfbc149f167c9", size = 15850795, upload-time = "2026-04-13T02:45:03.349Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e2/f7dffec1c7767078f9e9adf0c786d1fe0ff30964a77eb213c09b8b58cb76/mypy-1.20.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e364926308b3e66f1361f81a566fc1b2f8cd47fc8525e8136d4058a65a4b4f02", size = 16088539, upload-time = "2026-04-13T02:46:17.841Z" }, + { url = "https://files.pythonhosted.org/packages/1a/76/e0dee71035316e75a69d73aec2f03c39c21c967b97e277fd0ef8fd6aec66/mypy-1.20.1-cp314-cp314t-win_amd64.whl", hash = "sha256:a0c17fbd746d38c70cbc42647cfd884f845a9708a4b160a8b4f7e70d41f4d7fa", size = 12575567, upload-time = "2026-04-13T02:45:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/22/a8/7ed43c9d9c3d1468f86605e323a5d97e411a448790a00f07e779f3211a46/mypy-1.20.1-cp314-cp314t-win_arm64.whl", hash = "sha256:db2cb89654626a912efda69c0d5c1d22d948265e2069010d3dde3abf751c7d08", size = 10378823, upload-time = "2026-04-13T02:45:13.35Z" }, + { url = "https://files.pythonhosted.org/packages/d8/28/926bd972388e65a39ee98e188ccf67e81beb3aacfd5d6b310051772d974b/mypy-1.20.1-py3-none-any.whl", hash = "sha256:1aae28507f253fe82d883790d1c0a0d35798a810117c88184097fe8881052f06", size = 2636553, upload-time = "2026-04-13T02:46:30.45Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + [[package]] name = "packaging" version = "26.0" @@ -483,6 +595,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -883,6 +1004,7 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "httpx" }, + { name = "mypy" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "ruff" }, @@ -895,6 +1017,7 @@ requires-dist = [ { name = "claude-agent-sdk", specifier = ">=0.1.0" }, { name = "fastapi", specifier = ">=0.115" }, { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.27" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.13" }, { name = "pydantic", specifier = ">=2.9" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24" }, From 78132e4a72a35d35c4e4054ef30df8779ab54eb2 Mon Sep 17 00:00:00 2001 From: "Matt (via Claude Code)" Date: Sun, 19 Apr 2026 11:35:30 -0500 Subject: [PATCH 2/3] style: apply prettier to frontend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mechanical reformat of 56 files under frontend/ — double quotes, 100ch width, trailing commas, consistent arrow-param parens. Zero behavior changes. Establishes the format baseline that CI now enforces via \`npm run format:check\`. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/index.html | 84 ++- frontend/src/components/AgentRegistry.tsx | 39 +- frontend/src/components/AppShell.tsx | 5 +- frontend/src/components/AtomicLineageView.tsx | 80 +-- frontend/src/components/AtomicRunDetail.tsx | 137 ++-- frontend/src/components/AtomicSidebar.tsx | 52 +- frontend/src/components/BibleBrowser.tsx | 22 +- frontend/src/components/BreedingReport.tsx | 5 +- frontend/src/components/ChallengeGallery.tsx | 8 +- frontend/src/components/CompetitorCard.tsx | 14 +- .../src/components/CompositeMarkdownView.tsx | 20 +- frontend/src/components/EvolutionArena.tsx | 596 +++++++++--------- frontend/src/components/EvolutionCard.tsx | 9 +- .../src/components/EvolutionDashboard.tsx | 78 +-- frontend/src/components/EvolutionResults.tsx | 10 +- frontend/src/components/FileTree.tsx | 15 +- frontend/src/components/FitnessChart.tsx | 16 +- frontend/src/components/FitnessExplainer.tsx | 26 +- frontend/src/components/FitnessRadar.tsx | 21 +- frontend/src/components/InviteGate.tsx | 30 +- frontend/src/components/JournalBrowser.tsx | 42 +- .../src/components/JudgingPipelinePill.tsx | 6 +- frontend/src/components/LiveFeedLog.tsx | 16 +- frontend/src/components/ModeCard.tsx | 8 +- frontend/src/components/OverallAssessment.tsx | 67 +- frontend/src/components/PackageExplorer.tsx | 78 +-- frontend/src/components/ParameterInput.tsx | 10 +- .../src/components/PerDimensionFitnessBar.tsx | 16 +- frontend/src/components/PipelineOverview.tsx | 34 +- frontend/src/components/PipelineSteps.tsx | 513 +++++++++++---- frontend/src/components/PrimaryButton.tsx | 12 +- frontend/src/components/ProcessFlow.tsx | 11 +- frontend/src/components/ResearchBrowser.tsx | 38 +- .../src/components/RichVariantBreakdown.tsx | 9 +- frontend/src/components/RunNarrative.tsx | 19 +- frontend/src/components/SeedDetailView.tsx | 27 +- frontend/src/components/Sidebar.tsx | 11 +- frontend/src/components/SkillContentModal.tsx | 27 +- frontend/src/components/SkillDiffViewer.tsx | 45 +- .../src/components/SkillExportPreview.tsx | 20 +- frontend/src/components/SkillUploader.tsx | 13 +- frontend/src/components/SkillVariantCard.tsx | 45 +- frontend/src/components/SkldBench.tsx | 34 +- frontend/src/components/SkldBenchFamily.tsx | 32 +- frontend/src/components/SpecAssistantChat.tsx | 29 +- .../src/components/SpecializationInput.tsx | 59 +- frontend/src/components/StatCard.tsx | 17 +- frontend/src/components/StatusGlow.tsx | 3 +- frontend/src/components/TaxonomyBrowser.tsx | 61 +- frontend/src/components/VariantBreakdown.tsx | 19 +- frontend/src/hooks/derivePhases.test.ts | 5 +- frontend/src/hooks/useCssVar.ts | 4 +- frontend/src/hooks/useEvolutionSocket.test.ts | 12 +- frontend/src/hooks/useEvolutionSocket.ts | 52 +- frontend/src/index.css | 144 +++-- frontend/src/types/index.ts | 16 +- frontend/tailwind.config.js | 3 +- 57 files changed, 1337 insertions(+), 1487 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index b2c37a2..38daf63 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,26 +3,38 @@ - - + + SKLD.run — Evolve Claude Agent Skills - + - + - + - + @@ -34,7 +46,7 @@ diff --git a/frontend/src/components/AgentRegistry.tsx b/frontend/src/components/AgentRegistry.tsx index 3df5266..149498b 100644 --- a/frontend/src/components/AgentRegistry.tsx +++ b/frontend/src/components/AgentRegistry.tsx @@ -50,10 +50,7 @@ export default function AgentRegistry() { }, []); const completed = useMemo( - () => - (runs ?? []).filter( - (r) => r.status === "complete" && r.id !== SEED_RUN_ID, - ), + () => (runs ?? []).filter((r) => r.status === "complete" && r.id !== SEED_RUN_ID), [runs], ); @@ -68,9 +65,7 @@ export default function AgentRegistry() { if (modeFilter !== "all") list = list.filter((r) => r.mode === modeFilter); if (search.trim()) { const q = search.toLowerCase(); - list = list.filter( - (r) => r.specialization.toLowerCase().includes(q) || r.id.includes(q), - ); + list = list.filter((r) => r.specialization.toLowerCase().includes(q) || r.id.includes(q)); } const sorted = [...list]; if (sortKey === "fitness") { @@ -89,8 +84,7 @@ export default function AgentRegistry() { const filteredSeeds = useMemo(() => { let list = seeds ?? []; - if (categoryFilter !== "all") - list = list.filter((s) => s.category === categoryFilter); + if (categoryFilter !== "all") list = list.filter((s) => s.category === categoryFilter); if (search.trim()) { const q = search.toLowerCase(); list = list.filter( @@ -115,9 +109,8 @@ export default function AgentRegistry() { Skill Registry

- Browse curated Gen 0 Skills and completed evolution runs. Deploy - any Skill directly or fork-and-evolve it with your own - specialization. + Browse curated Gen 0 Skills and completed evolution runs. Deploy any Skill directly or + fork-and-evolve it with your own specialization.

@@ -133,11 +126,7 @@ export default function AgentRegistry() { />
- {error && ( -
- {error} -
- )} + {error &&
{error}
} {/* ─── Curated Seeds Section ─────────────────────────────────────── */}
@@ -146,12 +135,10 @@ export default function AgentRegistry() {

✦ Curated Library

-

- Gen 0 Skills -

+

Gen 0 Skills

- Production-ready Skills you can deploy immediately or fork as the - starting point for evolution. + Production-ready Skills you can deploy immediately or fork as the starting point for + evolution.

@@ -209,7 +196,7 @@ export default function AgentRegistry() { {seed.traits.slice(0, 3).map((t) => ( {t} @@ -219,7 +206,7 @@ export default function AgentRegistry() {
View @@ -243,9 +230,7 @@ export default function AgentRegistry() {

Completed Runs

-

- Community Evolutions -

+

Community Evolutions

{completed.length} runs diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx index c067298..701fe45 100644 --- a/frontend/src/components/AppShell.tsx +++ b/frontend/src/components/AppShell.tsx @@ -201,10 +201,7 @@ export default function AppShell() { GitHub - + /llms.txt diff --git a/frontend/src/components/AtomicLineageView.tsx b/frontend/src/components/AtomicLineageView.tsx index 21f71d0..1660f96 100644 --- a/frontend/src/components/AtomicLineageView.tsx +++ b/frontend/src/components/AtomicLineageView.tsx @@ -17,9 +17,7 @@ interface AtomicLineageViewProps { function parseCompositeSections(compositeMd: string): Map { const out = new Map(); // Strip frontmatter if present. - const fmStripped = - compositeMd.match(/^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)$/)?.[1] ?? - compositeMd; + const fmStripped = compositeMd.match(/^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)$/)?.[1] ?? compositeMd; const lines = fmStripped.split("\n"); let currentHeading: string | null = null; let currentBody: string[] = []; @@ -27,10 +25,7 @@ function parseCompositeSections(compositeMd: string): Map { const m = line.match(/^##\s+(.+)$/); if (m) { if (currentHeading) { - out.set( - normalizeHeading(currentHeading), - currentBody.join("\n").trim(), - ); + out.set(normalizeHeading(currentHeading), currentBody.join("\n").trim()); } currentHeading = m[1]; currentBody = []; @@ -93,11 +88,7 @@ function stripFrontmatter(md: string): string { * SKILL.md inline (no fake diff). A user can see exactly what each parent * contributed without being told lies about what "changed." */ -export default function AtomicLineageView({ - nodes, - edges, - genomes, -}: AtomicLineageViewProps) { +export default function AtomicLineageView({ nodes, edges, genomes }: AtomicLineageViewProps) { const [expandedId, setExpandedId] = useState(null); // Partition nodes into composite (generation 1, engineer_composite) and @@ -118,9 +109,7 @@ export default function AtomicLineageView({ return { composite: undefined, parents: [] as LineageNode[] }; } const parentIds = new Set( - edges - .filter((e) => e.child_id === composite!.id) - .map((e) => e.parent_id), + edges.filter((e) => e.child_id === composite!.id).map((e) => e.parent_id), ); const parentNodes = nodes.filter((n) => parentIds.has(n.id)); return { composite, parents: parentNodes }; @@ -135,9 +124,7 @@ export default function AtomicLineageView({ // side-by-side "what this parent contributed" view when a parent card is // clicked. The composite genome is the one with meta_strategy === 'engineer_composite'. const compositeSections = useMemo(() => { - const compositeGenome = genomes.find( - (g) => g.meta_strategy === "engineer_composite", - ); + const compositeGenome = genomes.find((g) => g.meta_strategy === "engineer_composite"); if (!compositeGenome) return new Map(); return parseCompositeSections(compositeGenome.skill_md_content); }, [genomes]); @@ -195,27 +182,23 @@ export default function AtomicLineageView({

- Atomic composites are assembled{" "} - from many independent parents, not mutated from one. - Running a line-by-line diff between a parent (e.g. the - heex-and-verified-routes variant) and the composite would produce - nonsense — the parent's ~30 lines all appear "removed" and the - composite's ~300 lines all appear "added", because they're about - completely different topics. The traditional Lineage Diff Viewer - is reserved for molecular{" "} - mutation chains where parent → child is a real edit. + Atomic composites are assembled from many + independent parents, not mutated from one. Running a line-by-line diff between + a parent (e.g. the heex-and-verified-routes variant) and the composite would produce + nonsense — the parent's ~30 lines all appear "removed" and the composite's ~300 lines + all appear "added", because they're about completely different topics. The traditional + Lineage Diff Viewer is reserved for{" "} + molecular mutation chains where parent → + child is a real edit.

Instead, this view shows a{" "} - contribution view: - click a parent card below to see its SKILL.md side-by-side with - the matching section in the composite. The composite's body is - organized by dimension — the heex-routes parent contributes the - ## HEEx + Verified Routes{" "} - section, the streams parent contributes the{" "} - ## Streams + Collections{" "} - section, and so on. You can read both sides and see exactly - what survived into the final skill. + contribution view: click a parent card + below to see its SKILL.md side-by-side with the matching section in the composite. The + composite's body is organized by dimension — the heex-routes parent contributes the + ## HEEx + Verified Routes section, the streams parent + contributes the ## Streams + Collections section, and + so on. You can read both sides and see exactly what survived into the final skill.

@@ -231,9 +214,7 @@ export default function AtomicLineageView({ {compositeGenome?.frontmatter && typeof compositeGenome.frontmatter === "object" && "name" in compositeGenome.frontmatter - ? String( - (compositeGenome.frontmatter as { name: string }).name, - ) + ? String((compositeGenome.frontmatter as { name: string }).name) : composite.id}

@@ -274,9 +255,7 @@ export default function AtomicLineageView({ }`} >
- - {dim} - + {dim} {isFoundation && ( FND @@ -332,9 +311,8 @@ export default function AtomicLineageView({ ) : (

- No matching section found in the composite body for - this parent's dimension. The parent may have - contributed cross-cutting rules rather than a single + No matching section found in the composite body for this parent's dimension. + The parent may have contributed cross-cutting rules rather than a single named section.

)} @@ -343,10 +321,9 @@ export default function AtomicLineageView({

Top: the parent variant's full SKILL.md. Bottom: the matching{" "} - ##{" "} - section from the composite where this parent's guidance was - distilled. Sections may have been compressed, reworded, or - merged during Engineer assembly. + ## section from the + composite where this parent's guidance was distilled. Sections may have been + compressed, reworded, or merged during Engineer assembly.

) : ( @@ -355,14 +332,13 @@ export default function AtomicLineageView({ Contribution view

- Select a parent from the list on the left to see its - SKILL.md alongside the matching section in the composite. + Select a parent from the list on the left to see its SKILL.md alongside the matching + section in the composite.

)} - ); } diff --git a/frontend/src/components/AtomicRunDetail.tsx b/frontend/src/components/AtomicRunDetail.tsx index cab69b4..df386ea 100644 --- a/frontend/src/components/AtomicRunDetail.tsx +++ b/frontend/src/components/AtomicRunDetail.tsx @@ -95,7 +95,7 @@ export default function AtomicRunDetail({ useEffect(() => { if (hasPropDims) return; fetch(`/api/runs/${runId}/dimensions`) - .then((r) => r.ok ? r.json() as Promise : []) + .then((r) => (r.ok ? (r.json() as Promise) : [])) .then(setDimensionsFetched) .catch(() => {}); }, [runId, hasPropDims]); @@ -205,10 +205,7 @@ export default function AtomicRunDetail({ }, [variants]); const strippedSpecialization = useMemo( - () => - runDetail.specialization - .replace(/\s*\[(mock|seed)_v[a-f0-9]+\]\s*/gi, " ") - .trim(), + () => runDetail.specialization.replace(/\s*\[(mock|seed)_v[a-f0-9]+\]\s*/gi, " ").trim(), [runDetail.specialization], ); @@ -226,9 +223,7 @@ export default function AtomicRunDetail({ const totalCost = report?.metadata.total_cost_usd ?? runDetail.total_cost_usd; const durationMin = - report?.metadata.duration_sec != null - ? (report.metadata.duration_sec / 60).toFixed(1) - : null; + report?.metadata.duration_sec != null ? (report.metadata.duration_sec / 60).toFixed(1) : null; const genomes = report?.skill_genomes ?? []; return ( @@ -242,9 +237,7 @@ export default function AtomicRunDetail({

{report?.taxonomy?.family_label ?? runDetail.specialization}

-

- {strippedSpecialization} -

+

{strippedSpecialization}

{runDetail.status} @@ -255,8 +248,7 @@ export default function AtomicRunDetail({ "This run has 1 generation. A re-evolution would produce Gen 2 seeded by this Gen 1 composite + newly-spawned alternatives." } > - Gen {competitionScores?.generation ?? 1} /{" "} - {competitionScores?.total_generations ?? 1} + Gen {competitionScores?.generation ?? 1} / {competitionScores?.total_generations ?? 1} {variants?.length ?? "—"} dimensions @@ -287,9 +279,7 @@ export default function AtomicRunDetail({

Baseline:{" "} - - {runDetail.baseline_fitness.toFixed(3)} - + {runDetail.baseline_fitness.toFixed(3)}

{runDetail.best_fitness != null && runDetail.baseline_fitness > 0 && (

@@ -313,9 +303,7 @@ export default function AtomicRunDetail({ )}

)} -

- ${totalCost.toFixed(2)} -

+

${totalCost.toFixed(2)}

@@ -348,9 +334,8 @@ export default function AtomicRunDetail({ report={report} seedWinnerCount={seedWinnerDimensions.size} perfectFitnessCount={ - variants?.filter( - (v) => v.tier === "capability" && v.fitness_score >= 0.999, - ).length ?? 0 + variants?.filter((v) => v.tier === "capability" && v.fitness_score >= 0.999).length ?? + 0 } /> @@ -364,7 +349,7 @@ export default function AtomicRunDetail({ )} {/* Sticky tab bar */} -
+
{TABS.map((tab) => (
)} - {activeTab === "tests" && report && ( - - )} + {activeTab === "tests" && report && } {activeTab === "narrative" && report && ( + )} {activeTab === "lineage" && !lineage && (

Loading lineage…

@@ -476,9 +457,7 @@ export default function AtomicRunDetail({ challenges={report.challenges} learningLog={runDetail.learning_log ?? report.learning_log} runId={runId} - familyLabel={ - report.taxonomy?.family_label ?? runDetail.specialization - } + familyLabel={report.taxonomy?.family_label ?? runDetail.specialization} /> )} {activeTab === "package" && !report && ( @@ -488,9 +467,7 @@ export default function AtomicRunDetail({ {/* Error fallback */} {reportError && ( -

- Report endpoint error: {reportError} -

+

Report endpoint error: {reportError}

)}
); @@ -525,8 +502,7 @@ function DimensionsOverview({ const capabilities = dimensions.filter((d) => d.tier === "capability"); const avgFitness = - dimensions.reduce((sum, d) => sum + (d.fitness_score ?? 0), 0) / - Math.max(dimensions.length, 1); + dimensions.reduce((sum, d) => sum + (d.fitness_score ?? 0), 0) / Math.max(dimensions.length, 1); return (
@@ -536,9 +512,7 @@ function DimensionsOverview({

Dimensions

-

- {dimensions.length} -

+

{dimensions.length}

{foundations.length} foundation · {capabilities.length} capability

@@ -570,13 +544,10 @@ function DimensionsOverview({ }`} > {bestFitness > baselineFitness ? "+" : ""} - {(((bestFitness - baselineFitness) / baselineFitness) * 100).toFixed(0)} - % + {(((bestFitness - baselineFitness) / baselineFitness) * 100).toFixed(0)}%

) : ( -

- — -

+

)}
@@ -587,8 +558,8 @@ function DimensionsOverview({ Per-Dimension Fitness vs Baseline

- Each dimension's evolved fitness compared to raw Sonnet on the same challenges. - Copper = skill fitness. Gray = raw Sonnet baseline. + Each dimension's evolved fitness compared to raw Sonnet on the same challenges. Copper = + skill fitness. Gray = raw Sonnet baseline.

@@ -599,11 +570,7 @@ function DimensionsOverview({ Foundation

{foundations.map((d) => ( - + ))} )} @@ -615,11 +582,7 @@ function DimensionsOverview({ Capabilities

{capabilities.map((d) => ( - + ))} )} @@ -633,7 +596,8 @@ function DimensionsOverview({ Benchmark Tier Breakdown

- Raw Sonnet performance by challenge difficulty tier on {benchDetail.total_challenges} challenges. + Raw Sonnet performance by challenge difficulty tier on {benchDetail.total_challenges}{" "} + challenges.

@@ -648,16 +612,18 @@ function DimensionsOverview({ {benchDetail.tiers.map((t) => ( - - + ))} - - - - + + + +
+
{t.tier} @@ -683,21 +649,12 @@ function DimensionsOverview({ ); } -function DimensionBar({ - dim, - baseline, -}: { - dim: DimensionStatus; - baseline?: number; -}) { +function DimensionBar({ dim, baseline }: { dim: DimensionStatus; baseline?: number }) { const fitness = dim.fitness_score ?? 0; const maxVal = Math.max(fitness, baseline ?? 0, 0.01); const fitnessPct = (fitness / maxVal) * 100; const baselinePct = baseline != null ? (baseline / maxVal) * 100 : 0; - const lift = - baseline != null && baseline > 0 - ? ((fitness - baseline) / baseline) * 100 - : null; + const lift = baseline != null && baseline > 0 ? ((fitness - baseline) / baseline) * 100 : null; return (
@@ -719,9 +676,7 @@ function DimensionBar({ />
- - {fitness.toFixed(3)} - + {fitness.toFixed(3)} {lift != null && ( = { +const STATUS_STYLES: Record = { pending: { dot: "bg-surface-container-high", label: "text-on-surface-dim", @@ -30,11 +27,7 @@ const STATUS_STYLES: Record< }, }; -export default function AtomicSidebar({ - runId, - dimensions, - activeDimension, -}: AtomicSidebarProps) { +export default function AtomicSidebar({ runId, dimensions, activeDimension }: AtomicSidebarProps) { const completed = dimensions.filter((d) => d.status === "complete").length; const total = dimensions.length; const foundations = dimensions.filter((d) => d.tier === "foundation"); @@ -47,9 +40,7 @@ export default function AtomicSidebar({

Atomic Evolution

-

- {runId.slice(0, 20)} -

+

{runId.slice(0, 20)}

@@ -77,11 +68,7 @@ export default function AtomicSidebar({ Foundation

{foundations.map((dim) => ( - + ))}
)} @@ -93,11 +80,7 @@ export default function AtomicSidebar({ Capabilities

{capabilities.map((dim) => ( - + ))}
)} @@ -116,32 +99,19 @@ export default function AtomicSidebar({ ); } -function DimensionRow({ - dim, - isActive, -}: { - dim: DimensionStatus; - isActive: boolean; -}) { +function DimensionRow({ dim, isActive }: { dim: DimensionStatus; isActive: boolean }) { const styles = STATUS_STYLES[dim.status]; const label = dim.dimension.replace(/-/g, " "); return (
-
+
-

+

{label}

@@ -151,9 +121,7 @@ function DimensionRow({ )} {dim.status === "running" && ( - - live - + live )}
); diff --git a/frontend/src/components/BibleBrowser.tsx b/frontend/src/components/BibleBrowser.tsx index 89757ae..041522a 100644 --- a/frontend/src/components/BibleBrowser.tsx +++ b/frontend/src/components/BibleBrowser.tsx @@ -46,12 +46,7 @@ export default function BibleBrowser() { const allEntries = useMemo(() => { if (!data) return []; - return [ - ...(data.books ?? []), - ...data.patterns, - ...data.findings, - ...data.anti_patterns, - ]; + return [...(data.books ?? []), ...data.patterns, ...data.findings, ...data.anti_patterns]; }, [data]); const selected = useMemo( @@ -79,18 +74,13 @@ export default function BibleBrowser() { The SKLD Bible

- Empirical knowledge about building skills for AI coding agents. - Every finding is backed by measured data from 867 controlled - experiments, not theory or intuition. + Empirical knowledge about building skills for AI coding agents. Every finding is backed + by measured data from 867 controlled experiments, not theory or intuition.

- {error && ( -
- {error} -
- )} + {error &&
{error}
}
{/* Sidebar */} @@ -140,9 +130,7 @@ export default function BibleBrowser() { {selected.filename}

- - {selected.body} - + {selected.body}
) : ( diff --git a/frontend/src/components/BreedingReport.tsx b/frontend/src/components/BreedingReport.tsx index 67396c2..3f48a8b 100644 --- a/frontend/src/components/BreedingReport.tsx +++ b/frontend/src/components/BreedingReport.tsx @@ -24,10 +24,7 @@ export default function BreedingReport({ report, lessons }: BreedingReportProps)

    {lessons.map((lesson, i) => ( -
  • +
  • {lesson}
  • diff --git a/frontend/src/components/ChallengeGallery.tsx b/frontend/src/components/ChallengeGallery.tsx index 3536647..4e0f3c7 100644 --- a/frontend/src/components/ChallengeGallery.tsx +++ b/frontend/src/components/ChallengeGallery.tsx @@ -42,9 +42,7 @@ export default function ChallengeGallery({ challenges }: ChallengeGalleryProps)

    Challenge Gallery

    -

    - No challenges recorded for this run. -

    +

    No challenges recorded for this run.

); } @@ -85,9 +83,7 @@ export default function ChallengeGallery({ challenges }: ChallengeGalleryProps) {c.difficulty}
-

- {c.prompt} -

+

{c.prompt}

))} diff --git a/frontend/src/components/CompetitorCard.tsx b/frontend/src/components/CompetitorCard.tsx index c3db9f5..f5bcd8f 100644 --- a/frontend/src/components/CompetitorCard.tsx +++ b/frontend/src/components/CompetitorCard.tsx @@ -31,11 +31,7 @@ const STATE_VARIANT: Record< error: "error", }; -const ACTIVE_STATES: ReadonlySet = new Set([ - "writing", - "testing", - "iterating", -]); +const ACTIVE_STATES: ReadonlySet = new Set(["writing", "testing", "iterating"]); export default function CompetitorCard({ competitorId, @@ -49,7 +45,7 @@ export default function CompetitorCard({ className={ "flex items-start gap-4 rounded-xl border px-5 py-4 transition-all " + (isActive - ? "border-primary/40 bg-surface-container-lowest animate-breathe-border" + ? "animate-breathe-border border-primary/40 bg-surface-container-lowest" : state === "done" ? "border-tertiary/30 bg-surface-container-lowest" : "border-outline-variant bg-surface-container-lowest hover:bg-surface-container-low") @@ -58,7 +54,7 @@ export default function CompetitorCard({
-
+

Competitor {String.fromCharCode(65 + competitorId)} @@ -76,9 +72,7 @@ export default function CompetitorCard({ skill {skillId.slice(0, 8)}

{challengeLabel && ( -

- → {challengeLabel} -

+

→ {challengeLabel}

)}
diff --git a/frontend/src/components/CompositeMarkdownView.tsx b/frontend/src/components/CompositeMarkdownView.tsx index 567c957..1b5c939 100644 --- a/frontend/src/components/CompositeMarkdownView.tsx +++ b/frontend/src/components/CompositeMarkdownView.tsx @@ -50,13 +50,9 @@ export default function CompositeMarkdownView({ Composite Skill — {bestSkillId?.slice(0, 16) ?? "—"}

{name && ( -

- {name} -

- )} - {description && ( -

{description}

+

{name}

)} + {description &&

{description}

}
- )} + +
+
+

+ Elapsed +

+

{elapsedFmt}

+

+ Budget Used +

+

+ ${sockState.totalCostUsd.toFixed(2)} / ${budgetCap.toFixed(2)} +

+ {!isComplete && !isFailed && ( + + )}
+ - {/* Connection/error banners */} - {sockState.status === "closed" && !isComplete && !isFailed && ( -
- Connection lost. Reconnecting... -
- )} - {isFailed && ( -
- Run failed: {sockState.failureReason ?? "(no reason)"} -
- )} - -
- {/* Main column */} -
- {/* Phase status — shows what the engine is doing during long waits */} - {(() => { - const activeDim = sockState.atomicDimensions.find( - (d) => d.dimension === activeDimension, - ); - if (!activeDim?.phaseDetail) return null; - // Don't show once competitors are running - if (variantGroups.length > 0) return null; - return ( -
-
-

- {activeDim.phaseDetail} -

-
- ); - })()} + {/* Connection/error banners */} + {sockState.status === "closed" && !isComplete && !isFailed && ( +
+ Connection lost. Reconnecting... +
+ )} + {isFailed && ( +
+ Run failed: {sockState.failureReason ?? "(no reason)"} +
+ )} + +
+ {/* Main column */} +
+ {/* Phase status — shows what the engine is doing during long waits */} + {(() => { + const activeDim = sockState.atomicDimensions.find( + (d) => d.dimension === activeDimension, + ); + if (!activeDim?.phaseDetail) return null; + // Don't show once competitors are running + if (variantGroups.length > 0) return null; + return ( +
+
+

{activeDim.phaseDetail}

+
+ ); + })()} - {/* Current challenge — single card, not a list */} - {challenges.length > 0 && (() => { + {/* Current challenge — single card, not a list */} + {challenges.length > 0 && + (() => { const ch = challenges[challenges.length - 1]; return (
@@ -300,220 +301,241 @@ export default function EvolutionArena() { > {ch.difficulty} -

- {ch.prompt} -

+

{ch.prompt}

); })()} - {/* Competition */} -
-
-
-

- Competition -

-

- Baseline vs seed vs spawn — scored with 6-layer composite. + {/* Competition */} +

+
+
+

Competition

+

+ Baseline vs seed vs spawn — scored with 6-layer composite. +

+
+ + {variantGroups.length > 0 ? `${variantGroups.length} competitors` : "waiting"} + +
+
+ {variantGroups.length === 0 ? ( +
+
+

+ Generating skill variants — each variant is a complete SKILL.md package with + scripts, references, and examples. This typically takes 1-2 minutes per + dimension.

- - {variantGroups.length > 0 ? `${variantGroups.length} competitors` : "waiting"} - -
-
- {variantGroups.length === 0 ? ( -
-
-

- Generating skill variants — each variant is a complete SKILL.md package with scripts, - references, and examples. This typically takes 1-2 minutes per dimension. -

-
- ) : ( - [...variantGroups].reverse().map((g) => { - const isBaseline = g.competitorId === 0; - const labels = ["Baseline (Raw Sonnet)", "Seed (V1)", "Spawn (V2)"]; - return ( - - ); - }) - )} -
+ ) : ( + [...variantGroups].reverse().map((g) => { + const isBaseline = g.competitorId === 0; + const labels = ["Baseline (Raw Sonnet)", "Seed (V1)", "Spawn (V2)"]; + return ( + + ); + }) + )}
- - {showBreeding && ( - - )} - -
- {/* Right column: completed dimensions + judging */} -
- {/* Completed dimensions summary */} - {completedDims > 0 && ( -
-

- Completed Dimensions -

-
- {effectiveDimensions - .filter((d) => d.status === "complete") - .map((d) => ( -
- - {d.dimension.replace(/-/g, " ")} - - - {d.fitness_score?.toFixed(2) ?? "—"} - -
- ))} -
-
-
- - Avg Fitness - - - {( - effectiveDimensions - .filter((d) => d.status === "complete" && d.fitness_score != null) - .reduce((sum, d) => sum + (d.fitness_score ?? 0), 0) / - Math.max(completedDims, 1) - ).toFixed(3)} - -
-
-
- )} + {showBreeding && ( + + )} + + +
- {/* Composite Scoring */} + {/* Right column: completed dimensions + judging */} +
+ {/* Completed dimensions summary */} + {completedDims > 0 && (

- Composite Scoring -

-

- Each variant is scored through 6 layers, weighted into one composite fitness. + Completed Dimensions

- {[ - { label: "Behavioral Tests", weight: "40%", desc: "ExUnit — does the code work?" }, - { label: "Compilation", weight: "15%", desc: "mix compile — does it build?" }, - { label: "AST Quality", weight: "15%", desc: "Structure, coverage, pipes" }, - { label: "String Match", weight: "10%", desc: "L0 expected patterns" }, - { label: "Template", weight: "10%", desc: "Modern HEEx idioms" }, - { label: "Brevity", weight: "10%", desc: "Conciseness" }, - ].map((layer) => ( -
- - {layer.weight} - - {layer.label} - {layer.desc} -
- ))} + {effectiveDimensions + .filter((d) => d.status === "complete") + .map((d) => ( +
+ + {d.dimension.replace(/-/g, " ")} + + + {d.fitness_score?.toFixed(2) ?? "—"} + +
+ ))}
- {activeDimension && sockState.generations.at(-1)?.best_fitness != null && ( -
-
- - Best This Dimension - - - {sockState.generations.at(-1)!.best_fitness!.toFixed(3)} - -
+
+
+ + Avg Fitness + + + {( + effectiveDimensions + .filter((d) => d.status === "complete" && d.fitness_score != null) + .reduce((sum, d) => sum + (d.fitness_score ?? 0), 0) / + Math.max(completedDims, 1) + ).toFixed(3)} +
- )} +
+ )} - {/* Baseline Context */} -
-

- Baseline — Raw Sonnet -

-

- What Claude Sonnet scores with no skill on the same challenges. - The goal: evolved skill consistently beats baseline. -

- {benchBaseline ? ( -
-
- Raw Composite - - {benchBaseline.rawComposite?.toFixed(3) ?? "—"} - -
-
- Challenges - - {benchBaseline.challenges} - -
-
- Families - - {benchBaseline.families} - -
- {runDetail?.baseline_fitness != null && ( -
- This Family Baseline - - {runDetail.baseline_fitness.toFixed(3)} - -
- )} + {/* Composite Scoring */} +
+

+ Composite Scoring +

+

+ Each variant is scored through 6 layers, weighted into one composite fitness. +

+
+ {[ + { + label: "Behavioral Tests", + weight: "40%", + desc: "ExUnit — does the code work?", + }, + { label: "Compilation", weight: "15%", desc: "mix compile — does it build?" }, + { label: "AST Quality", weight: "15%", desc: "Structure, coverage, pipes" }, + { label: "String Match", weight: "10%", desc: "L0 expected patterns" }, + { label: "Template", weight: "10%", desc: "Modern HEEx idioms" }, + { label: "Brevity", weight: "10%", desc: "Conciseness" }, + ].map((layer) => ( +
+ + {layer.weight} + + {layer.label} + + {layer.desc} +
- ) : ( -

Loading baseline data...

- )} + ))}
+ {activeDimension && sockState.generations.at(-1)?.best_fitness != null && ( +
+
+ + Best This Dimension + + + {sockState.generations.at(-1)!.best_fitness!.toFixed(3)} + +
+
+ )} +
- {/* Per-dimension pipeline steps */} -
-

- Per-Dimension Pipeline -

-
- {[ - { step: "1", label: "Design focused challenge", done: challenges.length > 0 }, - { step: "2", label: "Spawn seed + alternative", done: variantGroups.length >= 2 }, - { step: "3", label: "Compete on challenge", done: sockState.finishedCompetitors >= 2 }, - { step: "4", label: "Score (6-layer composite)", done: sockState.currentJudgingLayer >= 5 }, - { step: "5", label: "Pick winner", done: sockState.generations.at(-1)?.status === "complete" }, - ].map((s) => ( -
- - {s.done ? "✓" : s.step} - - - {s.label} + {/* Baseline Context */} +
+

+ Baseline — Raw Sonnet +

+

+ What Claude Sonnet scores with no skill on the same challenges. The goal: evolved + skill consistently beats baseline. +

+ {benchBaseline ? ( +
+
+ Raw Composite + + {benchBaseline.rawComposite?.toFixed(3) ?? "—"} + +
+
+ Challenges + + {benchBaseline.challenges} + +
+
+ Families + + {benchBaseline.families} + +
+ {runDetail?.baseline_fitness != null && ( +
+ This Family Baseline + + {runDetail.baseline_fitness.toFixed(3)}
- ))} + )}
+ ) : ( +

Loading baseline data...

+ )} +
+ + {/* Per-dimension pipeline steps */} +
+

+ Per-Dimension Pipeline +

+
+ {[ + { step: "1", label: "Design focused challenge", done: challenges.length > 0 }, + { step: "2", label: "Spawn seed + alternative", done: variantGroups.length >= 2 }, + { + step: "3", + label: "Compete on challenge", + done: sockState.finishedCompetitors >= 2, + }, + { + step: "4", + label: "Score (6-layer composite)", + done: sockState.currentJudgingLayer >= 5, + }, + { + step: "5", + label: "Pick winner", + done: sockState.generations.at(-1)?.status === "complete", + }, + ].map((s) => ( +
+ + {s.done ? "✓" : s.step} + + + {s.label} + +
+ ))}
- ); +
+ ); } diff --git a/frontend/src/components/EvolutionCard.tsx b/frontend/src/components/EvolutionCard.tsx index ddc9fbf..dbcfa2a 100644 --- a/frontend/src/components/EvolutionCard.tsx +++ b/frontend/src/components/EvolutionCard.tsx @@ -18,10 +18,7 @@ const STATUS_LABEL: Record = { failed: "FAILED", }; -const STATUS_VARIANT: Record< - RunStatus, - "running" | "success" | "error" | "neutral" -> = { +const STATUS_VARIANT: Record = { pending: "neutral", running: "running", complete: "success", @@ -47,9 +44,7 @@ export default function EvolutionCard({ {STATUS_LABEL[status]}
- - {id.slice(0, 8)} - + {id.slice(0, 8)}

diff --git a/frontend/src/components/EvolutionDashboard.tsx b/frontend/src/components/EvolutionDashboard.tsx index 34b4b42..6049317 100644 --- a/frontend/src/components/EvolutionDashboard.tsx +++ b/frontend/src/components/EvolutionDashboard.tsx @@ -69,9 +69,7 @@ export default function EvolutionDashboard() { const completed = runs?.filter((r) => r.status === "complete") ?? []; const avgFitness = completed.length > 0 - ? completed - .map((r) => r.best_fitness ?? 0) - .reduce((a, b) => a + b, 0) / completed.length + ? completed.map((r) => r.best_fitness ?? 0).reduce((a, b) => a + b, 0) / completed.length : 0; const totalSpent = (runs ?? []).reduce((sum, r) => sum + r.total_cost_usd, 0); @@ -88,13 +86,11 @@ export default function EvolutionDashboard() {

Evolve Agent Skills
- Through{" "} - Natural Selection + Through Natural Selection

- Deploy autonomous populations into adversarial environments. - Watch them compete, mutate, and survive to forge the ultimate - cognitive skillsets. + Deploy autonomous populations into adversarial environments. Watch them compete, + mutate, and survive to forge the ultimate cognitive skillsets.

@@ -117,28 +113,16 @@ export default function EvolutionDashboard() {

- - Bench Challenges - - - 867 - + Bench Challenges + 867
- - Skill Families - - - 7 - + Skill Families + 7
- - Scoring Layers - - - 6 - + Scoring Layers + 6
Your Runs @@ -180,11 +164,9 @@ export default function EvolutionDashboard() { Backed by Research

- SKLD didn't emerge from a vacuum. The techniques below come from - active ML and applied-AI research; SKLD composes them for a - specific artifact (Claude Agent Skills) with a controlled - benchmark. Each source is mapped to what we took and what we - didn't. + SKLD didn't emerge from a vacuum. The techniques below come from active ML and + applied-AI research; SKLD composes them for a specific artifact (Claude Agent Skills) + with a controlled benchmark. Each source is mapped to what we took and what we didn't.

- Proved population-based evolution of natural-language prompts - outperforms hand-tuning. SKLD's evolution loop skeleton. + Proved population-based evolution of natural-language prompts outperforms hand-tuning. + SKLD's evolution loop skeleton.

Guo et al. · arXiv:2309.08532 ↗ @@ -232,9 +214,8 @@ export default function EvolutionDashboard() { Reflective mutation + Pareto selection

- Trace-informed diagnosis before mutation, multi-objective - Pareto fronts. The two techniques that make evolution - intelligent instead of a random walk. + Trace-informed diagnosis before mutation, multi-objective Pareto fronts. The two + techniques that make evolution intelligent instead of a random walk.

Agrawal et al. · github.com/gepa-ai ↗ @@ -255,9 +236,8 @@ export default function EvolutionDashboard() { Joint multi-component optimization

- Mutating one agent component (a prompt, a tool, a parameter) - often requires matching changes elsewhere. SKLD applies this - to the interdependent parts of a SKILL.md. + Mutating one agent component (a prompt, a tool, a parameter) often requires matching + changes elsewhere. SKLD applies this to the interdependent parts of a SKILL.md.

TurinTech · arXiv:2512.09108 ↗ @@ -278,9 +258,8 @@ export default function EvolutionDashboard() { Persistent learning log + multi-parent crossover

- Accumulated lessons inject into every mutation prompt, so the - population never re-discovers failures it already explored. - SKLD promotes these to the public Bible. + Accumulated lessons inject into every mutation prompt, so the population never + re-discovers failures it already explored. SKLD promotes these to the public Bible.

Imbue Research · imbue.com ↗ @@ -301,9 +280,9 @@ export default function EvolutionDashboard() { Trigger accuracy + A/B comparator

- Skills have two reliability problems: activation (does it - trigger on the right queries?) and execution (does it produce - good output?). SKLD's L2 and L4 layers borrow this directly. + Skills have two reliability problems: activation (does it trigger on the right + queries?) and execution (does it produce good output?). SKLD's L2 and L4 layers borrow + this directly.

Anthropic · claude.com/blog ↗ @@ -324,9 +303,8 @@ export default function EvolutionDashboard() { Trace-based behavioral verification

- You can't assert output == expected for LLM - behavior. Execution traces answer what Claude did, - which is what SKLD's L3 layer scores against. + You can't assert output == expected for LLM behavior. Execution traces + answer what Claude did, which is what SKLD's L3 layer scores against.

MLflow · mlflow.org/blog ↗ @@ -352,9 +330,7 @@ export default function EvolutionDashboard() {

-

- Recent Evolutions -

+

Recent Evolutions

Completed Runs

diff --git a/frontend/src/components/EvolutionResults.tsx b/frontend/src/components/EvolutionResults.tsx index 6b89d59..7e490bc 100644 --- a/frontend/src/components/EvolutionResults.tsx +++ b/frontend/src/components/EvolutionResults.tsx @@ -15,11 +15,7 @@ interface EvolutionResultsProps { runDetail: RunDetail | null; } -export default function EvolutionResults({ - runId, - sockState, - runDetail, -}: EvolutionResultsProps) { +export default function EvolutionResults({ runId, sockState, runDetail }: EvolutionResultsProps) { const finalGen = sockState.generations.at(-1); const bestFitness = finalGen?.best_fitness ?? runDetail?.best_fitness ?? 0; @@ -153,9 +149,7 @@ export default function EvolutionResults({ ↓ Export Agent SDK Config - - Open Export Preview → - + Open Export Preview → - - {isOpen ? "▾" : "▸"} - + {isOpen ? "▾" : "▸"} 📁 {node.name}/ @@ -94,19 +92,18 @@ function TreeNodeRow({ className={`flex w-full items-center gap-1 rounded px-1 py-1 text-left font-mono text-[0.6875rem] transition-colors ${ isSelected ? "bg-primary/10 text-primary" - : "text-on-surface-dim hover:text-on-surface hover:bg-surface-container-mid" + : "hover:bg-surface-container-mid text-on-surface-dim hover:text-on-surface" }`} style={{ paddingLeft: `${indent + 4}px` }} title={node.path} > - - {fileIcon(node.name)} - + {fileIcon(node.name)} {node.name} )} - {node.isDir && isOpen && + {node.isDir && + isOpen && node.children.map((child) => ( - - + +
diff --git a/frontend/src/components/FitnessExplainer.tsx b/frontend/src/components/FitnessExplainer.tsx index e5bc944..87e8ee7 100644 --- a/frontend/src/components/FitnessExplainer.tsx +++ b/frontend/src/components/FitnessExplainer.tsx @@ -11,24 +11,22 @@ export default function FitnessExplainer() { What does "fitness" mean?

- Fitness is the average of the L1 - deterministic checks across all sampled challenges for a - variant. Each challenge defines a list of required substrings - (must_contain) and forbidden ones - (must_not_contain); the score is the fraction of checks - that pass, weighted by the challenge's own weight. + Fitness is the average of the{" "} + L1 deterministic checks across all sampled + challenges for a variant. Each challenge defines a list of required substrings ( + must_contain) and forbidden ones (must_not_contain); the score is + the fraction of checks that pass, weighted by the challenge's own weight.

- 1.0 = every required - substring was present and zero forbidden substrings appeared.{" "} - 0.5 = half the checks - passed. The composite skill's fitness is the average across all 12 - dimension winners. + 1.0 = every required substring was present and + zero forbidden substrings appeared. 0.5 = half + the checks passed. The composite skill's fitness is the average across all 12 dimension + winners.

- Higher reviewer layers — trigger accuracy (L2), trace analysis (L3), - comparative pairwise (L4), trait attribution (L5) — are part of the - production pipeline but were not run for this showcase. + Higher reviewer layers — trigger accuracy (L2), trace analysis (L3), comparative pairwise + (L4), trait attribution (L5) — are part of the production pipeline but were not run for this + showcase.

); diff --git a/frontend/src/components/FitnessRadar.tsx b/frontend/src/components/FitnessRadar.tsx index e3d37aa..9b1fc00 100644 --- a/frontend/src/components/FitnessRadar.tsx +++ b/frontend/src/components/FitnessRadar.tsx @@ -1,10 +1,4 @@ -import { - PolarAngleAxis, - PolarGrid, - Radar, - RadarChart, - ResponsiveContainer, -} from "recharts"; +import { PolarAngleAxis, PolarGrid, Radar, RadarChart, ResponsiveContainer } from "recharts"; import { useCssVar } from "../hooks/useCssVar"; @@ -35,17 +29,8 @@ export default function FitnessRadar({ objectives }: FitnessRadarProps) { - - + +
diff --git a/frontend/src/components/InviteGate.tsx b/frontend/src/components/InviteGate.tsx index d3026a4..eff1116 100644 --- a/frontend/src/components/InviteGate.tsx +++ b/frontend/src/components/InviteGate.tsx @@ -141,13 +141,10 @@ export default function InviteGate({ onValidated }: InviteGateProps) {

🔒 Invite Only

-

- Start an Evolution Run -

+

Start an Evolution Run

- Real evolution runs consume API budget, so SKLD.run is currently - invite-only. Enter your code below, or request an invite and we'll - email you one. + Real evolution runs consume API budget, so SKLD.run is currently invite-only. Enter your + code below, or request an invite and we'll email you one.

{/* Tabs */} @@ -194,9 +191,7 @@ export default function InviteGate({ onValidated }: InviteGateProps) { autoFocus className="mt-2 w-full rounded-lg border border-outline-variant bg-surface-container-low px-4 py-2.5 font-mono text-sm text-on-surface placeholder:text-on-surface-dim/60 focus:border-primary focus:outline-none" /> - {codeError && ( -

{codeError}

- )} + {codeError &&

{codeError}

}
@@ -263,12 +257,10 @@ export default function InviteGate({ onValidated }: InviteGateProps) { className="mt-2 w-full rounded-lg border border-outline-variant bg-surface-container-low px-4 py-2.5 text-sm text-on-surface placeholder:text-on-surface-dim/60 focus:border-primary focus:outline-none" />
- {requestError && ( -

{requestError}

- )} + {requestError &&

{requestError}

}

- Submitting does not automatically grant access. We'll review - and email a code if we can onboard you. + Submitting does not automatically grant access. We'll review and email a code if we + can onboard you.

- {error && ( -
- {error} -
- )} + {error &&
{error}
} {/* Mobile entry selector — visible below lg */}
@@ -143,9 +139,7 @@ export default function JournalBrowser() { #{String(entry.number).padStart(3, "0")} - - {entry.title} - + {entry.title}
{entry.date && (

@@ -183,9 +177,7 @@ export default function JournalBrowser() { #{String(entry.number).padStart(3, "0")} - - {entry.title} - + {entry.title}

{entry.date && (

@@ -208,9 +200,7 @@ export default function JournalBrowser() { Entry #{String(selected.number).padStart(3, "0")}

{selected.date && ( -

- {selected.date} -

+

{selected.date}

)} {selected.duration && (

@@ -219,9 +209,7 @@ export default function JournalBrowser() { )}

- - {selected.body} - + {selected.body}
{/* Prev / Next navigation */}
@@ -231,26 +219,32 @@ export default function JournalBrowser() { className="group flex items-center gap-2 text-sm text-on-surface-dim transition-colors hover:text-on-surface" > - #{String(entries![selectedIndex - 1].number).padStart(3, "0")} + + #{String(entries![selectedIndex - 1].number).padStart(3, "0")} + {entries![selectedIndex - 1].title} - ) :
} + ) : ( +
+ )} {hasNext ? ( - ) :
} + ) : ( +
+ )}
) : ( -

- Select an entry from the left. -

+

Select an entry from the left.

)}
diff --git a/frontend/src/components/JudgingPipelinePill.tsx b/frontend/src/components/JudgingPipelinePill.tsx index 965eef7..5e383c7 100644 --- a/frontend/src/components/JudgingPipelinePill.tsx +++ b/frontend/src/components/JudgingPipelinePill.tsx @@ -12,11 +12,7 @@ const STATUS_CLASS: Record = { complete: "bg-tertiary/15 text-tertiary", }; -export default function JudgingPipelinePill({ - layer, - label, - status, -}: JudgingPipelinePillProps) { +export default function JudgingPipelinePill({ layer, label, status }: JudgingPipelinePillProps) { return (
-
+
{events.length === 0 ? (

- + Waiting for events from the engine...

) : ( events.map((ev, i) => ( -
+
{ev.event.toUpperCase()}: - - {formatEvent(ev)} - + {formatEvent(ev)}
)) )} diff --git a/frontend/src/components/ModeCard.tsx b/frontend/src/components/ModeCard.tsx index a89ddfa..92b402d 100644 --- a/frontend/src/components/ModeCard.tsx +++ b/frontend/src/components/ModeCard.tsx @@ -40,13 +40,9 @@ export default function ModeCard({
{icon}
-

- {title} -

+

{title}

{description}

- {selected && ( - - )} + {selected && } ); } diff --git a/frontend/src/components/OverallAssessment.tsx b/frontend/src/components/OverallAssessment.tsx index 32c50d3..b101038 100644 --- a/frontend/src/components/OverallAssessment.tsx +++ b/frontend/src/components/OverallAssessment.tsx @@ -37,53 +37,40 @@ export default function OverallAssessment({

- What this is. A - production-ready Claude Agent Skill for writing{" "} - {family}. It enforces a - component-forward architecture — parent LiveView as a thin - coordinator, LiveComponents own stateful regions — covers the{" "} - {numDims - 1} most important capability areas (HEEx templates, - function components, streams, forms, mount lifecycle, event - handlers, PubSub, navigation, auth) and ships with a 14-rule - anti-patterns catalog that includes mechanical grep-based - detectors a reviewer can run directly. + What this is. A production-ready Claude Agent + Skill for writing {family}. It enforces a + component-forward architecture — parent LiveView as a thin coordinator, LiveComponents own + stateful regions — covers the {numDims - 1} most important capability areas (HEEx + templates, function components, streams, forms, mount lifecycle, event handlers, PubSub, + navigation, auth) and ships with a 14-rule anti-patterns catalog that includes mechanical + grep-based detectors a reviewer can run directly.

- How it was built.{" "} - {numDims} dimensions of the skill were evolved in parallel. For - each, a pre-existing seed variant competed against a freshly-spawned - alternative on 2 sampled challenges. The higher-scoring variant - became the dimension's winner. An Engineer then assembled the{" "} - {numDims} winners into one coherent composite, resolving{" "} - 3 conflicts between capability stances (mount vs. - async-load discipline, event funnel vs. PubSub direct handling, - composite granularity). The final composite weighs ~20KB of - Markdown across {numDims} named sections plus the detector - catalog. + How it was built. {numDims} dimensions of the + skill were evolved in parallel. For each, a pre-existing seed variant competed against a + freshly-spawned alternative on 2 sampled challenges. The higher-scoring variant became the + dimension's winner. An Engineer then assembled the {numDims} winners into one coherent + composite, resolving 3 conflicts between capability stances (mount vs. + async-load discipline, event funnel vs. PubSub direct handling, composite granularity). + The final composite weighs ~20KB of Markdown across {numDims} named sections plus the + detector catalog.

Quality signal.{" "} - {fitness.toFixed(3)}{" "} - average fitness across{" "} - {numChallenges * 2} test runs - (L1 deterministic checks — required-substring presence + - forbidden-substring absence).{" "} - {perfectFitnessCount} of{" "} - {numDims - 1} capability dimensions hit a perfect 1.000. The{" "} - {seedWinnerCount} dimensions - where the pre-existing seed beat the spawned alternative - (heex-and-verified-routes, streams-and-collections, - navigation-patterns) flag weak scorer discrimination on those - facets — the production pipeline would layer L2 trigger accuracy, - L3 trace analysis, and L4 comparative pairwise review on top to - get richer signal. Bottom line:{" "} - - publishable as a showcase - - ; {spawnWinnerCount}/{numDims} wins for the Spawner are a healthy - signal that atomic evolution isn't just elitism. + {fitness.toFixed(3)} average fitness across{" "} + {numChallenges * 2} test runs (L1 deterministic checks + — required-substring presence + forbidden-substring absence).{" "} + {perfectFitnessCount} of {numDims - 1} capability + dimensions hit a perfect 1.000. The {seedWinnerCount}{" "} + dimensions where the pre-existing seed beat the spawned alternative + (heex-and-verified-routes, streams-and-collections, navigation-patterns) flag weak scorer + discrimination on those facets — the production pipeline would layer L2 trigger accuracy, + L3 trace analysis, and L4 comparative pairwise review on top to get richer signal. Bottom + line: publishable as a showcase;{" "} + {spawnWinnerCount}/{numDims} wins for the Spawner are a healthy signal that atomic + evolution isn't just elitism.

diff --git a/frontend/src/components/PackageExplorer.tsx b/frontend/src/components/PackageExplorer.tsx index 387ac53..e838ac2 100644 --- a/frontend/src/components/PackageExplorer.tsx +++ b/frontend/src/components/PackageExplorer.tsx @@ -73,9 +73,7 @@ export default function PackageExplorer({ // assets/*) produced by post-assembly enrichment OR by a production // engine that natively generates rich directory packages. Sorted so // directories group together visually. - const composite = genomes.find( - (g) => g.meta_strategy === "engineer_composite", - ); + const composite = genomes.find((g) => g.meta_strategy === "engineer_composite"); const supportingFiles = composite?.supporting_files ?? {}; const sortedPaths = Object.keys(supportingFiles).sort((a, b) => { // Group by top-level directory, then alphabetical within. @@ -94,9 +92,7 @@ export default function PackageExplorer({ } // 3. PACKAGE.md — metadata, synthesized. - const winnerGenomes = genomes.filter( - (g) => g.meta_strategy === "seed_pipeline_winner", - ); + const winnerGenomes = genomes.filter((g) => g.meta_strategy === "seed_pipeline_winner"); meta.push({ path: "_meta/PACKAGE.md", content: buildPackageMd({ @@ -112,9 +108,7 @@ export default function PackageExplorer({ }); // 4. REPORT.md — integration report from learning_log - const reportEntry = learningLog.find((e) => - e.startsWith("[integration_report] "), - ); + const reportEntry = learningLog.find((e) => e.startsWith("[integration_report] ")); if (reportEntry) { meta.push({ path: "_meta/REPORT.md", @@ -146,19 +140,9 @@ export default function PackageExplorer({ } return { installable: inst, metadata: meta }; - }, [ - compositeSkillMd, - genomes, - challenges, - learningLog, - runId, - familyLabel, - ]); - - const allFiles = useMemo( - () => [...installable, ...metadata], - [installable, metadata], - ); + }, [compositeSkillMd, genomes, challenges, learningLog, runId, familyLabel]); + + const allFiles = useMemo(() => [...installable, ...metadata], [installable, metadata]); // SKILL.md is the first installable file and the default selection. const [selectedPath, setSelectedPath] = useState("SKILL.md"); @@ -198,9 +182,7 @@ export default function PackageExplorer({ path: "test_fixtures/", label: "test_fixtures/ (sample input files)", kind: "optional", - present: Array.from(present).some((p) => - p.startsWith("test_fixtures/"), - ), + present: Array.from(present).some((p) => p.startsWith("test_fixtures/")), }, { path: "assets/", @@ -214,9 +196,7 @@ export default function PackageExplorer({ const isMarkdown = selectedFile?.language === "markdown"; const bodyOnly = useMemo(() => { if (!selectedFile || !isMarkdown) return ""; - const m = selectedFile.content.match( - /^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)$/, - ); + const m = selectedFile.content.match(/^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)$/); return m?.[1] ?? selectedFile.content; }, [selectedFile, isMarkdown]); @@ -231,13 +211,11 @@ export default function PackageExplorer({

- {installable.length} installable{" "} - {installable.length === 1 ? "file" : "files"} + {installable.length} installable {installable.length === 1 ? "file" : "files"} {" "} - ship in the downloadable zip and are loaded by Claude at - runtime. {metadata.length} additional metadata files are - preserved for auditing the evolution process but are NOT part - of the deployable package. + ship in the downloadable zip and are loaded by Claude at runtime. {metadata.length}{" "} + additional metadata files are preserved for auditing the evolution process but are NOT + part of the deployable package.

{isMarkdown ? (
- - {bodyOnly} - + {bodyOnly}
) : ( - + )}
@@ -327,10 +300,7 @@ export default function PackageExplorer({

What this package contains vs. the Skill Authoring Constraints in{" "} - - CLAUDE.md - - . + CLAUDE.md.

{checklist.map((item) => ( @@ -345,15 +315,11 @@ export default function PackageExplorer({ > {item.present ? "✓" : "○"} -
-

- {item.label} -

+
+

{item.label}

{item.kind} ·{" "} - {item.present - ? "present in this package" - : "not generated by this run"} + {item.present ? "present in this package" : "not generated by this run"}

@@ -425,9 +391,7 @@ function FileTreeSection({ const grouped = new Map(); const dirNames: string[] = []; for (const f of files) { - const rest = stripPrefix - ? f.path.replace(new RegExp(`^${stripPrefix}`), "") - : f.path; + const rest = stripPrefix ? f.path.replace(new RegExp(`^${stripPrefix}`), "") : f.path; if (!rest.includes("/")) { top.push(f); } else { @@ -481,9 +445,7 @@ function FileTreeSection({ className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left font-mono text-[0.6875rem] text-on-surface-dim transition-colors hover:text-on-surface" style={{ paddingLeft: `${8 + indent * 12}px` }} > - - {isOpen ? "▾" : "▸"} - + {isOpen ? "▾" : "▸"} 📁 {dir}/ · {dirFiles.length} diff --git a/frontend/src/components/ParameterInput.tsx b/frontend/src/components/ParameterInput.tsx index f4d5ed8..160ed9a 100644 --- a/frontend/src/components/ParameterInput.tsx +++ b/frontend/src/components/ParameterInput.tsx @@ -26,8 +26,7 @@ export default function ParameterInput({ if (!Number.isNaN(next)) onChange(next); }; - const accentClass = - accent === "tertiary" ? "text-tertiary" : "text-on-surface"; + const accentClass = accent === "tertiary" ? "text-tertiary" : "text-on-surface"; return (
@@ -35,9 +34,7 @@ export default function ParameterInput({ {label}

- {prefix && ( - {prefix} - )} + {prefix && {prefix}}
diff --git a/frontend/src/components/PerDimensionFitnessBar.tsx b/frontend/src/components/PerDimensionFitnessBar.tsx index 322e7bc..801eccc 100644 --- a/frontend/src/components/PerDimensionFitnessBar.tsx +++ b/frontend/src/components/PerDimensionFitnessBar.tsx @@ -21,13 +21,11 @@ export default function PerDimensionFitnessBar({ seedWinnerDimensions, }: PerDimensionFitnessBarProps) { const rows = useMemo(() => { - return variants - .slice() - .sort((a, b) => { - // Foundation first, then by fitness DESC within each tier. - if (a.tier !== b.tier) return a.tier === "foundation" ? -1 : 1; - return b.fitness_score - a.fitness_score; - }); + return variants.slice().sort((a, b) => { + // Foundation first, then by fitness DESC within each tier. + if (a.tier !== b.tier) return a.tier === "foundation" ? -1 : 1; + return b.fitness_score - a.fitness_score; + }); }, [variants]); if (rows.length === 0) { @@ -53,9 +51,7 @@ export default function PerDimensionFitnessBar({
diff --git a/frontend/src/components/PipelineOverview.tsx b/frontend/src/components/PipelineOverview.tsx index 60cb09b..8c4257b 100644 --- a/frontend/src/components/PipelineOverview.tsx +++ b/frontend/src/components/PipelineOverview.tsx @@ -37,15 +37,10 @@ export default function PipelineOverview({ report }: PipelineOverviewProps) {

This run decomposed the skill into{" "} {winners} dimensions, spawned{" "} - {startingVariants} candidate variants, - competed them on{" "} - {challenges} challenges, and - assembled the winners into{" "} - one composite skill at{" "} - - {fitness.toFixed(2)} average fitness - - . + {startingVariants} candidate variants, competed + them on {challenges} challenges, and assembled + the winners into one composite skill at{" "} + {fitness.toFixed(2)} average fitness.

{/* Mini pipeline diagram */} @@ -56,18 +51,9 @@ export default function PipelineOverview({ report }: PipelineOverviewProps) { caption={`${winners} seeds + ${winners} spawns`} /> - + - +

@@ -102,9 +88,7 @@ function PipelineStage({ label, count, caption, accent }: PipelineStageProps) {

{count}

-

- {caption} -

+

{caption}

); } diff --git a/frontend/src/components/PipelineSteps.tsx b/frontend/src/components/PipelineSteps.tsx index 68fcf39..d2bdac6 100644 --- a/frontend/src/components/PipelineSteps.tsx +++ b/frontend/src/components/PipelineSteps.tsx @@ -17,22 +17,46 @@ function VisualResearch() { {/* nodes */} {[ - [20, 40], [45, 15], [45, 65], [70, 30], [70, 55], - [95, 20], [95, 45], [95, 70], + [20, 40], + [45, 15], + [45, 65], + [70, 30], + [70, 55], + [95, 20], + [95, 45], + [95, 70], ].map(([cx, cy], i) => ( - + ))} {/* edges */} {[ - [20, 40, 45, 15], [20, 40, 45, 65], [45, 15, 70, 30], - [45, 65, 70, 55], [70, 30, 95, 20], [70, 30, 95, 45], - [70, 55, 95, 45], [70, 55, 95, 70], + [20, 40, 45, 15], + [20, 40, 45, 65], + [45, 15, 70, 30], + [45, 65, 70, 55], + [70, 30, 95, 20], + [70, 30, 95, 45], + [70, 55, 95, 45], + [70, 55, 95, 70], ].map(([x1, y1, x2, y2], i) => ( - + ))} ); @@ -44,13 +68,27 @@ function VisualSelect() { return ( {heights.map((h, i) => ( - + ))} - + ); } @@ -59,20 +97,53 @@ function VisualSelect() { function VisualDecompose() { return ( - + {[20, 50, 80].map((x, i) => ( - - + + {[x - 8, x, x + 8].map((cx, j) => ( - - + + ))} @@ -93,14 +164,30 @@ function VisualChallenges() { {tiers.map((tier, ti) => Array.from({ length: tier.count }).map((_, i) => ( - + )), )} {["E", "M", "H", "L"].map((label, i) => ( - {label} + + {label} + ))} ); @@ -117,12 +204,31 @@ function VisualBaseline() { {bars.map((bar, i) => ( - {bar.label} - - + + {bar.label} + + + {bar.w}% @@ -145,12 +251,24 @@ function VisualSeed() { {files.map((f, i) => ( - - + {f.name} + fontSize="6" + fontFamily="monospace" + > + {f.name} + ))} @@ -163,17 +281,37 @@ function VisualSpawn() { {/* center seed */} - S + + S + {/* branch lines + variant dots */} {[15, 30, 45, 60].map((y, i) => ( - + - + ))} @@ -185,18 +323,56 @@ function VisualCompete() { return ( {/* left variant */} - - V1 + + + V1 + {/* VS */} - vs + + vs + {/* right variant */} - - V2 + + + V2 + {/* challenge dots */} {[28, 40, 52].map((y, i) => ( @@ -220,10 +396,23 @@ function VisualScore() { {layers.map((l, i) => ( - - + + {l.label} @@ -238,25 +427,59 @@ function VisualBreed() { {/* population dots top */} {[20, 35, 50, 65, 80, 95].map((x, i) => ( - + ))} {/* funnel lines */} - - + + {/* winners bottom */} {[35, 55, 75].map((x, i) => ( - + ))} {/* arrows */} - + - + @@ -270,24 +493,68 @@ function VisualAssemble() { {/* incoming pieces */} {[ - [10, 10], [10, 30], [10, 50], [10, 70], - [35, 10], [35, 30], [35, 50], [35, 70], + [10, 10], + [10, 30], + [10, 50], + [10, 70], + [35, 10], + [35, 30], + [35, 50], + [35, 70], ].map(([x, y], i) => ( - - + + ))} {/* composite result */} - - SKILL - .md + + + SKILL + + + .md + ); } @@ -299,23 +566,41 @@ function VisualShip() { {/* checkmarks */} {Array.from({ length: 7 }).map((_, i) => ( - - + + ))} {/* arrow up */} - - + + {/* Registry label */} - - Registry + + + Registry + ); } @@ -444,21 +729,19 @@ export default function PipelineSteps() { return (
-

- How SKLD Works -

+

How SKLD Works

The Evolution Pipeline

- From ecosystem research to a shipped, tested skill package — every - step backed by measured data from real experiments. + From ecosystem research to a shipped, tested skill package — every step backed by measured + data from real experiments.

{/* Vertical line */} -
+
{STEPS.map((step, i) => { const isVisible = visibleSteps.has(i); @@ -473,9 +756,7 @@ export default function PipelineSteps() { }} data-step-idx={i} className={`relative mb-10 transition-all duration-700 ease-out ${ - isVisible - ? "translate-y-0 opacity-100" - : "translate-y-8 opacity-0" + isVisible ? "translate-y-0 opacity-100" : "translate-y-8 opacity-0" }`} > {/* Timeline dot */} @@ -491,9 +772,7 @@ export default function PipelineSteps() { {/* Content card */}
{String(step.number).padStart(2, "0")} -

- {step.title} -

+

{step.title}

{step.isLoop && ( loop )}
-

- {step.description} -

-

- {step.metric} -

+

{step.description}

+

{step.metric}

{/* Visual illustration */}
diff --git a/frontend/src/components/PrimaryButton.tsx b/frontend/src/components/PrimaryButton.tsx index 555be8c..140be8b 100644 --- a/frontend/src/components/PrimaryButton.tsx +++ b/frontend/src/components/PrimaryButton.tsx @@ -4,18 +4,14 @@ interface PrimaryButtonProps extends ButtonHTMLAttributes { children: ReactNode; } -export default function PrimaryButton({ - children, - className = "", - ...rest -}: PrimaryButtonProps) { +export default function PrimaryButton({ children, className = "", ...rest }: PrimaryButtonProps) { return ( {showReport && ( -
- - {integrationReportMd} - +
+ {integrationReportMd}
)}
diff --git a/frontend/src/components/SeedDetailView.tsx b/frontend/src/components/SeedDetailView.tsx index 7cd7ddf..b08d471 100644 --- a/frontend/src/components/SeedDetailView.tsx +++ b/frontend/src/components/SeedDetailView.tsx @@ -37,9 +37,7 @@ export default function SeedDetailView() { const files = useMemo(() => { if (!skill) return []; - const list = [ - { path: "SKILL.md", content: skill.skill_md_content || "" }, - ]; + const list = [{ path: "SKILL.md", content: skill.skill_md_content || "" }]; if (skill.supporting_files) { for (const [path, content] of Object.entries(skill.supporting_files)) { list.push({ path, content: content as string }); @@ -55,9 +53,7 @@ export default function SeedDetailView() { if (error) { return (
-
- {error} -
+
{error}
); } @@ -71,15 +67,10 @@ export default function SeedDetailView() { } // Strip YAML frontmatter for the main markdown render - const bodyOnly = skill.skill_md_content.replace( - /^---\n[\s\S]*?\n---\n?/, - "", - ); + const bodyOnly = skill.skill_md_content.replace(/^---\n[\s\S]*?\n---\n?/, ""); // Extract frontmatter values for the sidebar - const frontmatterMatch = skill.skill_md_content.match( - /^---\n([\s\S]*?)\n---/, - ); + const frontmatterMatch = skill.skill_md_content.match(/^---\n([\s\S]*?)\n---/); const frontmatter = frontmatterMatch ? frontmatterMatch[1] : ""; const nameMatch = frontmatter.match(/^name:\s*(.+)$/m); const descMatch = frontmatter.match(/description:\s*>-\n((?:\s+.*\n?)+)/); @@ -124,7 +115,7 @@ export default function SeedDetailView() { {/* File tree sidebar (only when there are supporting files) */} {hasMultipleFiles && (
-

+

Skill Package

+
{hasMultipleFiles && (

{selectedFile} @@ -194,7 +185,7 @@ export default function SeedDetailView() {

↓ Download .zip @@ -202,7 +193,7 @@ export default function SeedDetailView() { href={`/api/runs/${runId}/export?format=skill_md`} target="_blank" rel="noreferrer" - className="block rounded-lg bg-surface-container-mid px-3 py-2 text-center text-xs text-on-surface transition-colors hover:bg-surface-container-high" + className="bg-surface-container-mid block rounded-lg px-3 py-2 text-center text-xs text-on-surface transition-colors hover:bg-surface-container-high" > ↓ Download SKILL.md @@ -210,7 +201,7 @@ export default function SeedDetailView() { href={`/api/runs/${runId}/export?format=agent_sdk_config`} target="_blank" rel="noreferrer" - className="block rounded-lg bg-surface-container-mid px-3 py-2 text-center text-xs text-on-surface transition-colors hover:bg-surface-container-high" + className="bg-surface-container-mid block rounded-lg px-3 py-2 text-center text-xs text-on-surface transition-colors hover:bg-surface-container-high" > ↓ Agent SDK Config diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index c86c6c1..f9b47d3 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -10,12 +10,7 @@ interface SidebarProps { phases: PhaseState[]; } -export default function Sidebar({ - runId, - generation, - totalGenerations, - phases, -}: SidebarProps) { +export default function Sidebar({ runId, generation, totalGenerations, phases }: SidebarProps) { return (
{/* Process flow diagram */} diff --git a/frontend/src/components/SkillContentModal.tsx b/frontend/src/components/SkillContentModal.tsx index e8585ac..ed3c9fa 100644 --- a/frontend/src/components/SkillContentModal.tsx +++ b/frontend/src/components/SkillContentModal.tsx @@ -57,7 +57,10 @@ export default function SkillContentModal({ {traits && traits.length > 0 && (
{traits.map((t) => ( - + {t} ))} @@ -72,7 +75,10 @@ export default function SkillContentModal({

{mutations.map((m) => ( - + {m} ))} @@ -82,9 +88,7 @@ export default function SkillContentModal({ {/* Mutation rationale */} {mutationRationale && ( -

- {mutationRationale} -

+

{mutationRationale}

)} {/* Supporting files list */} @@ -95,15 +99,12 @@ export default function SkillContentModal({

{supportingFiles.map((path) => ( -

+

{path}

))}
-

+

Full package available in the skill registry detail view.

@@ -115,7 +116,11 @@ export default function SkillContentModal({ SKILL.md

{skillMdContent ? ( - + ) : (

Skill content not available (demo run or skill not yet persisted). diff --git a/frontend/src/components/SkillDiffViewer.tsx b/frontend/src/components/SkillDiffViewer.tsx index 90aec2f..e6a1f0e 100644 --- a/frontend/src/components/SkillDiffViewer.tsx +++ b/frontend/src/components/SkillDiffViewer.tsx @@ -131,11 +131,9 @@ export default function SkillDiffViewer() { return diffLines(parent.skill_md_content, child.skill_md_content); }, [parent, child]); - const nodeById = (nodeId: string) => - lineage?.nodes.find((n) => n.id === nodeId); + const nodeById = (nodeId: string) => lineage?.nodes.find((n) => n.id === nodeId); - const currentEdge = - lineage && selectedIdx != null ? lineage.edges[selectedIdx] : null; + const currentEdge = lineage && selectedIdx != null ? lineage.edges[selectedIdx] : null; // Atomic-mode render path: skip the diff machinery entirely and render // the new AtomicLineageView that explains the 12→1 assembly without @@ -152,9 +150,8 @@ export default function SkillDiffViewer() { Lineage Assembly View

- Run {runId?.slice(0, 12)} · this composite was assembled from - many parents rather than mutated from one, so there's no diff - to show — browse each parent below. + Run {runId?.slice(0, 12)} · this composite was assembled from many parents rather than + mutated from one, so there's no diff to show — browse each parent below.

Diff Viewer

- Run {runId?.slice(0, 12)} · pick a parent→child transition to see - what changed. + Run {runId?.slice(0, 12)} · pick a parent→child transition to see what changed.

- {error && ( -
- {error} -
- )} + {error &&
{error}
}

{child.mutation_rationale || ( - - (no rationale recorded) - + (no rationale recorded) )}

{child.mutations.length > 0 && ( @@ -324,9 +307,7 @@ export default function SkillDiffViewer() { {lines .map((line, j) => - j < lines.length - 1 || line - ? prefix + line + "\n" - : "", + j < lines.length - 1 || line ? prefix + line + "\n" : "", ) .join("")} diff --git a/frontend/src/components/SkillExportPreview.tsx b/frontend/src/components/SkillExportPreview.tsx index a237ddb..c284cf1 100644 --- a/frontend/src/components/SkillExportPreview.tsx +++ b/frontend/src/components/SkillExportPreview.tsx @@ -11,7 +11,9 @@ export default function SkillExportPreview() { useEffect(() => { if (!runId) return; - fetch(`/api/runs/${runId}`).then((r) => r.json()).then(setRun); + fetch(`/api/runs/${runId}`) + .then((r) => r.json()) + .then(setRun); fetch(`/api/runs/${runId}/export?format=skill_md`) .then((r) => r.text()) .then(setSkillMd) @@ -44,9 +46,7 @@ export default function SkillExportPreview() {

Skill Directory

-

- Compressed Binary Structure -

+

Compressed Binary Structure

             {skillMd ? skillMd.slice(0, 800) + (skillMd.length > 800 ? "\n..." : "") : "(loading)"}
           
@@ -62,11 +62,11 @@ export default function SkillExportPreview() {

Agent SDK Config

-

- Skill Schema Definition -

+

Skill Schema Definition

-            {sdkConfig ? sdkConfig.slice(0, 800) + (sdkConfig.length > 800 ? "\n..." : "") : "(loading)"}
+            {sdkConfig
+              ? sdkConfig.slice(0, 800) + (sdkConfig.length > 800 ? "\n..." : "")
+              : "(loading)"}
           
-

- {label ?? `Variant ${letter}`} -

+

{label ?? `Variant ${letter}`}

{isControl && ( {controlLabel ?? "Control"} @@ -110,11 +106,7 @@ export default function SkillVariantCard({ {allDone @@ -152,9 +144,7 @@ export default function SkillVariantCard({ style={{ width: `${Math.min(1, s.value) * 100}%` }} />
- - {s.label} - + {s.label} {typeof s.value === "number" ? s.value.toFixed(2) : s.value} @@ -168,7 +158,10 @@ export default function SkillVariantCard({ {firstCompetitor?.traits && firstCompetitor.traits.length > 0 && (
{firstCompetitor.traits.slice(0, 3).map((t) => ( - + {t} ))} @@ -177,7 +170,7 @@ export default function SkillVariantCard({ {/* Mutation rationale */} {!isControl && firstCompetitor?.mutationRationale && ( -

+

{firstCompetitor.mutationRationale}

)} @@ -232,7 +225,8 @@ export default function SkillVariantCard({
{match?.state === "writing" && match.turn != null && ( - Turn {match.turn}{match.lastTool ? ` \u00b7 ${match.lastTool}` : ""} + Turn {match.turn} + {match.lastTool ? ` \u00b7 ${match.lastTool}` : ""} )}
@@ -251,7 +245,8 @@ export default function SkillVariantCard({ > {showCode ? "▾ Hide Output" : "▸ View Output"}{" "} - ({Object.keys(firstCompetitor!.outputFiles!).length} file{Object.keys(firstCompetitor!.outputFiles!).length > 1 ? "s" : ""}) + ({Object.keys(firstCompetitor!.outputFiles!).length} file + {Object.keys(firstCompetitor!.outputFiles!).length > 1 ? "s" : ""}) {showCode && ( @@ -264,7 +259,11 @@ export default function SkillVariantCard({ {content.split("\n").length} lines
- +
))}
diff --git a/frontend/src/components/SkldBench.tsx b/frontend/src/components/SkldBench.tsx index 0a1f2b3..86c0bfc 100644 --- a/frontend/src/components/SkldBench.tsx +++ b/frontend/src/components/SkldBench.tsx @@ -50,10 +50,9 @@ export default function SkldBench() { {overall.challenges} Elixir Challenges

- A controlled evaluation benchmark for measuring whether Claude Agent - Skills actually improve code generation. Each challenge is scored - through multiple layers: string matching, compilation, AST analysis, - and behavioral testing. + A controlled evaluation benchmark for measuring whether Claude Agent Skills actually + improve code generation. Each challenge is scored through multiple layers: string + matching, compilation, AST analysis, and behavioral testing.

@@ -75,15 +74,11 @@ export default function SkldBench() { key={item.label} className="rounded-lg bg-surface-container-lowest p-3 text-center" > -

- {item.weight} -

+

{item.weight}

{item.label}

-

- {item.desc} -

+

{item.desc}

))}
@@ -220,30 +215,23 @@ export default function SkldBench() { : "—"}
- {fam.compile_pct != null - ? `${(fam.compile_pct * 100).toFixed(0)}%` - : "—"} + {fam.compile_pct != null ? `${(fam.compile_pct * 100).toFixed(0)}%` : "—"}
Overall - {overall.challenges} - - {overall.raw_composite?.toFixed(3) ?? "—"} - Overall{overall.challenges}{overall.raw_composite?.toFixed(3) ?? "—"}

- Click a family name to see per-challenge detail, tier breakdowns, and - score distributions. + Click a family name to see per-challenge detail, tier breakdowns, and score distributions.

diff --git a/frontend/src/components/SkldBenchFamily.tsx b/frontend/src/components/SkldBenchFamily.tsx index 0e99b66..0f8e0e7 100644 --- a/frontend/src/components/SkldBenchFamily.tsx +++ b/frontend/src/components/SkldBenchFamily.tsx @@ -63,11 +63,7 @@ export default function SkldBenchFamily() { case "dimension": return a.dimension.localeCompare(b.dimension) * dir; case "compiles": - return ( - (Number(a.raw?.compiles ?? false) - - Number(b.raw?.compiles ?? false)) * - dir - ); + return (Number(a.raw?.compiles ?? false) - Number(b.raw?.compiles ?? false)) * dir; default: return 0; } @@ -107,9 +103,7 @@ export default function SkldBenchFamily() { > ← SKLD-bench -

- {data.label} -

+

{data.label}

{data.total_challenges} challenges scored with composite evaluation

@@ -134,10 +128,7 @@ export default function SkldBenchFamily() { {data.tiers.map((t) => ( - + @@ -200,7 +191,8 @@ export default function SkldBenchFamily() { Score Distribution

- How many challenges scored in each composite range — most challenges cluster around 0.5–0.8 + How many challenges scored in each composite range — most challenges cluster around + 0.5–0.8

{data.histogram.counts.map((count, i) => { @@ -315,9 +307,7 @@ export default function SkldBenchFamily() { const rawComp = c.raw?.composite ?? 0; const skillComp = c.skill?.composite; const lift = - skillComp != null && rawComp > 0 - ? (skillComp - rawComp) / rawComp - : null; + skillComp != null && rawComp > 0 ? (skillComp - rawComp) / rawComp : null; return ( - {lift != null - ? `${lift > 0 ? "+" : ""}${(lift * 100).toFixed(0)}%` - : "—"} + {lift != null ? `${lift > 0 ? "+" : ""}${(lift * 100).toFixed(0)}%` : "—"} ); @@ -390,11 +378,7 @@ function SortHeader({ const arrow = current === sortKey ? (dir === "asc" ? " ↑" : " ↓") : ""; return ( - diff --git a/frontend/src/components/SpecAssistantChat.tsx b/frontend/src/components/SpecAssistantChat.tsx index 592864a..f00fab7 100644 --- a/frontend/src/components/SpecAssistantChat.tsx +++ b/frontend/src/components/SpecAssistantChat.tsx @@ -21,10 +21,7 @@ interface SpecAssistantChatProps { onPackageReady?: (pkg: GeneratedPackage) => void; } -export default function SpecAssistantChat({ - onSpecReady, - onPackageReady, -}: SpecAssistantChatProps) { +export default function SpecAssistantChat({ onSpecReady, onPackageReady }: SpecAssistantChatProps) { const [open, setOpen] = useState(false); const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); @@ -104,10 +101,7 @@ export default function SpecAssistantChat({ message: string; final_spec?: string | null; }; - setMessages((prev) => [ - ...prev, - { role: "assistant", content: data.message }, - ]); + setMessages((prev) => [...prev, { role: "assistant", content: data.message }]); if (data.final_spec) { onSpecReady(data.final_spec); setFinalized(true); @@ -132,10 +126,7 @@ export default function SpecAssistantChat({ const submit = async () => { const text = input.trim(); if (!text || sending || finalized) return; - const next: ChatMessage[] = [ - ...messages, - { role: "user", content: text }, - ]; + const next: ChatMessage[] = [...messages, { role: "user", content: text }]; setMessages(next); setInput(""); await sendMessage(next); @@ -205,18 +196,12 @@ export default function SpecAssistantChat({
{/* Messages */} -
+
{messages.length === 0 && !sending && (

Starting conversation…

)} {messages.map((msg, i) => ( -
+
{error && ( -
- {error} -
+
{error}
)} {/* Package generation status */} diff --git a/frontend/src/components/SpecializationInput.tsx b/frontend/src/components/SpecializationInput.tsx index 1c4c21f..1f2e8cb 100644 --- a/frontend/src/components/SpecializationInput.tsx +++ b/frontend/src/components/SpecializationInput.tsx @@ -57,17 +57,13 @@ export default function SpecializationInput() { const [searchParams] = useSearchParams(); const seedParam = searchParams.get("seed"); - const [sourceMode, setSourceMode] = useState( - seedParam ? "fork" : "scratch", - ); + const [sourceMode, setSourceMode] = useState(seedParam ? "fork" : "scratch"); const [specialization, setSpecialization] = useState(""); const [populationSize, setPopulationSize] = useState(5); const [numGenerations, setNumGenerations] = useState(3); const [budget, setBudget] = useState(10); // v2.0 — Auto lets the Taxonomist decide; Atomic and Classic force the mode. - const [evolutionMode, setEvolutionMode] = useState<"auto" | "atomic" | "molecular">( - "auto", - ); + const [evolutionMode, setEvolutionMode] = useState<"auto" | "atomic" | "molecular">("auto"); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const [upload, setUpload] = useState(null); @@ -107,9 +103,8 @@ export default function SpecializationInput() { : ["all"]; const visibleSeeds = - allSeeds?.filter( - (s) => seedCategoryFilter === "all" || s.category === seedCategoryFilter, - ) ?? []; + allSeeds?.filter((s) => seedCategoryFilter === "all" || s.category === seedCategoryFilter) ?? + []; const submit = async () => { setSubmitting(true); @@ -217,9 +212,7 @@ export default function SpecializationInput() {

Protocol: New Evolution

-

- Start an Evolution Run -

+

Start an Evolution Run

{/* Source mode toggle */}
@@ -297,14 +290,14 @@ export default function SpecializationInput() {

⑂ Forking from

-

- {forkedSeed.title} -

+

{forkedSeed.title}

{forkedSeed.category} - + {forkedSeed.difficulty}

@@ -403,8 +396,8 @@ export default function SpecializationInput() { ✓ Skill Package Ready

- {Object.keys(generatedPackage.supportingFiles).length + 1} files - will be used as the Gen 0 seed + {Object.keys(generatedPackage.supportingFiles).length + 1} files will be used as the + Gen 0 seed

@@ -558,8 +539,7 @@ export default function SpecializationInput() { Competitor Runs

- {competitorRuns} ({populationSize}×{numGenerations}× - {CHALLENGES_PER_GEN}) + {competitorRuns} ({populationSize}×{numGenerations}×{CHALLENGES_PER_GEN})

@@ -569,9 +549,8 @@ export default function SpecializationInput() {
{overBudget && (

- ⚠ Estimated cost exceeds your ${budget} budget cap. The run - will abort when the cap is hit — increase the cap or reduce - population/generations. + ⚠ Estimated cost exceeds your ${budget} budget cap. The run will abort when the cap + is hit — increase the cap or reduce population/generations.

)}
diff --git a/frontend/src/components/StatCard.tsx b/frontend/src/components/StatCard.tsx index 280d441..be4a137 100644 --- a/frontend/src/components/StatCard.tsx +++ b/frontend/src/components/StatCard.tsx @@ -11,29 +11,20 @@ const ACCENT_BORDER: Record, string> = { tertiary: "before:bg-tertiary", }; -export default function StatCard({ - label, - value, - hint, - accent = "primary", -}: StatCardProps) { +export default function StatCard({ label, value, hint, accent = "primary" }: StatCardProps) { return (

{label}

-

- {value} -

- {hint && ( -

{hint}

- )} +

{value}

+ {hint &&

{hint}

}
); } diff --git a/frontend/src/components/StatusGlow.tsx b/frontend/src/components/StatusGlow.tsx index 5c4d711..d4b0097 100644 --- a/frontend/src/components/StatusGlow.tsx +++ b/frontend/src/components/StatusGlow.tsx @@ -18,8 +18,7 @@ export default function StatusGlow({ className = "", pulse, }: StatusGlowProps) { - const shouldPulse = - pulse ?? (variant === "running" || variant === "warning"); + const shouldPulse = pulse ?? (variant === "running" || variant === "warning"); return ( { if (filter.domain_id && f.domain_id !== filter.domain_id) return false; if (filter.focus_id && f.focus_id !== filter.focus_id) return false; @@ -91,10 +88,7 @@ export default function TaxonomyBrowser() { .catch((err) => setError(String(err))); }, []); - const domains = useMemo( - () => (nodes ?? []).filter((n) => n.level === "domain"), - [nodes], - ); + const domains = useMemo(() => (nodes ?? []).filter((n) => n.level === "domain"), [nodes]); const focusesByDomain = useMemo(() => { const map = new Map(); @@ -123,9 +117,7 @@ export default function TaxonomyBrowser() { const isLoading = nodes == null || families == null; const hasNoFilter = - filter.domain_id == null && - filter.focus_id == null && - filter.language_id == null; + filter.domain_id == null && filter.focus_id == null && filter.language_id == null; return (
@@ -138,8 +130,8 @@ export default function TaxonomyBrowser() { Skill Taxonomy

- Browse the Domain → Focus → Language hierarchy. Pick any node to - filter the family list to variants that live underneath it. + Browse the Domain → Focus → Language hierarchy. Pick any node to filter the family list + to variants that live underneath it.

@@ -147,11 +139,7 @@ export default function TaxonomyBrowser() {
- {error && ( -
- {error} -
- )} + {error &&
{error}
} {isLoading ? (

Loading taxonomy…

@@ -173,11 +161,7 @@ export default function TaxonomyBrowser() {
    {domains.map((dom) => { - const domCount = countFamiliesUnderNode( - dom, - nodes ?? [], - families ?? [], - ); + const domCount = countFamiliesUnderNode(dom, nodes ?? [], families ?? []); const isDomActive = filter.domain_id === dom.id; const focuses = focusesByDomain.get(dom.id) ?? []; return ( @@ -205,11 +189,7 @@ export default function TaxonomyBrowser() { {isDomActive && focuses.length > 0 && (
      {focuses.map((foc) => { - const focCount = countFamiliesUnderNode( - foc, - nodes ?? [], - families ?? [], - ); + const focCount = countFamiliesUnderNode(foc, nodes ?? [], families ?? []); const isFocActive = filter.focus_id === foc.id; const languages = languagesByFocus.get(foc.id) ?? []; return ( @@ -242,8 +222,7 @@ export default function TaxonomyBrowser() { nodes ?? [], families ?? [], ); - const isLngActive = - filter.language_id === lng.id; + const isLngActive = filter.language_id === lng.id; return (