chore: workspace eslint catalog, typescript dev dep, monorepo skill

- pnpm-workspace catalog: replace oxlint with eslint ^10.4.1.
- Add typescript dev dep (root) so vitest typecheck (stdlib) resolves tsc.
- Root vitest projects: configs/oxlint → configs/eslint.
- Update monorepo skill + add component-test-auditor agent.
This commit is contained in:
2026-06-07 16:28:20 +07:00
parent 7693b49253
commit 70a8678743
5 changed files with 109 additions and 45 deletions
@@ -0,0 +1,71 @@
---
description: "Audit and complete Vue/web component test suites. Use when the user asks to check test coverage, find missing test cases, fill gaps in component tests, verify a11y of a component, audit *.test.ts / a11y.test.ts files, or ensure a component is fully covered by vitest. Triggers: \"check tests\", \"complete tests\", \"test coverage\", \"missing test cases\", \"audit a11y\", \"проверь тесты\", \"допиши тесты\", \"покрытие тестов\"."
name: "Component Test Auditor"
tools: [read, search, edit, execute, todo]
model: "Claude Sonnet 4.5 (copilot)"
argument-hint: "Path to a component or test file to audit"
user-invocable: true
---
You are a specialist in Vue/web component test completeness auditing. Your single job: given a component, determine whether its test suite fully covers behavior, props, emits, slots, edge cases, and accessibility — then fill the gaps with additional vitest specs.
## Constraints
- DO NOT modify production component source. ONLY add or refine test files.
- DO NOT introduce new test frameworks. Use the existing `vitest` + `@vue/test-utils` setup found in the repo.
- DO NOT create redundant tests for behavior already covered. Each new spec must close a concrete gap.
- DO NOT skip running the suite — every change must be verified with vitest before reporting back.
- ONLY audit one component (or a small explicit list) per invocation. Stay focused.
## Required Skills
Always load these skills via `read_file` before acting:
1. `/Users/robonen/.agents/skills/vue-testing-best-practices/SKILL.md` — patterns for Vue component tests.
2. `/Users/robonen/.agents/skills/vitest/SKILL.md` — vitest API, mocking, coverage.
3. `/Users/robonen/.agents/skills/accessibility-a11y/SKILL.md` — WCAG checks, ARIA, keyboard nav. Load whenever the component has interactive behavior, focus management, ARIA attributes, or a sibling `a11y.test.ts` file exists.
4. `/Users/robonen/Projects/tools/.github/skills/monorepo/SKILL.md` — for repo-specific commands (`pnpm`, test scoping, workspace layout).
## Approach
1. **Locate the component.** Resolve the target `.vue` / `.ts` source and its `__test__/` folder. Inspect sibling `*.test.ts`, `a11y.test.ts`, and any AGENTS.md / README.md for the package.
2. **Inventory the surface.** From the component source, enumerate: props (with defaults & types), emits, exposed methods, slots (named + scoped), `v-model`s, internal state machines, conditional branches, lifecycle effects, DOM/ARIA attributes, keyboard handlers.
3. **Inventory existing coverage.** Read all related test files and map each `it(...)` to the surface item it covers. Build a gap table.
4. **Decide on a11y scope.** If the component is interactive (focusable, ARIA roles, keyboard, form-related) → run the a11y skill checklist and ensure an `a11y.test.ts` exists with: role, accessible name, keyboard interaction, focus order, `aria-*` invariants, and (where applicable) `axe`-style assertions consistent with neighboring components.
5. **Write the missing specs.** Match the file/folder conventions used by neighbors (e.g. `src/<comp>/__test__/<Component>.test.ts` + `a11y.test.ts`). Mirror style (mount helpers, fixtures, naming). Keep specs deterministic and isolated.
6. **Run vitest.** Use `runTests` when available, otherwise `pnpm --filter <pkg> test -- <pattern>`. Iterate until green. If a failure reveals a real component bug, STOP and report it — do not silently fix production code.
7. **Optionally measure coverage.** If coverage is requested or unclear, run vitest in coverage mode scoped to the component file and report uncovered lines/branches.
## Output Format
Return a concise markdown report:
```
### Component: <name> (<path>)
**Surface inventory**
- props: ...
- emits: ...
- slots: ...
- interactive/a11y: yes/no — <reason>
**Existing coverage**
- <file>: N specs — covers X, Y
- a11y.test.ts: present/absent
**Gaps closed**
- + <spec name> — <what it covers>
- + <spec name> — <what it covers>
**Files changed**
- <path> (+N specs)
**Verification**
- vitest: PASS (M tests) | coverage: L% lines / B% branches
- a11y skill applied: yes/no
**Follow-ups (not done)**
- <anything out of scope or requiring human decision>
```
If no gaps exist, say so explicitly and do not touch any files.
+30 -40
View File
@@ -1,6 +1,6 @@
--- ---
name: monorepo name: monorepo
description: "Manage the @robonen/tools monorepo. Use when: installing dependencies, creating new packages, linting, building, testing, publishing, or scaffolding workspace packages. Covers pnpm catalogs, tsdown builds, oxlint presets, vitest projects, JSR/NPM publishing." description: "Manage the @robonen/tools monorepo. Use when: installing dependencies, creating new packages, linting, building, testing, publishing, or scaffolding workspace packages. Covers pnpm catalogs, tsdown builds, eslint presets, vitest projects, JSR/NPM publishing."
--- ---
# Monorepo Management # Monorepo Management
@@ -13,7 +13,7 @@ This is a pnpm workspace monorepo (`@robonen/tools`) with shared configs, strict
| Directory | Purpose | Examples | | Directory | Purpose | Examples |
|-----------|---------|---------| |-----------|---------|---------|
| `configs/*` | Shared tooling configs | `oxlint`, `tsconfig`, `tsdown` | | `configs/*` | Shared tooling configs | `eslint`, `tsconfig`, `tsdown` |
| `core/*` | Platform-agnostic TS libraries | `stdlib`, `platform`, `encoding` | | `core/*` | Platform-agnostic TS libraries | `stdlib`, `platform`, `encoding` |
| `vue/*` | Vue 3 packages | `primitives`, `toolkit` | | `vue/*` | Vue 3 packages | `primitives`, `toolkit` |
| `docs` | Nuxt 4 documentation site | — | | `docs` | Nuxt 4 documentation site | — |
@@ -35,7 +35,7 @@ pnpm -C <package-path> add -D <dep-name>
Examples: Examples:
```bash ```bash
pnpm -C core/stdlib add -D oxlint pnpm -C core/stdlib add -D eslint
pnpm -C vue/primitives add vue pnpm -C vue/primitives add vue
``` ```
@@ -47,7 +47,7 @@ Versions shared across multiple packages MUST use the pnpm catalog system. The c
catalog: catalog:
vitest: ^4.0.18 vitest: ^4.0.18
tsdown: ^0.21.0 tsdown: ^0.21.0
oxlint: ^1.2.0 eslint: ^10.4.1
vue: ^3.5.28 vue: ^3.5.28
# ... etc # ... etc
``` ```
@@ -57,7 +57,7 @@ In `package.json`, reference catalog versions with the `catalog:` protocol:
{ {
"devDependencies": { "devDependencies": {
"vitest": "catalog:", "vitest": "catalog:",
"oxlint": "catalog:" "eslint": "catalog:"
} }
} }
``` ```
@@ -72,7 +72,7 @@ Reference sibling packages with the workspace protocol:
```json ```json
{ {
"devDependencies": { "devDependencies": {
"@robonen/oxlint": "workspace:*", "@robonen/eslint": "workspace:*",
"@robonen/tsconfig": "workspace:*", "@robonen/tsconfig": "workspace:*",
"@robonen/tsdown": "workspace:*" "@robonen/tsdown": "workspace:*"
} }
@@ -87,7 +87,7 @@ Always run `pnpm install` at the root after editing `pnpm-workspace.yaml` or any
## Creating a New Package ## Creating a New Package
> **Note:** The existing `bin/cli.ts` (`pnpm create`) is outdated — it generates Vite configs instead of tsdown and lacks oxlint/vitest setup. Follow the manual steps below instead. > **Note:** The existing `bin/cli.ts` (`pnpm create`) is outdated — it generates Vite configs instead of tsdown and lacks eslint/vitest setup. Follow the manual steps below instead.
### 1. Create the directory ### 1. Create the directory
@@ -116,18 +116,17 @@ Choose the correct parent based on package type:
} }
}, },
"scripts": { "scripts": {
"lint:check": "oxlint -c oxlint.config.ts", "lint:check": "eslint .",
"lint:fix": "oxlint -c oxlint.config.ts --fix", "lint:fix": "eslint . --fix",
"test": "vitest run", "test": "vitest run",
"dev": "vitest dev", "dev": "vitest dev",
"build": "tsdown" "build": "tsdown"
}, },
"devDependencies": { "devDependencies": {
"@robonen/oxlint": "workspace:*", "@robonen/eslint": "workspace:*",
"@robonen/tsconfig": "workspace:*", "@robonen/tsconfig": "workspace:*",
"@robonen/tsdown": "workspace:*", "@robonen/tsdown": "workspace:*",
"@stylistic/eslint-plugin": "catalog:", "eslint": "catalog:",
"oxlint": "catalog:",
"tsdown": "catalog:" "tsdown": "catalog:"
} }
} }
@@ -139,7 +138,6 @@ For Vue packages, also add:
"dependencies": { "dependencies": {
"vue": "catalog:", "vue": "catalog:",
"@vue/shared": "catalog:" "@vue/shared": "catalog:"
"@stylistic/eslint-plugin": "catalog:",
}, },
"devDependencies": { "devDependencies": {
"@vue/test-utils": "catalog:" "@vue/test-utils": "catalog:"
@@ -215,16 +213,17 @@ export default defineConfig({
}); });
``` ```
### 5. Create `oxlint.config.ts` ### 5. Create `eslint.config.ts`
Standard (node packages): Standard (node packages):
```typescript ```typescript
import { defineConfig } from 'oxlint'; import { base, compose, imports, stylistic, typescript } from '@robonen/eslint';
import { compose, base, typescript, imports, stylistic } from '@robonen/oxlint';
export default defineConfig(compose(base, typescript, imports, stylistic)); export default compose(base, typescript, imports, stylistic);
``` ```
> ESLint auto-discovers `eslint.config.ts` (loaded via `jiti`, which `@robonen/eslint` depends on). No `defineConfig` wrapper is needed — `compose()` returns the flat-config array directly.
### 6. Create `vitest.config.ts` ### 6. Create `vitest.config.ts`
Node package: Node package:
@@ -292,7 +291,7 @@ pnpm -C <package-path> test
## Linting ## Linting
Uses **oxlint** (not ESLint) with composable presets from `@robonen/oxlint`. Uses **ESLint** (flat config) with composable presets from `@robonen/eslint`.
### Run linting ### Run linting
@@ -314,38 +313,29 @@ pnpm lint:fix
| Preset | Purpose | | Preset | Purpose |
|--------|---------| |--------|---------|
| `base` | ESLint core + Oxc + Unicorn rules | | `base` | ESLint core + Unicorn rules + global ignores |
| `typescript` | TypeScript rules (via overrides on `*.ts` files) | | `typescript` | TypeScript rules (via `typescript-eslint`, on `*.ts` + `*.vue`) |
| `imports` | Import ordering, cycles, duplicates | | `imports` | Import ordering, cycles, duplicates (`eslint-plugin-import-x`) |
| `stylistic` | Code style via `@stylistic/eslint-plugin` | | `stylistic` | Code style via `@stylistic/eslint-plugin` |
| `vue` | Vue 3 Composition API rules | | `vue` | Vue 3 Composition API rules (`eslint-plugin-vue`) |
| `vitest` | Test file rules | | `vitest` | Test file rules (`@vitest/eslint-plugin`) |
| `node` | Node.js-specific rules | | `node` | Node.js-specific rules (`eslint-plugin-n`) |
Compose presets in `oxlint.config.ts`: Compose presets in `eslint.config.ts`:
```typescript ```typescript
import { defineConfig } from 'oxlint'; import { base, compose, imports, typescript } from '@robonen/eslint';
import { compose, base, typescript, imports } from '@robonen/oxlint';
export default defineConfig(compose(base, typescript, imports)); export default compose(base, typescript, imports);
```
**Recommended:** Include `stylistic` preset for code formatting: **Recommended:** Include `stylistic` preset for code formatting:
```typescript ```typescript
import { defineConfig } from 'oxlint'; import { base, compose, imports, stylistic, typescript } from '@robonen/eslint';
import { compose, base, typescript, imports, stylistic } from '@robonen/oxlint';
export default defineConfig(compose(base, typescript, imports, stylistic)); export default compose(base, typescript, imports, stylistic);
``` ```
When using `stylistic`, add `@stylistic/eslint-plugin` to devDependencies: All plugins (including `@stylistic/eslint-plugin`) ship as dependencies of `@robonen/eslint`, so packages only need `@robonen/eslint` + `eslint` in devDependencies.
```json
{
"devDependencies": {
"@stylistic/eslint-plugin": "catalog:"
}
}
```
```
## Building ## Building
+1
View File
@@ -28,6 +28,7 @@
"jiti": "^2.6.1", "jiti": "^2.6.1",
"jsdom": "catalog:", "jsdom": "catalog:",
"scule": "^1.3.0", "scule": "^1.3.0",
"typescript": "^5.9.3",
"vitest": "catalog:" "vitest": "catalog:"
}, },
"scripts": { "scripts": {
+6 -4
View File
@@ -3,19 +3,21 @@ packages:
- core/* - core/*
- infra/* - infra/*
- vue/* - vue/*
- vue/*/playground
- docs - docs
catalog: catalog:
'@stylistic/eslint-plugin': ^5.10.0 '@stylistic/eslint-plugin': ^5.10.0
'@vitest/coverage-v8': ^4.0.18 '@vitest/browser': ^4.1.4
'@vitest/ui': ^4.0.18 '@vitest/coverage-v8': ^4.1.4
'@vitest/ui': ^4.1.4
'@vue/shared': ^3.5.29 '@vue/shared': ^3.5.29
'@vue/test-utils': ^2.4.6 '@vue/test-utils': ^2.4.6
eslint: ^10.4.1
jsdom: ^28.1.0 jsdom: ^28.1.0
nuxt: ^4.3.1 nuxt: ^4.3.1
oxlint: ^1.51.0
tsdown: ^0.21.0 tsdown: ^0.21.0
vitest: ^4.0.18 vitest: ^4.1.4
vue: ^3.5.29 vue: ^3.5.29
ignoredBuiltDependencies: ignoredBuiltDependencies:
+1 -1
View File
@@ -3,7 +3,7 @@ import { defineConfig } from 'vitest/config';
export default defineConfig({ export default defineConfig({
test: { test: {
projects: [ projects: [
'configs/oxlint/vitest.config.ts', 'configs/eslint/vitest.config.ts',
'core/fetch/vitest.config.ts', 'core/fetch/vitest.config.ts',
'core/stdlib/vitest.config.ts', 'core/stdlib/vitest.config.ts',
'core/platform/vitest.config.ts', 'core/platform/vitest.config.ts',