From 82ea7578dcd7e7dddbe2b2cb7989830623b70d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Tue, 17 Feb 2026 09:19:42 +0000 Subject: [PATCH] feat: ci complexity linters (#236) --- .github/workflows/backend-tests.yml | 1 + AGENTS.md | 123 ++++-- CLAUDE.md | 1 + apps/backend/.golangci.yml | 50 ++- apps/pnpm-lock.yaml | 184 +++++++- apps/web/app/demo/agent-messages/page.tsx | 498 +++++++++++----------- apps/web/eslint.config.mjs | 20 + apps/web/package.json | 4 +- package-lock.json | 6 - 9 files changed, 599 insertions(+), 288 deletions(-) create mode 120000 CLAUDE.md delete mode 100644 package-lock.json diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index aa00af8ebe..33c6fa6be2 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -51,6 +51,7 @@ jobs: version: v2.9 args: ./... working-directory: apps/backend + only-new-issues: true - name: Run tests run: | diff --git a/AGENTS.md b/AGENTS.md index 8a35deb5db..25367c6e7c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,19 +5,18 @@ ## Repo Layout ``` -kandev-3/ -├── apps/ -│ ├── backend/ # Go backend (orchestrator, lifecycle, agentctl, WS gateway) -│ ├── web/ # Next.js frontend (SSR + WS + Zustand) -│ ├── packages/ # Shared packages/types -│ └── landing/ # Marketing site (shares UI with web) +apps/ +├── backend/ # Go backend (orchestrator, lifecycle, agentctl, WS gateway) +├── web/ # Next.js frontend (SSR + WS + Zustand) +├── cli/ # CLI tool (TypeScript) +└── packages/ # Shared packages/types ``` ## Tooling -- **Package manager**: `pnpm` workspace (root) +- **Package manager**: `pnpm` workspace (run from `apps/`, not repo root) - **Backend**: Go with Make (`make -C apps/backend test|lint|build`) -- **Frontend**: Next.js (`pnpm -C apps/web dev|lint|typecheck`) +- **Frontend**: Next.js (`cd apps && pnpm --filter @kandev/web dev|lint|typecheck`) - **UI**: Shadcn components via `@kandev/ui` --- @@ -30,31 +29,64 @@ kandev-3/ apps/backend/ ├── cmd/ │ ├── kandev/ # Main backend binary entry point -│ └── agentctl/ # Agentctl binary (runs inside containers or standalone) +│ ├── agentctl/ # Agentctl binary (runs inside containers or standalone) +│ └── mock-agent/ # Mock agent for testing ├── internal/ │ ├── agent/ │ │ ├── lifecycle/ # Agent instance management (see below) +│ │ ├── agents/ # Agent type implementations +│ │ ├── controller/ # Agent control operations +│ │ ├── credentials/ # Agent credential management +│ │ ├── discovery/ # Agent discovery +│ │ ├── docker/ # Docker-specific agent logic +│ │ ├── dto/ # Agent data transfer objects +│ │ ├── handlers/ # Agent event handlers │ │ ├── registry/ # Agent type registry and defaults │ │ ├── runtime/ # Runtime name constants +│ │ ├── settings/ # Agent settings │ │ └── mcpconfig/ # MCP server configuration │ ├── agentctl/ │ │ ├── client/ # HTTP client for talking to agentctl │ │ └── server/ # agentctl HTTP server │ │ ├── api/ # HTTP endpoints +│ │ ├── adapter/ # Protocol adapters (ACP, Codex, Copilot, Amp) │ │ ├── instance/ # Multi-instance management +│ │ ├── mcp/ # MCP server integration │ │ ├── process/ # Agent subprocess management -│ │ └── adapter/ # Protocol adapters (ACP, Codex, REST, MCP) +│ │ └── shell/ # Shell session management │ ├── orchestrator/ # Task execution coordination │ │ ├── executor/ # Launches agents via lifecycle manager -│ │ ├── scheduler/ # Task scheduling +│ │ ├── handlers/ # Orchestrator event handlers +│ │ ├── messagequeue/ # Message queue for agent prompts │ │ ├── queue/ # Task queue +│ │ ├── scheduler/ # Task scheduling │ │ └── watcher/ # Event handlers │ ├── task/ +│ │ ├── controller/ # Task HTTP/WS controllers +│ │ ├── dto/ # Task data transfer objects +│ │ ├── events/ # Task event types +│ │ ├── handlers/ # Task event handlers │ │ ├── models/ # Task, Session, Executor, Message models -│ │ ├── service/ # Task business logic -│ │ └── repository/ # Database access (SQLite) -│ ├── worktree/ # Git worktree management for workspace isolation -│ └── events/ # Event bus for internal pub/sub +│ │ ├── repository/ # Database access (SQLite) +│ │ └── service/ # Task business logic +│ ├── analytics/ # Usage analytics +│ ├── clarification/ # Agent clarification handling +│ ├── common/ # Shared utilities +│ ├── db/ # Database initialization +│ ├── debug/ # Debug tooling +│ ├── editors/ # Editor integration +│ ├── events/ # Event bus for internal pub/sub +│ ├── gateway/ # WebSocket gateway +│ ├── integration/ # External integrations +│ ├── lsp/ # LSP server +│ ├── mcp/ # MCP protocol support +│ ├── notifications/ # Notification system +│ ├── persistence/ # Persistence layer +│ ├── prompts/ # Prompt management +│ ├── sysprompt/ # System prompt injection +│ ├── user/ # User management +│ ├── workflow/ # Workflow engine +│ └── worktree/ # Git worktree management for workspace isolation ``` ### Key Concepts @@ -66,13 +98,15 @@ apps/backend/ - Located in `internal/orchestrator/` **Lifecycle Manager** (`internal/agent/lifecycle/`) manages agent instances: -- `Manager` - central coordinator for agent lifecycle -- `Runtime` interface - abstracts execution environment (Docker, Standalone) -- `ExecutionStore` - thread-safe in-memory execution tracking -- `SessionManager` - ACP session initialization and resume -- `StreamManager` - WebSocket stream connections to agentctl -- `EventPublisher` - publishes events to the event bus -- `ContainerManager` - Docker container operations +- `Manager` (`manager.go`) - central coordinator for agent lifecycle +- `Runtime` interface (`runtime.go`) - abstracts execution environment (Docker, Standalone, Remote Docker) +- `ExecutionStore` (`execution_store.go`) - thread-safe in-memory execution tracking +- `session.go` - ACP session initialization and resume +- `streams.go` - WebSocket stream connections to agentctl +- `events.go` - publishes events to the event bus +- `container.go` - Docker container operations +- `process_runner.go` - agent process launch and management +- `profile_resolver.go` - resolves agent profiles/settings **agentctl** is an HTTP server that: - Runs inside Docker containers or as standalone process @@ -148,9 +182,12 @@ Client (WS) Orchestrator Lifecycle Manager Runtime age Protocol adapters normalize different agent CLIs: - `AgentAdapter` interface defines `Start()`, `Stop()`, `Prompt()`, `Cancel()` -- `ACPAdapter` - ACP JSON-RPC over stdio -- `CodexAdapter` - Codex-style JSON-RPC +- `ACP` - ACP JSON-RPC over stdio (Claude Code) +- `Codex` - Codex-style JSON-RPC (OpenAI Codex) +- `CopilotAdapter` - GitHub Copilot SDK +- `AmpAdapter` - Sourcegraph Amp CLI - `process.Manager` owns subprocess, wires stdio to adapter +- Factory pattern in `adapter/factory.go` selects adapter by agent type --- @@ -196,19 +233,22 @@ SSR Fetch -> Hydrate Store -> Components Read Store -> Hooks Subscribe ``` lib/state/ -├── store.ts (~370 lines) # Root composition +├── store.ts # Root composition ├── slices/ # Domain slices │ ├── kanban/ # boards, tasks, columns │ ├── session/ # sessions, messages, turns, worktrees │ ├── session-runtime/ # shell, processes, git, context │ ├── workspace/ # workspaces, repos, branches │ ├── settings/ # executors, agents, editors, prompts +│ ├── diff-comments/ # code review diff comments │ └── ui/ # preview, connection, active state ├── hydration/ # SSR merge strategies -└── selectors/ # Memoized selectors (future) hooks/domains/{kanban,session,workspace,settings}/ # Domain-organized hooks -lib/api/domains/{kanban,session,workspace,settings,process}-api.ts # API clients +lib/api/domains/ # API clients +├── kanban-api, session-api, workspace-api, settings-api, process-api +├── plan-api, queue-api, workflow-api, stats-api +├── user-shell-api, debug-api ``` **Key State Paths:** @@ -222,12 +262,31 @@ lib/api/domains/{kanban,session,workspace,settings,process}-api.ts # API client ### WS -**Format:** `{id, type, action, payload, timestamp}`. See `docs/asyncapi.yaml` for all actions. +**Format:** `{id, type, action, payload, timestamp}`. --- ## Best Practices +### Code Quality (enforced by linters) + +Static analysis runs in CI and pre-commit. New code **must** stay within these limits: + +**Go** (`apps/backend/.golangci.yml` — errors on new code only): +- Functions: **≤80 lines**, **≤50 statements** +- Cyclomatic complexity: **≤15** · Cognitive complexity: **≤30** +- Nesting depth: **≤5** · Naked returns only in functions **≤30 lines** +- No duplicated blocks (**≥150 tokens**) · Repeated strings → constants (**≥3 occurrences**) + +**TypeScript** (`apps/web/eslint.config.mjs` — warnings, will become errors): +- Files: **≤600 lines** · Functions: **≤100 lines** +- Cyclomatic complexity: **≤15** · Cognitive complexity: **≤20** +- Nesting depth: **≤4** · Parameters: **≤5** +- No duplicated strings (**≥4 occurrences**) · No identical functions · No unused imports +- No nested ternaries + +**When you hit a limit:** extract a helper function, custom hook, or sub-component. Prefer composition over growing a single function. + ### Backend - Provider pattern for DI; stderr for logs, stdout for ACP only - Pass context through chains; event bus for cross-component comm @@ -250,4 +309,10 @@ lib/api/domains/{kanban,session,workspace,settings,process}-api.ts # API client --- -**Last Updated**: 2026-02-01 +## Maintaining This File + +This file is read by AI coding agents (Claude Code via `CLAUDE.md` symlink, Codex via `AGENTS.md`). If your changes make any section of this file outdated or inaccurate — e.g., you add/remove/rename packages, change architectural patterns, add new adapters, modify store slices, or change conventions — **update the relevant sections of this file as part of the same PR**. Keep descriptions concise and factual. Do not add speculative or aspirational content. + +--- + +**Last Updated**: 2026-02-17 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/apps/backend/.golangci.yml b/apps/backend/.golangci.yml index 0894db6ef6..55bee3d7f6 100644 --- a/apps/backend/.golangci.yml +++ b/apps/backend/.golangci.yml @@ -1,2 +1,50 @@ version: "2" -# Default golangci-lint configuration (uses built-in defaults). + +linters: + enable: + - funlen + - cyclop + - gocognit + - nestif + - dupl + - goconst + - gocritic + - misspell + - nakedret + - unconvert + - dupword + - maintidx + + settings: + funlen: + lines: 80 + statements: 50 + cyclop: + max-complexity: 15 + gocognit: + min-complexity: 30 + nestif: + min-complexity: 5 + dupl: + threshold: 150 + goconst: + min-occurrences: 3 + min-len: 3 + nakedret: + max-func-lines: 30 + maintidx: + under: 15 + + exclusions: + rules: + - path: _test\.go + linters: + - funlen + - cyclop + - gocognit + - dupl + - goconst + - maintidx + +issues: + new: true diff --git a/apps/pnpm-lock.yaml b/apps/pnpm-lock.yaml index c3dcbd5f3e..4615b67904 100644 --- a/apps/pnpm-lock.yaml +++ b/apps/pnpm-lock.yaml @@ -373,7 +373,7 @@ importers: version: 0.55.1 next: specifier: 16.1.1 - version: 16.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 16.1.1(@babel/core@7.28.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -412,7 +412,7 @@ importers: version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) styled-jsx: specifier: 5.1.6 - version: 5.1.6(react@19.2.3) + version: 5.1.6(@babel/core@7.28.6)(react@19.2.3) tailwind-merge: specifier: ^3.4.0 version: 3.4.0 @@ -453,6 +453,12 @@ importers: eslint-config-next: specifier: 16.1.1 version: 16.1.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-sonarjs: + specifier: ^3.0.7 + version: 3.0.7(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-unused-imports: + specifier: ^4.4.1 + version: 4.4.1(@typescript-eslint/eslint-plugin@8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)) happy-dom: specifier: ^20.1.0 version: 20.1.0 @@ -1157,89 +1163,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1307,6 +1329,10 @@ packages: resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} engines: {node: 20 || >=22} + '@isaacs/brace-expansion@5.0.1': + resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} + engines: {node: 20 || >=22} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -1429,24 +1455,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.1.1': resolution: {integrity: sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.1.1': resolution: {integrity: sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.1.1': resolution: {integrity: sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.1.1': resolution: {integrity: sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==} @@ -2278,66 +2308,79 @@ packages: resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.55.1': resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.55.1': resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.55.1': resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.55.1': resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.55.1': resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.55.1': resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.55.1': resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.55.1': resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.55.1': resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.55.1': resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.55.1': resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.55.1': resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.55.1': resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} @@ -2455,24 +2498,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -3061,41 +3108,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -3301,6 +3356,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -3995,6 +4054,20 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-sonarjs@3.0.7: + resolution: {integrity: sha512-62jB20krIPvcwBLAyG3VVKa2ce2j2lL1yCb8Y0ylMRR/dLvCCTiQx8gQbXb+G81k1alPZ2/I3muZinqWQdBbzw==} + peerDependencies: + eslint: ^8.0.0 || ^9.0.0 + + eslint-plugin-unused-imports@4.4.1: + resolution: {integrity: sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^10.0.0 || ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4187,6 +4260,9 @@ packages: resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} + functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} @@ -4669,6 +4745,10 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsx-ast-utils-x@0.1.0: + resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -4747,24 +4827,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -5042,6 +5126,10 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@10.1.2: + resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5557,6 +5645,10 @@ packages: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + refa@0.12.1: + resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -5570,6 +5662,10 @@ packages: regex@6.1.0: resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + regexp-ast-analysis@0.7.1: + resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -5673,6 +5769,10 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scslre@0.3.0: + resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} + engines: {node: ^14.0.0 || >=16.0.0} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -5682,6 +5782,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + send@1.2.1: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} @@ -7204,6 +7309,10 @@ snapshots: dependencies: '@isaacs/balanced-match': 4.0.1 + '@isaacs/brace-expansion@5.0.1': + dependencies: + '@isaacs/balanced-match': 4.0.1 + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 @@ -9294,6 +9403,8 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) + builtin-modules@3.3.0: {} + bundle-name@4.1.0: dependencies: run-applescript: 7.1.0 @@ -10014,8 +10125,8 @@ snapshots: '@next/eslint-plugin-next': 16.1.1 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1)) @@ -10037,7 +10148,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -10048,22 +10159,22 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -10074,7 +10185,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -10144,6 +10255,26 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-sonarjs@3.0.7(eslint@9.39.2(jiti@2.6.1)): + dependencies: + '@eslint-community/regexpp': 4.12.2 + builtin-modules: 3.3.0 + bytes: 3.1.2 + eslint: 9.39.2(jiti@2.6.1) + functional-red-black-tree: 1.0.1 + jsx-ast-utils-x: 0.1.0 + lodash.merge: 4.6.2 + minimatch: 10.1.2 + scslre: 0.3.0 + semver: 7.7.4 + typescript: 5.9.3 + + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)): + dependencies: + eslint: 9.39.2(jiti@2.6.1) + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -10414,6 +10545,8 @@ snapshots: hasown: 2.0.2 is-callable: 1.2.7 + functional-red-black-tree@1.0.1: {} + functions-have-names@1.2.3: {} fuzzysort@3.1.0: {} @@ -10860,6 +10993,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsx-ast-utils-x@0.1.0: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -11431,6 +11566,10 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@10.1.2: + dependencies: + '@isaacs/brace-expansion': 5.0.1 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -11501,7 +11640,7 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - next@16.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next@16.1.1(@babel/core@7.28.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@next/env': 16.1.1 '@swc/helpers': 0.5.15 @@ -11510,7 +11649,7 @@ snapshots: postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.28.6)(react@19.2.3) optionalDependencies: '@next/swc-darwin-arm64': 16.1.1 '@next/swc-darwin-x64': 16.1.1 @@ -12088,6 +12227,10 @@ snapshots: tiny-invariant: 1.3.3 victory-vendor: 36.9.2 + refa@0.12.1: + dependencies: + '@eslint-community/regexpp': 4.12.2 + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -12109,6 +12252,11 @@ snapshots: dependencies: regex-utilities: 2.3.0 + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.12.2 + refa: 0.12.1 + regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -12272,10 +12420,18 @@ snapshots: scheduler@0.27.0: {} + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.12.2 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + semver@6.3.1: {} semver@7.7.3: {} + semver@7.7.4: {} + send@1.2.1: dependencies: debug: 4.4.3 @@ -12589,10 +12745,12 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(react@19.2.3): + styled-jsx@5.1.6(@babel/core@7.28.6)(react@19.2.3): dependencies: client-only: 0.0.1 react: 19.2.3 + optionalDependencies: + '@babel/core': 7.28.6 stylis@4.3.6: {} diff --git a/apps/web/app/demo/agent-messages/page.tsx b/apps/web/app/demo/agent-messages/page.tsx index ab4e2b6b67..4f8910ac48 100644 --- a/apps/web/app/demo/agent-messages/page.tsx +++ b/apps/web/app/demo/agent-messages/page.tsx @@ -16,11 +16,18 @@ import { IconChevronDown, IconChevronRight, IconRefresh } from '@tabler/icons-re type ToolFilter = 'all' | 'tool_edit' | 'tool_read' | 'tool_execute' | 'tool_call'; type ViewMode = 'fixtures' | 'events'; +const TOOL_TABS: { value: ToolFilter; label: string }[] = [ + { value: 'all', label: 'All' }, + { value: 'tool_edit', label: 'Edit' }, + { value: 'tool_read', label: 'Read' }, + { value: 'tool_execute', label: 'Execute' }, + { value: 'tool_call', label: 'Call' }, +]; + function fixtureToMessage(fixture: NormalizedFixture, index: number): Message { const payload = fixture.payload as Record; const toolType = fixture.tool_type as MessageType; - // Build content based on tool type let content = ''; if (payload.file_path) { content = `${fixture.tool_type}: ${payload.file_path}`; @@ -46,6 +53,102 @@ function fixtureToMessage(fixture: NormalizedFixture, index: number): Message { }; } +// --- Hooks --- + +function useAgentMessages() { + const [viewMode, setViewMode] = useState('events'); + const [fixtureFiles, setFixtureFiles] = useState([]); + const [selectedFixtureFile, setSelectedFixtureFile] = useState(''); + const [fixtures, setFixtures] = useState([]); + const [eventFiles, setEventFiles] = useState([]); + const [selectedEventFile, setSelectedEventFile] = useState(''); + const [messages, setMessages] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [toolFilter, setToolFilter] = useState('all'); + + useEffect(() => { + async function loadFiles() { + try { + const [fixtureFilesData, eventFilesData] = await Promise.all([ + fetchFixtureFiles(), + fetchNormalizedFiles(), + ]); + setFixtureFiles(fixtureFilesData); + setEventFiles(eventFilesData); + if (eventFilesData.length > 0 && !selectedEventFile) { + setSelectedEventFile(eventFilesData[0].path); + } + if (fixtureFilesData.length > 0 && !selectedFixtureFile) { + setSelectedFixtureFile(fixtureFilesData[0].path); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load files'); + } finally { + setLoading(false); + } + } + loadFiles(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const loadFixtures = useCallback(async () => { + if (!selectedFixtureFile) return; + setLoading(true); + setError(null); + try { + const data = await fetchNormalizedMessages(selectedFixtureFile); + setFixtures(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load fixtures'); + } finally { + setLoading(false); + } + }, [selectedFixtureFile]); + + const loadMessages = useCallback(async () => { + if (!selectedEventFile) return; + setLoading(true); + setError(null); + try { + const data = await fetchNormalizedEventsAsMessages(selectedEventFile); + setMessages(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load messages'); + } finally { + setLoading(false); + } + }, [selectedEventFile]); + + useEffect(() => { + if (viewMode === 'fixtures' && selectedFixtureFile) { + loadFixtures(); + } else if (viewMode === 'events' && selectedEventFile) { + loadMessages(); + } + }, [viewMode, selectedFixtureFile, selectedEventFile, loadFixtures, loadMessages]); + + const filteredFixtures = fixtures.filter((f) => toolFilter === 'all' || f.tool_type === toolFilter); + const currentFiles = viewMode === 'fixtures' ? fixtureFiles : eventFiles; + const selectedFile = viewMode === 'fixtures' ? selectedFixtureFile : selectedEventFile; + const setSelectedFile = viewMode === 'fixtures' ? setSelectedFixtureFile : setSelectedEventFile; + const loadContent = viewMode === 'fixtures' ? loadFixtures : loadMessages; + const itemCount = viewMode === 'fixtures' ? filteredFixtures.length : messages.length; + const totalCount = viewMode === 'fixtures' ? fixtures.length : messages.length; + + return { + viewMode, setViewMode, + loading, error, + toolFilter, setToolFilter, + currentFiles, selectedFile, setSelectedFile, + loadContent, + filteredFixtures, messages, + itemCount, totalCount, + }; +} + +// --- UI Components --- + function JsonPanel({ title, data, defaultExpanded = false }: { title: string; data: unknown; defaultExpanded?: boolean }) { const [expanded, setExpanded] = useState(defaultExpanded); @@ -72,7 +175,6 @@ function FixtureCard({ fixture, index }: { fixture: NormalizedFixture; index: nu return (
- {/* Header */}
@@ -86,18 +188,10 @@ function FixtureCard({ fixture, index }: { fixture: NormalizedFixture; index: nu
- - {/* Rendered Message */}
Rendered Output:
- +
- - {/* JSON Panels */}
@@ -114,7 +208,6 @@ function MessageCard({ message }: { message: Message }) { return (
- {/* Header */}
@@ -132,8 +225,6 @@ function MessageCard({ message }: { message: Message }) { )}
- - {/* Rendered Message */}
Rendered Output:
- - {/* JSON Panel */}
@@ -151,126 +240,154 @@ function MessageCard({ message }: { message: Message }) { ); } +function ViewModeTabs({ viewMode, setViewMode }: { viewMode: ViewMode; setViewMode: (v: ViewMode) => void }) { + return ( +
+
+ {(['events', 'fixtures'] as const).map((mode) => ( + + ))} +
+
+ ); +} + +function FiltersBar({ + viewMode, currentFiles, selectedFile, setSelectedFile, toolFilter, setToolFilter, +}: { + viewMode: ViewMode; + currentFiles: DiscoveredFile[]; + selectedFile: string; + setSelectedFile: (v: string) => void; + toolFilter: ToolFilter; + setToolFilter: (v: ToolFilter) => void; +}) { + return ( +
+
+ + {viewMode === 'events' ? 'Event File:' : 'Fixture File:'} + + +
+ {viewMode === 'fixtures' && ( +
+ Tool Type: +
+ {TOOL_TABS.map((tab) => ( + + ))} +
+
+ )} +
+ ); +} + +function ContentArea({ + viewMode, loading, error, itemCount, filteredFixtures, messages, +}: { + viewMode: ViewMode; + loading: boolean; + error: string | null; + itemCount: number; + filteredFixtures: NormalizedFixture[]; + messages: Message[]; +}) { + if (loading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+
Error loading {viewMode}
+
{error}
+
+ ); + } + + if (itemCount === 0) { + return ( +
+ {viewMode === 'events' + ? 'No normalized event files found. Run an agent with KANDEV_DEBUG_AGENT_MESSAGES=1 to generate event logs.' + : 'No fixtures found. Select a fixture file from the dropdown above.'} +
+ ); + } + + if (viewMode === 'fixtures') { + return ( +
+ {filteredFixtures.map((fixture, index) => ( + + ))} +
+ ); + } + + return ( +
+ {messages.map((message, index) => ( + + ))} +
+ ); +} + +// --- Page --- + export default function AgentMessagesPage() { - const [viewMode, setViewMode] = useState('events'); - - // Fixture mode state - const [fixtureFiles, setFixtureFiles] = useState([]); - const [selectedFixtureFile, setSelectedFixtureFile] = useState(''); - const [fixtures, setFixtures] = useState([]); - - // Events mode state - const [eventFiles, setEventFiles] = useState([]); - const [selectedEventFile, setSelectedEventFile] = useState(''); - const [messages, setMessages] = useState([]); - - // Shared state - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [toolFilter, setToolFilter] = useState('all'); - - // Load available files on mount - useEffect(() => { - async function loadFiles() { - try { - // Load both fixture files and normalized event files - const [fixtureFilesData, eventFilesData] = await Promise.all([ - fetchFixtureFiles(), - fetchNormalizedFiles(), - ]); - setFixtureFiles(fixtureFilesData); - setEventFiles(eventFilesData); - - // Auto-select first file based on current mode - if (eventFilesData.length > 0 && !selectedEventFile) { - setSelectedEventFile(eventFilesData[0].path); - } - if (fixtureFilesData.length > 0 && !selectedFixtureFile) { - setSelectedFixtureFile(fixtureFilesData[0].path); - } - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load files'); - } finally { - setLoading(false); - } - } - loadFiles(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const loadFixtures = useCallback(async () => { - if (!selectedFixtureFile) return; - - setLoading(true); - setError(null); - try { - const data = await fetchNormalizedMessages(selectedFixtureFile); - setFixtures(data); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load fixtures'); - } finally { - setLoading(false); - } - }, [selectedFixtureFile]); - - const loadMessages = useCallback(async () => { - if (!selectedEventFile) return; - - setLoading(true); - setError(null); - try { - const data = await fetchNormalizedEventsAsMessages(selectedEventFile); - setMessages(data); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load messages'); - } finally { - setLoading(false); - } - }, [selectedEventFile]); - - // Load content when selected file changes - useEffect(() => { - if (viewMode === 'fixtures' && selectedFixtureFile) { - loadFixtures(); - } else if (viewMode === 'events' && selectedEventFile) { - loadMessages(); - } - }, [viewMode, selectedFixtureFile, selectedEventFile, loadFixtures, loadMessages]); - - const filteredFixtures = fixtures.filter((f) => { - if (toolFilter === 'all') return true; - return f.tool_type === toolFilter; - }); - - const currentFiles = viewMode === 'fixtures' ? fixtureFiles : eventFiles; - const selectedFile = viewMode === 'fixtures' ? selectedFixtureFile : selectedEventFile; - const setSelectedFile = viewMode === 'fixtures' ? setSelectedFixtureFile : setSelectedEventFile; - const loadContent = viewMode === 'fixtures' ? loadFixtures : loadMessages; - - const toolTabs: { value: ToolFilter; label: string }[] = [ - { value: 'all', label: 'All' }, - { value: 'tool_edit', label: 'Edit' }, - { value: 'tool_read', label: 'Read' }, - { value: 'tool_execute', label: 'Execute' }, - { value: 'tool_call', label: 'Call' }, - ]; - - const itemCount = viewMode === 'fixtures' ? filteredFixtures.length : messages.length; - const totalCount = viewMode === 'fixtures' ? fixtures.length : messages.length; + const data = useAgentMessages(); return (
- {/* Header */}

Agent Messages QA

@@ -278,121 +395,26 @@ export default function AgentMessagesPage() { Visual inspection of agent message normalization. Each card shows the rendered output alongside raw JSON.

- - {/* View Mode Tabs */} -
-
- - -
-
- - {/* Filters */} -
- {/* File Selector */} -
- - {viewMode === 'events' ? 'Event File:' : 'Fixture File:'} - - -
- - {/* Tool Filter - only show for fixtures mode */} - {viewMode === 'fixtures' && ( -
- Tool Type: -
- {toolTabs.map((tab) => ( - - ))} -
-
- )} -
- - {/* Stats */} + +
- Showing {itemCount} of {totalCount} {viewMode === 'events' ? 'messages' : 'fixtures'} + Showing {data.itemCount} of {data.totalCount} {data.viewMode === 'events' ? 'messages' : 'fixtures'}
- - {/* Content */} - {loading && ( -
- -
- )} - - {error && ( -
-
Error loading {viewMode}
-
{error}
-
- )} - - {!loading && !error && itemCount === 0 && ( -
- {viewMode === 'events' - ? 'No normalized event files found. Run an agent with KANDEV_DEBUG_AGENT_MESSAGES=1 to generate event logs.' - : 'No fixtures found. Select a fixture file from the dropdown above.'} -
- )} - - {!loading && !error && viewMode === 'fixtures' && filteredFixtures.length > 0 && ( -
- {filteredFixtures.map((fixture, index) => ( - - ))} -
- )} - - {!loading && !error && viewMode === 'events' && messages.length > 0 && ( -
- {messages.map((message, index) => ( - - ))} -
- )} +
); diff --git a/apps/web/eslint.config.mjs b/apps/web/eslint.config.mjs index 05e726d1b4..a31757230f 100644 --- a/apps/web/eslint.config.mjs +++ b/apps/web/eslint.config.mjs @@ -1,6 +1,8 @@ import { defineConfig, globalIgnores } from "eslint/config"; import nextVitals from "eslint-config-next/core-web-vitals"; import nextTs from "eslint-config-next/typescript"; +import sonarjs from "eslint-plugin-sonarjs"; +import unusedImports from "eslint-plugin-unused-imports"; const eslintConfig = defineConfig([ ...nextVitals, @@ -13,6 +15,24 @@ const eslintConfig = defineConfig([ "build/**", "next-env.d.ts", ]), + { + plugins: { + sonarjs, + "unused-imports": unusedImports, + }, + rules: { + "max-lines": ["warn", { max: 600, skipBlankLines: true, skipComments: true }], + "max-lines-per-function": ["warn", { max: 100, skipBlankLines: true, skipComments: true }], + "complexity": ["warn", 15], + "max-depth": ["warn", 4], + "max-params": ["warn", 5], + "no-nested-ternary": "warn", + "sonarjs/cognitive-complexity": ["warn", 20], + "sonarjs/no-duplicate-string": ["warn", { threshold: 4 }], + "sonarjs/no-identical-functions": "warn", + "unused-imports/no-unused-imports": "warn", + }, + }, ]); export default eslintConfig; diff --git a/apps/web/package.json b/apps/web/package.json index 09d6753ebc..68bb170f14 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -107,9 +107,11 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "cross-env": "^7.0.3", "eslint": "^9", "eslint-config-next": "16.1.1", - "cross-env": "^7.0.3", + "eslint-plugin-sonarjs": "^3.0.7", + "eslint-plugin-unused-imports": "^4.4.1", "happy-dom": "^20.1.0", "tailwindcss": "^4", "typescript": "^5", diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 90278e6374..0000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "kandev-3", - "lockfileVersion": 3, - "requires": true, - "packages": {} -}