commit 4b24606d0e529c2a33e3ff85585b7d69bc1d528a Author: Harivansh Rathi Date: Sun Jan 11 16:58:40 2026 -0500 iteration 0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8225baa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/dist diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..88a75e8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,52 @@ +# Evaluclaude Harness - Agent Instructions + +## Project Overview + +This is a CLI tool for generating evaluation tests for codebases using Claude. The core philosophy is "Zero-to-evals in one command." + +## Commands + +```bash +# Build the project +npm run build + +# Run typecheck +npm run typecheck + +# Run tests +npm test + +# Run the CLI +npm start -- intro +``` + +## Project Structure + +``` +src/ +├── cli/ # Commander.js CLI +├── introspector/ # Tree-sitter codebase parsing (NO LLM) +│ ├── parsers/ # Language-specific parsers +│ ├── scanner.ts # File discovery +│ ├── git.ts # Git integration +│ └── summarizer.ts # Main analysis logic +└── index.ts # Main exports +``` + +## Key Principles + +1. **Tree-sitter for introspection**: Never send raw code to Claude for structure extraction +2. **Claude generates specs, not code**: EvalSpec JSON is generated by Claude, test code is rendered deterministically +3. **Git-aware incremental**: Only re-analyze changed files + +## Dependencies + +- `tree-sitter`: Native AST parsing +- `tree-sitter-python`: Python grammar +- `tree-sitter-typescript`: TypeScript grammar +- `commander`: CLI framework +- `glob`: File pattern matching + +## Testing + +Use vitest for testing. Test files go in `tests/` directory. diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..9c32a2c --- /dev/null +++ b/PLAN.md @@ -0,0 +1,1036 @@ +# Evaluclaude Harness - Development Plan + +> **Goal**: Zero-to-evals in one command. Install into any codebase, Claude analyzes it, generates real functional tests. + +--- + +## The Vibe + +This isn't just another test generator. It's a **collaborative eval system** where Claude actually understands your codebase and asks smart questions before generating tests. The key insights: + +1. **Claude generates specs, we generate code** — Claude is great at understanding intent, bad at deterministic code gen. We use its strengths. + +2. **Functional tests only** — Every test must invoke actual code. No syntax checks. No "output looks good" vibes. Real assertions that catch real bugs. + +3. **Conversation, not commands** — During init, Claude asks questions like "I see 3 database models. Which is the core one?" This turns a dumb generator into a thinking partner. + +4. **Full observability** — Every eval (deterministic or LLM-graded) has a trace. You can click into it and see exactly what Claude was thinking. + +--- + +## Core Principles (Non-Negotiable) + +These are foundational. Don't compromise on them. + +### 🌳 Tree-Sitter Introspector +Claude should **never see raw code** for structure extraction. Use tree-sitter to parse Python/TypeScript and extract: +- Function signatures +- Class hierarchies +- Import graphs +- Public APIs + +Then send Claude a **structured summary**, not the actual files. This saves tokens, is faster, and more reliable. + +### 🔄 Git-Aware Incremental Generation +- `init` command → Full codebase analysis +- `generate` command → Only analyze git diff since last run + +Don't re-analyze unchanged files. Massive time/cost savings. + +### 🐛 Hooks for Debugging +You WILL need to debug why Claude generated bad specs. Log every tool call: +```typescript +hooks: { + PostToolUse: [{ + hooks: [async (input) => { + await trace.log({ tool: input.tool_name, input: input.tool_input }); + return {}; + }] + }] +} +``` + +### 💬 AskUserQuestion is Gold +During init, Claude should ask clarifying questions: +- "I see 3 database models. Which is the core domain object?" +- "This API has no tests. Should I generate CRUD tests or skip it?" +- "Found a `config.example.py`. Should tests use these values?" +- "There are 47 utility functions. Want me to prioritize the 10 most-used?" + +This transforms eval generation from "spray and pray" to thoughtful, targeted tests. + +### 👁️ Full Observability +Every eval run (deterministic AND LLM-graded) must produce a trace: +- What files Claude read +- What questions it asked +- What specs it generated +- The thinking behind each decision + +In the UI, you click an eval and see the entire reasoning chain. No black boxes. + +### 🔒 Sandbox Mode for Test Execution +Generated tests might do unexpected things. Run them in isolation: +```typescript +sandbox: { + enabled: true, + autoAllowBashIfSandboxed: true, + network: { allowLocalBinding: true } +} +``` + +--- + +## Architecture Overview + +``` +┌──────────────────────────────────────────────────────────────────────────┐ +│ evaluclaude-harness CLI │ +├──────────────────────────────────────────────────────────────────────────┤ +│ init │ generate │ run │ view │ validate │ calibrate │ +└────────────────────────────┬─────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────────────┐ +│ Claude Agent SDK (Local Auth) │ +│ Uses locally authenticated Claude Code instance │ +└────────────────────────────┬─────────────────────────────────────────────┘ + │ + ┌────────────────────┴────────────────────┐ + ▼ ▼ +┌───────────────────┐ ┌───────────────────┐ +│ Analyzer Agent │ │ Grader Agent │ +│ (Spec Generator) │ │ (LLM Rubrics) │ +└─────────┬─────────┘ └─────────┬─────────┘ + │ │ + ▼ ▼ +┌───────────────────┐ ┌───────────────────┐ +│ EvalSpec JSON │ │ Rubrics JSON │ +│ (Deterministic) │ │ (Subjective) │ +└─────────┬─────────┘ └─────────┬─────────┘ + │ │ + ▼ ▼ +┌──────────────────────────────────────────────────────────────────────────┐ +│ Test Renderers │ +│ Python (pytest) │ TypeScript (Vitest/Jest) │ Grader Scripts │ +└────────────────────────────┬─────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────────────┐ +│ Promptfoo │ +│ Orchestration │ Execution │ Results │ Web UI │ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Iteration Areas (Split Work) + +The project has **6 major iteration areas** that can be developed in parallel by different agents/sessions. Each requires significant thought and refinement. + +> **Note for agents**: Each area is self-contained. You can work on one without deep knowledge of the others. The interfaces between them are defined by TypeScript types (EvalSpec, RepoSummary, TraceEvent, etc.). + +--- + +### 0. Tree-Sitter Introspector (`/src/introspector/`) + +**Complexity**: Medium +**Iteration Required**: Moderate +**Priority**: 🔴 FOUNDATIONAL — Build this first + +#### What It Does +Parses Python and TypeScript codebases using tree-sitter to extract structured information WITHOUT using LLM tokens. This is the foundation — Claude never sees raw code for structure extraction. + +#### Key Outputs + +```typescript +interface RepoSummary { + languages: ('python' | 'typescript')[]; + root: string; + + // File inventory (no content, just structure) + files: { + path: string; + lang: 'python' | 'typescript' | 'other'; + role: 'source' | 'test' | 'config' | 'docs'; + size: number; + }[]; + + // Extracted structure (from tree-sitter, NOT from reading files) + modules: { + path: string; + exports: { + name: string; + kind: 'function' | 'class' | 'constant' | 'type'; + signature?: string; // e.g., "(user_id: int, include_deleted: bool = False) -> User" + docstring?: string; // First line only + }[]; + imports: string[]; // What this module depends on + }[]; + + // Config detection + config: { + python?: { + entryPoints: string[]; + testFramework: 'pytest' | 'unittest' | 'none'; + hasTyping: boolean; + }; + typescript?: { + entryPoints: string[]; + testFramework: 'vitest' | 'jest' | 'none'; + hasTypes: boolean; + }; + }; + + // Git info for incremental + git?: { + lastAnalyzedCommit: string; + changedSince: string[]; // Files changed since last analysis + }; +} +``` + +#### Tree-Sitter Integration + +```typescript +import Parser from 'tree-sitter'; +import Python from 'tree-sitter-python'; +import TypeScript from 'tree-sitter-typescript'; + +const parser = new Parser(); +parser.setLanguage(Python); + +const tree = parser.parse(sourceCode); + +// Extract function signatures +const query = new Parser.Query(Python, ` + (function_definition + name: (identifier) @name + parameters: (parameters) @params + return_type: (type)? @return + ) @func +`); + +const matches = query.matches(tree.rootNode); +``` + +#### Git-Aware Incremental + +```typescript +// On `generate` command, only re-analyze changed files +async function getChangedFiles(since: string): Promise { + const result = await exec(`git diff --name-only ${since}`); + return result.stdout.split('\n').filter(f => isSourceFile(f)); +} + +// Skip unchanged modules in RepoSummary +const incrementalSummary = await introspector.analyze({ + onlyFiles: await getChangedFiles(lastCommit) +}); +``` + +#### Iteration Focus +- Parse accuracy across different Python/TS coding styles +- Handle edge cases: decorators, async functions, generics +- Performance on large codebases (>1000 files) +- Incremental updates without full re-parse + +#### Files to Create +``` +src/introspector/ +├── tree-sitter.ts # Core parser wrapper +├── python-parser.ts # Python-specific queries +├── typescript-parser.ts# TS-specific queries +├── git-diff.ts # Incremental change detection +└── summarizer.ts # Combine into RepoSummary +``` + +--- + +### 1. Codebase Analyzer Prompt (`/prompts/analyzer.md`) + +**Complexity**: High +**Iteration Required**: Extensive + +#### What It Does +The core LLM prompt that analyzes any Python/TypeScript codebase and outputs a structured `EvalSpec` JSON. Must be language-agnostic in design but produce language-specific output. + +#### Key Challenges +- Must work across diverse project structures (monorepos, microservices, simple scripts) +- Must identify *functional behaviors* not just code structure +- Must produce deterministic, unambiguous test specifications +- Must avoid proposing tests that depend on network, time, randomness, or external state + +#### EvalSpec Schema (Output Target) + +```typescript +interface EvalSpec { + functional_tests: FunctionalTestSpec[]; + rubric_graders: RubricGraderSpec[]; +} + +interface FunctionalTestSpec { + id: string; + description: string; + target: { + runtime: 'python' | 'typescript'; + module_path: string; // e.g., "src/utils/math.py" + import_path: string; // e.g., "project.utils.math" + callable: string; // function/method name + kind: 'function' | 'method' | 'cli' | 'http_handler'; + }; + setup?: { + env?: Record; + files?: { path: string; content: string }[]; + }; + cases: { + id: string; + description: string; + inputs: unknown; + expected: { + return_value?: unknown; + stdout_contains?: string[]; + stderr_contains?: string[]; + json_schema?: unknown; + raises_exception?: string; + requires_mocks?: boolean; + }; + }[]; +} + +interface RubricGraderSpec { + id: string; + description: string; + target: { + kind: 'cli_output' | 'http_response' | 'error_message' | 'doc_page'; + location: string; + }; + rubric: { + dimensions: { + name: string; + weight: number; + scale: { + min: number; + max: number; + definitions: { score: number; description: string }[]; + }; + }[]; + overall_scoring: 'weighted_average'; + }; +} +``` + +#### Prompt Structure (Multi-Part) + +1. **System Prompt**: Role definition, constraints, JSON-only output +2. **Developer Prompt**: Schema definition, formatting rules +3. **User Prompt**: `RepoSummary` JSON + specific instructions + +#### Iteration Focus +- Test against diverse repos: CLI tools, web apps, libraries, ML projects +- Refine heuristics for identifying "testable" vs "non-testable" code +- Tune specificity of test case generation +- Handle edge cases: no tests exist, tests exist but are bad, etc. + +#### Files to Create +``` +prompts/ +├── analyzer-system.md # Core identity + constraints +├── analyzer-developer.md # Schema + formatting +└── analyzer-user.md # Template for RepoSummary injection +``` + +--- + +### 2. Synchronous Claude Session with Questions (`/src/session/`) + +**Complexity**: Medium-High +**Iteration Required**: Moderate + +#### What It Does +Runs Claude Agent SDK synchronously, handles `AskUserQuestion` tool calls, collects user input via CLI, and returns answers to continue the agent. + +#### Key Technical Details + +**Claude Agent SDK Patterns**: +```typescript +// Using ClaudeSDKClient for multi-turn with questions +import { ClaudeSDKClient, ClaudeAgentOptions } from 'claude-agent-sdk'; + +const options: ClaudeAgentOptions = { + allowed_tools: ['Read', 'Glob', 'Grep', 'AskUserQuestion'], + permission_mode: 'acceptEdits', + can_use_tool: async (toolName, input, context) => { + if (toolName === 'AskUserQuestion') { + // Display questions to user + const answers = await promptUserForAnswers(input.questions); + return { + behavior: 'allow', + updatedInput: { ...input, answers } + }; + } + return { behavior: 'allow', updatedInput: input }; + } +}; +``` + +**Two Operating Modes**: + +| Mode | Behavior | Use Case | +|------|----------|----------| +| `--interactive` | Questions allowed, waits for user | Local development | +| `--non-interactive` | Questions forbidden, best-effort | CI/CD, automation | + +#### Iteration Focus +- Clean CLI UX for question display (multi-choice, free text) +- Timeout handling (60s limit per question) +- Graceful fallback when questions are disabled +- Session persistence for resuming interrupted generation + +#### Files to Create +``` +src/session/ +├── client.ts # ClaudeSDKClient wrapper +├── question-handler.ts # AskUserQuestion UI +├── modes.ts # Interactive vs non-interactive +└── persistence.ts # Session save/resume +``` + +--- + +### 3. Test Renderers (Deterministic Code Gen) (`/src/renderers/`) + +**Complexity**: Medium +**Iteration Required**: Moderate + +#### What It Does +Transforms `EvalSpec` JSON into actual runnable test files. **Claude generates specs, we generate code** — this is critical for reliability. + +#### Python Renderer (pytest) + +**Input**: `FunctionalTestSpec` +**Output**: `.py` file in `.evaluclaude/tests/` + +```python +# Generated: .evaluclaude/tests/test_{id}.py +import importlib +import pytest + +module = importlib.import_module("{import_path}") +target = getattr(module, "{callable}") + +@pytest.mark.evaluclaude +def test_{id}_{case_id}(): + result = target(**{inputs}) + assert result == {expected.return_value} +``` + +**Features**: +- `pytest` fixtures for env vars (`monkeypatch.setenv`) +- `tmp_path` for file setup +- `capsys` for stdout/stderr assertions +- Parameterized tests for multiple cases + +#### TypeScript Renderer (Vitest/Jest) + +**Input**: `FunctionalTestSpec` +**Output**: `.test.ts` file in `.evaluclaude/tests/` + +```typescript +// Generated: .evaluclaude/tests/{id}.test.ts +import { describe, it, expect } from 'vitest'; +import { callable } from '{import_path}'; + +describe('{description}', () => { + it('{case.description}', () => { + const result = callable({inputs}); + expect(result).toEqual({expected.return_value}); + }); +}); +``` + +#### Iteration Focus +- Handle all `FunctionalTestSpec.target.kind` variants (function, method, CLI, HTTP) +- Proper import path resolution (relative, absolute, aliased) +- Mock scaffolding for `requires_mocks: true` cases +- Error handling for invalid specs + +#### Files to Create +``` +src/renderers/ +├── python/ +│ ├── pytest-renderer.ts +│ ├── fixtures.ts +│ └── templates/ +├── typescript/ +│ ├── vitest-renderer.ts +│ ├── jest-renderer.ts +│ └── templates/ +└── common/ + ├── spec-validator.ts + └── path-resolver.ts +``` + +--- + +### 4. Functional Test Execution & Grading (`/src/runners/`) + +**Complexity**: Medium-High +**Iteration Required**: Moderate + +#### What It Does +Executes generated tests and produces structured results for Promptfoo. **Tests must be functional, never just syntax checks.** + +#### Pytest Execution + +```bash +pytest .evaluclaude/tests/ \ + --json-report \ + --json-report-file=.evaluclaude/results/pytest.json +``` + +**Key Packages**: +- `pytest-json-report`: Structured JSON output with per-test pass/fail +- `pytest.main()`: Programmatic invocation from Python + +**JSON Report Structure**: +```json +{ + "summary": { "passed": 5, "failed": 1, "total": 6 }, + "tests": [ + { + "nodeid": "test_auth.py::test_login_success", + "outcome": "passed", + "duration": 0.023 + } + ] +} +``` + +#### Vitest/Jest Execution + +```bash +vitest run .evaluclaude/tests/ --reporter=json --outputFile=.evaluclaude/results/vitest.json +``` + +**Programmatic (Vitest)**: +```typescript +import { startVitest } from 'vitest/node'; + +const vitest = await startVitest('test', ['.evaluclaude/tests/']); +const results = vitest.state.getFiles(); +``` + +#### Grader Interface for Promptfoo + +```typescript +// graders/deterministic/run-tests.ts +export async function getAssert(output: string, context: AssertContext): Promise { + const testResults = await runTests(context.vars.runtime); + + return { + pass: testResults.failed === 0, + score: testResults.passed / testResults.total, + reason: testResults.failed > 0 + ? `${testResults.failed} tests failed` + : 'All tests passed', + componentResults: testResults.tests.map(t => ({ + pass: t.outcome === 'passed', + score: t.outcome === 'passed' ? 1 : 0, + reason: t.outcome, + assertion: t.nodeid + })) + }; +} +``` + +#### Iteration Focus +- Isolated test environments (clean state per run) +- Timeout handling for long-running tests +- Parallel execution where safe +- Rich error reporting with stack traces + +#### Files to Create +``` +src/runners/ +├── pytest-runner.ts +├── vitest-runner.ts +├── result-parser.ts +└── grader-adapter.ts # Promptfoo GradingResult format + +graders/ +├── deterministic/ +│ ├── run-tests.py # Python grader entry +│ └── run-tests.ts # TypeScript grader entry +└── templates/ + └── grader-wrapper.ts +``` + +--- + +### 5. LLM Rubric Graders (`/src/graders/llm/`) + +**Complexity**: Medium +**Iteration Required**: High (calibration) + +#### What It Does +Uses Claude to grade subjective qualities (code clarity, error message helpfulness, documentation completeness) via structured rubrics. + +#### Custom Promptfoo Provider + +```typescript +// providers/evaluclaude-grader.ts +import { query, ClaudeAgentOptions } from 'claude-agent-sdk'; + +export async function call( + input: string, // Candidate output to grade + context: { vars?: Record; config?: { rubricId: string } } +) { + const rubric = loadRubric(context.config.rubricId); + + const systemPrompt = `You are a deterministic grader. +Use the rubric to assign scores. Output JSON only. +Do not use randomness. If unsure, choose the lower score.`; + + const messages = []; + for await (const msg of query({ + prompt: JSON.stringify({ rubric, candidateOutput: input }), + options: { + system_prompt: systemPrompt, + allowed_tools: [], // No tools needed for grading + } + })) { + messages.push(msg); + } + + return { output: extractGradeFromMessages(messages) }; +} +``` + +#### Rubric Structure + +```yaml +# rubrics/code-quality.yaml +id: code_quality +description: Evaluates code changes for quality +dimensions: + - name: correctness + weight: 0.4 + scale: + min: 1 + max: 5 + definitions: + - score: 5 + description: "Code is completely correct, handles all edge cases" + - score: 3 + description: "Code works for common cases, misses some edge cases" + - score: 1 + description: "Code has significant bugs or doesn't work" + + - name: clarity + weight: 0.3 + scale: + min: 1 + max: 5 + definitions: + - score: 5 + description: "Code is self-documenting, easy to understand" + - score: 3 + description: "Code is understandable with some effort" + - score: 1 + description: "Code is confusing or poorly organized" + + - name: efficiency + weight: 0.3 + scale: + min: 1 + max: 5 + definitions: + - score: 5 + description: "Optimal algorithm and implementation" + - score: 3 + description: "Reasonable performance, not optimal" + - score: 1 + description: "Significant performance issues" + +overall_scoring: weighted_average +``` + +#### Iteration Focus +- Calibration against human judgments +- Consistency across runs (temperature=0, fixed seeds if available) +- Rubric design for different eval types +- Version control for rubrics (drift detection) + +#### Files to Create +``` +src/graders/ +├── llm/ +│ ├── provider.ts # Promptfoo custom provider +│ ├── rubric-loader.ts +│ └── grade-parser.ts +└── calibration/ + ├── benchmark-cases/ # Known good/bad examples + └── calibrator.ts # Compare LLM grades to human + +rubrics/ +├── code-quality.yaml +├── error-messages.yaml +├── documentation.yaml +└── api-design.yaml +``` + +--- + +### 6. Observability & Tracing (`/src/observability/`) + +**Complexity**: Medium +**Iteration Required**: Moderate +**Priority**: 🟡 IMPORTANT — Debug-ability depends on this + +#### What It Does +Captures a complete trace of every eval run so you can click into any result and see exactly what happened. No black boxes. + +#### What Gets Traced + +Every eval (deterministic OR LLM-graded) produces a trace: + +```typescript +interface EvalTrace { + id: string; + timestamp: Date; + evalId: string; // Links to Promptfoo test case + + // What the introspector found + introspection: { + filesScanned: number; + modulesExtracted: number; + duration: number; + }; + + // Claude's analysis session + analysis: { + // Every tool Claude called + toolCalls: { + tool: string; + input: unknown; + output: unknown; + duration: number; + }[]; + + // Questions asked (if interactive) + questions: { + question: string; + options: string[]; + userAnswer: string; + }[]; + + // Claude's reasoning (from thinking blocks if available) + reasoning?: string; + + // Final spec generated + specsGenerated: string[]; // IDs of FunctionalTestSpecs + }; + + // Test execution + execution: { + testsRun: number; + passed: number; + failed: number; + sandboxed: boolean; + duration: number; + }; + + // For LLM-graded evals + grading?: { + rubricUsed: string; + dimensionScores: Record; + finalScore: number; + reasoning: string; + }; +} +``` + +#### Hook-Based Collection + +```typescript +// Automatically collect traces via SDK hooks +const traceCollector: TraceCollector = new TraceCollector(); + +const options: ClaudeAgentOptions = { + hooks: { + PreToolUse: [{ + hooks: [async (input, toolUseId) => { + traceCollector.startToolCall(toolUseId, input.tool_name, input.tool_input); + return {}; + }] + }], + PostToolUse: [{ + hooks: [async (input, toolUseId) => { + traceCollector.endToolCall(toolUseId, input.tool_response); + return {}; + }] + }], + UserPromptSubmit: [{ + hooks: [async (input) => { + traceCollector.recordPrompt(input.prompt); + return {}; + }] + }] + } +}; +``` + +#### UI Integration + +Traces are stored as JSON and surfaced in the Promptfoo UI: + +```yaml +# In generated promptfooconfig.yaml +defaultTest: + metadata: + traceFile: .evaluclaude/traces/{{evalId}}.json +``` + +When you click an eval in Promptfoo's web UI, you see: +1. **Overview**: Pass/fail, duration, cost +2. **Introspection**: What files were analyzed +3. **Claude's Journey**: Every tool call, every question asked +4. **Reasoning**: Why Claude made the decisions it did +5. **Execution**: Which tests ran, which failed + +#### Iteration Focus +- Efficient storage (traces can get large) +- Clean UI formatting (collapsible sections, syntax highlighting) +- Linking traces to specific test failures +- Diff view for comparing traces between runs + +#### Files to Create +``` +src/observability/ +├── tracer.ts # Hook-based collection +├── trace-store.ts # Persist to .evaluclaude/traces/ +├── trace-viewer.ts # Format for display +└── types.ts # EvalTrace interface + +templates/ +└── trace-ui/ # Custom Promptfoo view components +``` + +--- + +## Technology Reference + +### Claude Agent SDK + +| Feature | Usage | +|---------|-------| +| `query()` | One-off tasks, stateless | +| `ClaudeSDKClient` | Multi-turn, sessions, questions | +| `AskUserQuestion` | Clarifying questions during generation | +| `can_use_tool` | Permission callback for questions | +| Local Auth | Uses `claude` CLI authentication | + +**Key Flags**: +- `permission_mode: 'acceptEdits'` — Auto-approve file changes +- `allowed_tools: [...]` — Restrict tool access +- `setting_sources: ['project']` — Load CLAUDE.md + +### Promptfoo + +| Feature | Usage | +|---------|-------| +| Python Provider | `file://providers/agent.py` | +| Python Assertions | `file://graders/check.py` | +| LLM Rubrics | `llm-rubric:` assertion type | +| Custom Provider | For Claude Agent SDK integration | +| JSON Reports | `promptfoo eval -o results.json` | + +**Python Grader Return Types**: +```python +# Boolean +return True # pass +return False # fail + +# Score (0-1) +return 0.85 + +# GradingResult +return { + 'pass': True, + 'score': 0.85, + 'reason': 'All checks passed', + 'componentResults': [...] +} +``` + +### Test Runners + +| Runner | JSON Output | Programmatic API | +|--------|-------------|------------------| +| pytest | `pytest-json-report` | `pytest.main([...])` | +| Vitest | `--reporter=json` | `startVitest('test', [...])` | +| Jest | `jest-ctrf-json-reporter` | `runCLI({...})` | + +--- + +## Directory Structure (Final) + +``` +evaluclaude-harness/ +├── src/ +│ ├── cli/ # Commander.js CLI +│ │ ├── index.ts +│ │ ├── commands/ +│ │ │ ├── init.ts # Full analysis + questions +│ │ │ ├── generate.ts # Incremental (git diff only) +│ │ │ ├── run.ts # Execute evals +│ │ │ └── view.ts # Open Promptfoo UI +│ │ └── utils/ +│ │ +│ ├── introspector/ # 🌳 NON-LLM codebase parsing +│ │ ├── tree-sitter.ts # Multi-language AST parsing +│ │ ├── python-parser.ts # Python-specific extraction +│ │ ├── typescript-parser.ts# TS-specific extraction +│ │ ├── git-diff.ts # 🔄 Incremental change detection +│ │ └── summarizer.ts # RepoSummary generation +│ │ +│ ├── session/ # Claude SDK wrapper +│ │ ├── client.ts # ClaudeSDKClient wrapper +│ │ ├── question-handler.ts # 💬 AskUserQuestion UI +│ │ ├── modes.ts # Interactive vs non-interactive +│ │ └── persistence.ts # Session save/resume +│ │ +│ ├── observability/ # 👁️ Full tracing +│ │ ├── tracer.ts # Hook-based logging +│ │ ├── trace-store.ts # Persist traces per eval +│ │ └── trace-viewer.ts # Format for UI display +│ │ +│ ├── analyzer/ # LLM-based analysis +│ │ ├── spec-generator.ts # RepoSummary → EvalSpec +│ │ └── validator.ts # Validate generated specs +│ │ +│ ├── renderers/ # Spec → Test code (deterministic) +│ │ ├── python/ +│ │ │ ├── pytest-renderer.ts +│ │ │ └── fixtures.ts +│ │ └── typescript/ +│ │ ├── vitest-renderer.ts +│ │ └── jest-renderer.ts +│ │ +│ ├── runners/ # 🔒 Sandboxed test execution +│ │ ├── sandbox.ts # Isolation wrapper +│ │ ├── pytest-runner.ts +│ │ ├── vitest-runner.ts +│ │ └── result-parser.ts +│ │ +│ └── graders/ # LLM grading +│ ├── llm/ +│ │ ├── provider.ts # Promptfoo custom provider +│ │ └── rubric-loader.ts +│ ├── deterministic/ +│ │ └── test-grader.ts +│ └── calibration/ +│ └── calibrator.ts +│ +├── prompts/ # LLM prompts (iterable) +│ ├── analyzer-system.md # Core identity + constraints +│ ├── analyzer-developer.md # Schema + formatting +│ └── analyzer-user.md # Template for RepoSummary +│ +├── rubrics/ # Grading rubrics +│ ├── code-quality.yaml +│ ├── error-messages.yaml +│ └── documentation.yaml +│ +├── templates/ # Generated file templates +│ ├── promptfooconfig.yaml +│ └── ... +│ +├── tests/ # Our own tests +├── package.json +├── tsconfig.json +└── README.md +``` + +--- + +## Development Phases + +### Phase 1: Foundation (Days 1-2) +- [ ] CLI scaffold with Commander.js +- [ ] 🌳 **Tree-sitter introspector** — This is foundational, do it first +- [ ] RepoSummary type definitions +- [ ] Basic Claude SDK session wrapper + +### Phase 2: Analysis (Days 2-4) +- [ ] Analyzer prompt v1 (system + developer + user) +- [ ] EvalSpec schema + validation +- [ ] 💬 **AskUserQuestion flow** — Interactive mode with CLI prompts +- [ ] Non-interactive fallback mode + +### Phase 3: Observability (Days 3-4) +- [ ] 👁️ **Hook-based tracing** — Capture every tool call +- [ ] Trace storage (.evaluclaude/traces/) +- [ ] Basic trace viewer formatting + +### Phase 4: Renderers (Days 4-5) +- [ ] Python/pytest renderer +- [ ] TypeScript/Vitest renderer +- [ ] Spec validation before rendering +- [ ] 🔄 **Git-aware incremental** — Only regenerate for changed files + +### Phase 5: Execution (Days 5-6) +- [ ] 🔒 **Sandbox mode** — Isolated test execution +- [ ] Test runners (pytest, Vitest) +- [ ] Result parsing and aggregation +- [ ] Promptfoo integration + +### Phase 6: Grading (Days 6-7) +- [ ] LLM grader provider +- [ ] Rubric system +- [ ] Calibration tooling + +### Phase 7: Polish (Day 7+) +- [ ] Error handling and recovery +- [ ] Trace UI improvements +- [ ] Documentation +- [ ] Example repos for testing + +--- + +## Key Design Decisions + +1. **Claude generates specs, not code**: Test code is deterministically rendered from specs. This ensures reliability and maintainability. + +2. **Functional tests only**: Every test must invoke actual code. No syntax checks, no format validation, no "output looks good" assertions. + +3. **Language-agnostic schema**: One analyzer prompt, multiple renderers. Adding new languages means adding renderers, not prompts. + +4. **Two-mode operation**: Interactive for development (questions allowed), non-interactive for CI (best-effort, no blocking). + +5. **Promptfoo as orchestrator**: We do the heavy lifting; Promptfoo handles parallelism, caching, and UI. + +6. **🌳 Tree-sitter over token burn**: Never send raw code to Claude for structure extraction. Parse locally, send summaries. + +7. **🔄 Incremental by default**: `generate` only re-analyzes git diff. Full analysis is opt-in via `init --full`. + +8. **👁️ No black boxes**: Every eval has a trace. You can always see what Claude did and why. + +9. **🔒 Sandbox execution**: Generated tests run in isolation. Assume they might do anything. + +10. **💬 Conversation > commands**: Claude asks clarifying questions. This isn't a fire-and-forget generator. + +--- + +## Success Criteria + +- [ ] `npx evaluclaude-harness init` works on a fresh Python/TS repo +- [ ] Generated tests actually run and catch real bugs +- [ ] LLM graders correlate with human judgment (>80% agreement) +- [ ] Full pipeline runs in <5 minutes for medium repo +- [ ] Zero manual config required for basic usage +- [ ] 🌳 Introspector handles 1000+ file repos in <10 seconds +- [ ] 🔄 Incremental `generate` is 10x faster than full `init` +- [ ] 👁️ Every eval result is traceable to Claude's decisions +- [ ] 💬 Claude asks at least 2-3 clarifying questions on complex repos +- [ ] 🔒 No test can escape sandbox to affect host system diff --git a/docs/00-tree-sitter-introspector.md b/docs/00-tree-sitter-introspector.md new file mode 100644 index 0000000..258032f --- /dev/null +++ b/docs/00-tree-sitter-introspector.md @@ -0,0 +1,133 @@ +# 0. Tree-Sitter Introspector - System Design + +> **Priority**: 🔴 FOUNDATIONAL — Build this first +> **Complexity**: Medium +> **Effort Estimate**: 6-10 hours + +--- + +## Overview + +The Tree-Sitter Introspector parses Python and TypeScript codebases locally using tree-sitter AST parsing, extracting structured metadata (functions, classes, imports) **without** sending raw code to Claude. This saves tokens, is faster, and produces reliable structured data. + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Introspector Module │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ File Scanner │───▶│ Tree-Sitter │───▶│ Summarizer │ │ +│ │ (glob/git) │ │ Parsers │ │ │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ File list + Per-file AST RepoSummary │ +│ metadata extracts JSON │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Core Types + +```typescript +interface RepoSummary { + languages: ('python' | 'typescript')[]; + root: string; + analyzedAt: string; + files: FileInfo[]; + modules: ModuleInfo[]; + config: ConfigInfo; + git?: GitInfo; +} + +interface ModuleInfo { + path: string; + exports: ExportInfo[]; + imports: string[]; + complexity: 'low' | 'medium' | 'high'; +} + +interface ExportInfo { + name: string; + kind: 'function' | 'class' | 'constant' | 'type'; + signature?: string; + docstring?: string; + lineNumber: number; + isAsync?: boolean; +} +``` + +--- + +## Key Implementation Details + +### Tree-Sitter Queries (Python) + +```typescript +const FUNCTION_QUERY = ` + (function_definition + name: (identifier) @name + parameters: (parameters) @params + return_type: (type)? @return_type + ) @func +`; + +const CLASS_QUERY = ` + (class_definition + name: (identifier) @name + body: (block) @body + ) @class +`; +``` + +### Git-Aware Incremental + +```typescript +async function getChangedFiles(since: string): Promise { + const { stdout } = await exec(`git diff --name-only ${since}`); + return stdout.split('\n').filter(f => /\.(py|ts|tsx)$/.test(f)); +} +``` + +--- + +## File Structure + +``` +src/introspector/ +├── index.ts # Main entry point +├── types.ts # TypeScript interfaces +├── scanner.ts # File discovery +├── parsers/ +│ ├── python.ts # Python tree-sitter queries +│ └── typescript.ts # TS tree-sitter queries +├── git.ts # Git integration +└── summarizer.ts # Combine into RepoSummary +``` + +--- + +## Dependencies + +```json +{ + "tree-sitter": "^0.21.0", + "tree-sitter-python": "^0.21.0", + "tree-sitter-typescript": "^0.21.0", + "glob": "^10.3.0" +} +``` + +--- + +## Success Criteria + +- [ ] Parses Python files (functions, classes, imports) +- [ ] Parses TypeScript files (functions, classes, imports) +- [ ] Handles 1000+ file repos in <10 seconds +- [ ] Incremental mode only parses changed files +- [ ] Gracefully handles syntax errors diff --git a/docs/01-codebase-analyzer-prompt.md b/docs/01-codebase-analyzer-prompt.md new file mode 100644 index 0000000..0160ee4 --- /dev/null +++ b/docs/01-codebase-analyzer-prompt.md @@ -0,0 +1,142 @@ +# 1. Codebase Analyzer Prompt - System Design + +> **Priority**: 🟡 HIGH — Core LLM logic +> **Complexity**: High (prompt engineering) +> **Effort Estimate**: 8-12 hours (iterative refinement) + +--- + +## Overview + +The Codebase Analyzer takes structured `RepoSummary` from the introspector and generates `EvalSpec` JSON defining what tests to create. Key insight: **Claude generates specs, not code**. Test code is deterministically rendered from specs. + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Codebase Analyzer Agent │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ RepoSummary │───▶│ Claude Agent │───▶│ EvalSpec │ │ +│ │ JSON │ │ SDK │ │ JSON │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │AskUserQuestion│ │ +│ │ (optional) │ │ +│ └──────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Core Types + +```typescript +interface EvalSpec { + version: '1.0'; + repo: { name: string; languages: string[]; analyzedAt: string }; + scenarios: EvalScenario[]; + grading: { + deterministic: DeterministicGrade[]; + rubrics: RubricGrade[]; + }; + metadata: { + generatedBy: string; + totalTokens: number; + questionsAsked: number; + confidence: 'low' | 'medium' | 'high'; + }; +} + +interface EvalScenario { + id: string; // "auth-login-success" + name: string; + description: string; + target: { + module: string; + function: string; + type: 'function' | 'method' | 'class'; + }; + category: 'unit' | 'integration' | 'edge-case' | 'negative'; + priority: 'critical' | 'high' | 'medium' | 'low'; + setup?: { fixtures: string[]; mocks: MockSpec[] }; + input: { args: Record; kwargs?: Record }; + assertions: Assertion[]; + tags: string[]; +} +``` + +--- + +## Prompt Architecture (Three-Part) + +### 1. System Prompt +- Defines Claude's identity as codebase analyzer +- Constraints: functional tests only, no syntax checks, ask don't assume + +### 2. Developer Prompt +- Contains EvalSpec JSON schema +- Formatting rules (snake_case, kebab-case IDs) +- Assertion type reference + +### 3. User Prompt (Template) +- Injects RepoSummary JSON +- User context about what to evaluate +- Instructions for output format + +--- + +## Key Implementation + +```typescript +async function generateEvalSpec(options: GenerateOptions): Promise { + const agentOptions: ClaudeAgentOptions = { + systemPrompt: await loadPrompt('analyzer-system.md'), + permissionMode: options.interactive ? 'default' : 'dontAsk', + canUseTool: async ({ toolName, input }) => { + if (toolName === 'AskUserQuestion' && options.onQuestion) { + const answer = await options.onQuestion(input); + return { behavior: 'allow', updatedInput: { ...input, answers: { [input.question]: answer } } }; + } + return { behavior: 'deny' }; + }, + outputFormat: { type: 'json_schema', json_schema: { name: 'EvalSpec', schema: EVAL_SPEC_SCHEMA } }, + }; + + for await (const msg of query(prompt, agentOptions)) { + if (msg.type === 'result') return msg.output as EvalSpec; + } +} +``` + +--- + +## File Structure + +``` +src/analyzer/ +├── index.ts # Main entry point +├── types.ts # EvalSpec types +├── spec-generator.ts # Claude Agent SDK integration +├── validator.ts # JSON schema validation +└── prompt-builder.ts # Builds prompts from templates + +prompts/ +├── analyzer-system.md +├── analyzer-developer.md +└── analyzer-user.md +``` + +--- + +## Success Criteria + +- [ ] Generates valid EvalSpec JSON for Python repos +- [ ] Generates valid EvalSpec JSON for TypeScript repos +- [ ] Asks 2-3 clarifying questions on complex repos +- [ ] <10k tokens per analysis +- [ ] 100% assertion coverage (every scenario has assertions) diff --git a/docs/02-synchronous-claude-session.md b/docs/02-synchronous-claude-session.md new file mode 100644 index 0000000..ec5c398 --- /dev/null +++ b/docs/02-synchronous-claude-session.md @@ -0,0 +1,159 @@ +# 2. Synchronous Claude Session with Questions - System Design + +> **Priority**: 🟡 HIGH — Interactive UX +> **Complexity**: Medium +> **Effort Estimate**: 4-6 hours + +--- + +## Overview + +Handles **interactive communication** between Claude and the user during eval generation. When Claude calls `AskUserQuestion`, we display it in CLI, collect the answer, and return it to Claude. + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Claude Session Manager │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ Claude Agent │◀──────────────────▶│ Question │ │ +│ │ SDK │ AskUserQuestion │ Handler │ │ +│ └──────────────┘ └──────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ Result CLI/stdin │ +│ (EvalSpec) (inquirer) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Session Modes + +| Mode | Usage | Behavior | +|------|-------|----------| +| `interactive` | Local dev | Full CLI prompts via inquirer | +| `non-interactive` | CI/CD | Deny questions, use defaults | +| `auto-answer` | Scripted | Use provided default answers | + +--- + +## Core Types + +```typescript +interface Question { + header: string; + question: string; + options?: QuestionOption[]; + multiSelect?: boolean; + freeText?: boolean; + defaultValue?: string; +} + +interface SessionOptions { + interactive: boolean; + defaultAnswers?: Record; + timeout?: number; +} + +type SessionMode = 'interactive' | 'non-interactive' | 'auto-answer'; +``` + +--- + +## Key Implementation + +```typescript +class ClaudeSession { + async run(systemPrompt: string, userPrompt: string, outputSchema?: object): Promise { + const agentOptions: ClaudeAgentOptions = { + systemPrompt, + permissionMode: this.getPermissionMode(), + canUseTool: this.createToolHandler(), + outputFormat: outputSchema ? { type: 'json_schema', json_schema: { name: 'Output', schema: outputSchema } } : undefined, + }; + + for await (const msg of query(userPrompt, agentOptions)) { + if (msg.type === 'result') return msg.output as T; + } + } + + private async handleAskUserQuestion(input: any) { + if (this.mode === 'non-interactive') { + return { behavior: 'deny', message: 'Interactive questions not allowed in CI' }; + } + + const answers: Record = {}; + for (const question of input.questions) { + answers[question.question] = await promptCLI(question); + } + return { behavior: 'allow', updatedInput: { questions: input.questions, answers } }; + } +} +``` + +--- + +## CLI Adapter (inquirer) + +```typescript +async function promptSelect(question: Question): Promise { + const { answer } = await inquirer.prompt([{ + type: 'list', + name: 'answer', + message: question.question, + choices: question.options!.map(opt => ({ name: `${opt.label} - ${opt.description}`, value: opt.label })), + }]); + return answer; +} +``` + +**User sees:** +``` +┌─ Priority ──────────────────────── +│ I found 47 utility functions. Which should I prioritize? + +? Select an option: +❯ all - Test all 47 functions + top-10 - Focus on 10 most-used + critical - Only critical path functions +``` + +--- + +## File Structure + +``` +src/session/ +├── index.ts # Main exports +├── types.ts # TypeScript interfaces +├── client.ts # Claude SDK wrapper +├── question-handler.ts # AskUserQuestion logic +├── cli-adapter.ts # Terminal UI (inquirer) +├── modes.ts # Mode detection +└── persistence.ts # Save/resume session +``` + +--- + +## Dependencies + +```json +{ + "@anthropic-ai/claude-agent-sdk": "^0.1.0", + "inquirer": "^9.2.0" +} +``` + +--- + +## Success Criteria + +- [ ] Interactive mode works in terminal +- [ ] Non-interactive mode works in CI +- [ ] Auto-answer mode uses provided defaults +- [ ] Session state can be saved and resumed +- [ ] Ctrl+C exits cleanly diff --git a/docs/03-test-renderers.md b/docs/03-test-renderers.md new file mode 100644 index 0000000..78d10ca --- /dev/null +++ b/docs/03-test-renderers.md @@ -0,0 +1,157 @@ +# 3. Test Renderers - System Design + +> **Priority**: 🟢 MEDIUM — Deterministic layer +> **Complexity**: Medium +> **Effort Estimate**: 8-12 hours + +--- + +## Overview + +Test Renderers **deterministically transform** `EvalSpec` JSON into runnable test files. Key insight: +- **Claude generates specs** (what to test, inputs, assertions) +- **Renderers generate code** (deterministic, templated, no LLM) + +This makes tests reliable, debuggable, and version-controllable. + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Renderer Pipeline │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ EvalSpec │───▶│ Renderer │───▶│ Test Files │ │ +│ │ JSON │ │ (per-lang) │ │ (.py/.ts) │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ Supported: pytest (Python) | vitest (TS) | jest (TS) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Core Types + +```typescript +interface RenderOptions { + outputDir: string; + framework: 'pytest' | 'vitest' | 'jest'; + includeFixtures: boolean; + generateMocks: boolean; +} + +interface RenderResult { + files: GeneratedFile[]; + stats: { scenarioCount: number; fileCount: number; assertionCount: number }; +} + +interface GeneratedFile { + path: string; + content: string; + scenarios: string[]; // Which scenario IDs +} +``` + +--- + +## Assertion Mapping + +| EvalSpec Type | Python (pytest) | TypeScript (vitest) | +|---------------|-----------------|---------------------| +| `equals` | `assert result == expected` | `expect(result).toBe(expected)` | +| `contains` | `assert key in result` | `expect(result).toContain(key)` | +| `matches` | `assert re.match(pattern, result)` | `expect(result).toMatch(pattern)` | +| `throws` | `pytest.raises(ExceptionType)` | `expect(() => fn()).toThrow()` | +| `type` | `assert isinstance(result, Type)` | `expect(typeof result).toBe('type')` | + +--- + +## Example Transformation + +**EvalSpec scenario:** +```json +{ + "id": "auth-login-success", + "target": { "module": "src/auth/login.py", "function": "login" }, + "input": { "args": { "username": "test", "password": "valid" } }, + "assertions": [ + { "type": "type", "target": "return", "expected": "dict" }, + { "type": "contains", "target": "return", "expected": "token" } + ] +} +``` + +**Generated pytest:** +```python +def test_auth_login_success(): + """Verify login returns JWT on valid credentials""" + result = login("test", "valid") + assert isinstance(result, dict) + assert "token" in result +``` + +--- + +## File Structure + +``` +src/renderers/ +├── index.ts # Registry + main export +├── types.ts # Interfaces +├── base.ts # Abstract base renderer +├── python/ +│ ├── pytest-renderer.ts +│ ├── assertions.ts +│ └── templates/ +│ └── test-file.py.hbs +├── typescript/ +│ ├── vitest-renderer.ts +│ ├── jest-renderer.ts +│ └── assertions.ts +└── utils/ + └── template-engine.ts +``` + +--- + +## Incremental Rendering + +```typescript +async function renderIncremental( + spec: EvalSpec, + options: RenderOptions, + changedFiles: string[] +): Promise { + const filteredSpec = { + ...spec, + scenarios: spec.scenarios.filter(s => + changedFiles.some(f => s.target.module.includes(f)) + ), + }; + return renderSpec(filteredSpec, options); +} +``` + +--- + +## Dependencies + +```json +{ + "handlebars": "^4.7.8" +} +``` + +--- + +## Success Criteria + +- [ ] Pytest renderer generates valid Python test files +- [ ] Vitest renderer generates valid TypeScript test files +- [ ] Generated tests pass linting +- [ ] All assertion types are supported +- [ ] Mocks and fixtures correctly generated +- [ ] Incremental rendering works diff --git a/docs/04-functional-test-execution.md b/docs/04-functional-test-execution.md new file mode 100644 index 0000000..d91a6b3 --- /dev/null +++ b/docs/04-functional-test-execution.md @@ -0,0 +1,269 @@ +# 4. Functional Test Execution & Grading - System Design + +> **Priority**: 🟢 MEDIUM — Runtime layer +> **Complexity**: Medium-High +> **Effort Estimate**: 6-10 hours + +--- + +## Overview + +Executes generated tests in a **sandboxed environment** and produces structured results. Tests run in isolation to prevent accidental side effects. Results feed into Promptfoo for aggregation and UI. + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Test Execution Pipeline │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Test Files │───▶│ Sandbox │───▶│ Results │ │ +│ │ (.py/.ts) │ │ Runner │ │ JSON │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ pytest/ │ │ Promptfoo │ │ +│ │ vitest │ │ Integration │ │ +│ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Core Types + +```typescript +interface ExecutionOptions { + framework: 'pytest' | 'vitest' | 'jest'; + sandbox: boolean; + timeout: number; // ms per test + parallel: boolean; + filter?: string[]; // Run specific test IDs +} + +interface ExecutionResult { + summary: { + total: number; + passed: number; + failed: number; + skipped: number; + duration: number; + }; + tests: TestResult[]; + errors: string[]; +} + +interface TestResult { + id: string; // Maps to EvalScenario.id + name: string; + status: 'passed' | 'failed' | 'skipped' | 'error'; + duration: number; + assertions: { + passed: number; + failed: number; + details: AssertionResult[]; + }; + error?: { message: string; stack?: string }; + stdout?: string; + stderr?: string; +} +``` + +--- + +## Sandbox Configuration + +```typescript +const SANDBOX_CONFIG = { + enabled: true, + autoAllowBashIfSandboxed: true, + network: { + allowLocalBinding: true, + allowOutbound: false, // No external network + }, + filesystem: { + readOnly: ['/'], + writable: ['/tmp', './test-output'], + }, + env: { + inherit: ['PATH', 'HOME'], + set: { CI: 'true', NODE_ENV: 'test' }, + }, +}; +``` + +--- + +## Runner Implementations + +### Pytest Runner + +```typescript +async function runPytest(testDir: string, options: ExecutionOptions): Promise { + const args = [ + '-v', + '--tb=short', + '--json-report', + '--json-report-file=results.json', + options.parallel ? '-n auto' : '', + options.filter?.map(f => `-k ${f}`).join(' ') || '', + ].filter(Boolean); + + const { exitCode, stdout, stderr } = await exec( + `pytest ${args.join(' ')} ${testDir}`, + { timeout: options.timeout, cwd: testDir } + ); + + const report = JSON.parse(await fs.readFile('results.json', 'utf-8')); + return parseJsonReport(report); +} +``` + +### Vitest Runner + +```typescript +async function runVitest(testDir: string, options: ExecutionOptions): Promise { + const args = [ + 'run', + '--reporter=json', + '--outputFile=results.json', + options.filter?.length ? `--testNamePattern="${options.filter.join('|')}"` : '', + ].filter(Boolean); + + const { exitCode } = await exec( + `npx vitest ${args.join(' ')}`, + { timeout: options.timeout, cwd: testDir } + ); + + const report = JSON.parse(await fs.readFile('results.json', 'utf-8')); + return parseVitestReport(report); +} +``` + +--- + +## Promptfoo Integration + +### Custom Provider (`providers/test-runner.py`) + +```python +def get_provider_response(prompt: str, options: dict, context: dict) -> dict: + """Runs tests and returns structured results.""" + import subprocess + import json + + test_dir = options.get('test_dir', './tests') + framework = options.get('framework', 'pytest') + + if framework == 'pytest': + result = subprocess.run( + ['pytest', '--json-report', '--json-report-file=/tmp/results.json', test_dir], + capture_output=True, text=True, timeout=300 + ) + with open('/tmp/results.json') as f: + report = json.load(f) + + return { + 'output': json.dumps({ + 'passed': report['summary']['passed'], + 'failed': report['summary']['failed'], + 'tests': report['tests'], + }), + 'error': None, + } +``` + +### Promptfoo Config + +```yaml +providers: + - id: file://providers/test-runner.py + label: functional-tests + config: + test_dir: .evaluclaude/tests + framework: pytest + timeout: 300 + +tests: + - vars: + scenario_id: auth-login-success + assert: + - type: python + value: | + import json + result = json.loads(output) + result['passed'] > 0 and result['failed'] == 0 +``` + +--- + +## File Structure + +``` +src/runners/ +├── index.ts # Main entry + registry +├── types.ts # Interfaces +├── sandbox.ts # Isolation wrapper +├── pytest-runner.ts # Python test execution +├── vitest-runner.ts # Vitest execution +├── jest-runner.ts # Jest execution +└── result-parser.ts # Normalize results + +providers/ +└── test-runner.py # Promptfoo provider +``` + +--- + +## Result Parsing + +```typescript +function parseJsonReport(report: any): ExecutionResult { + return { + summary: { + total: report.summary.total, + passed: report.summary.passed, + failed: report.summary.failed, + skipped: report.summary.skipped || 0, + duration: report.duration, + }, + tests: report.tests.map((t: any) => ({ + id: extractScenarioId(t.nodeid), + name: t.nodeid, + status: t.outcome, + duration: t.call?.duration || 0, + assertions: { passed: 0, failed: 0, details: [] }, + error: t.call?.crash ? { message: t.call.crash.message } : undefined, + })), + errors: [], + }; +} +``` + +--- + +## Dependencies + +```json +{ + "dependencies": {} +} +``` + +**Test framework deps (installed in target repo):** +- `pytest`, `pytest-json-report`, `pytest-xdist` (Python) +- `vitest` (TypeScript) + +--- + +## Success Criteria + +- [ ] Pytest tests run and produce JSON results +- [ ] Vitest tests run and produce JSON results +- [ ] Sandbox prevents network/filesystem escape +- [ ] Results map back to EvalScenario IDs +- [ ] Promptfoo integration works +- [ ] Parallel execution supported diff --git a/docs/05-llm-rubric-graders.md b/docs/05-llm-rubric-graders.md new file mode 100644 index 0000000..0919c0e --- /dev/null +++ b/docs/05-llm-rubric-graders.md @@ -0,0 +1,305 @@ +# 5. LLM Rubric Graders - System Design + +> **Priority**: 🟢 MEDIUM — Subjective quality layer +> **Complexity**: Medium +> **Effort Estimate**: 4-6 hours + +--- + +## Overview + +LLM Rubric Graders use Claude to evaluate **subjective quality** that deterministic tests can't measure: +- Code readability +- Error message helpfulness +- Documentation quality +- API design consistency + +These complement functional tests with human-like judgment. + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ LLM Grading Pipeline │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Output │───▶│ Rubric │───▶│ Grading │ │ +│ │ (code/ │ │ + Claude │ │ Result │ │ +│ │ text) │ │ │ │ │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ │ +│ Uses Promptfoo │ +│ llm-rubric assertion │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Core Types + +```typescript +interface Rubric { + name: string; + description: string; + criteria: RubricCriterion[]; + passingThreshold: number; // 0-1 +} + +interface RubricCriterion { + name: string; + description: string; + weight: number; // Relative weight + examples?: { + good: string; + bad: string; + }; +} + +interface RubricGradingResult { + pass: boolean; + score: number; // 0-1 + reason: string; + criterionScores: { + name: string; + score: number; + feedback: string; + }[]; +} +``` + +--- + +## Rubric Examples + +### Code Quality Rubric (`rubrics/code-quality.yaml`) + +```yaml +name: code-quality +description: Evaluates generated code for quality and maintainability +passingThreshold: 0.7 + +criteria: + - name: readability + weight: 0.3 + description: Code is easy to read and understand + examples: + good: "Clear variable names, logical flow, proper indentation" + bad: "Single-letter variables, deeply nested logic, inconsistent style" + + - name: correctness + weight: 0.4 + description: Code correctly implements the intended behavior + examples: + good: "Handles edge cases, correct algorithm, proper error handling" + bad: "Missing edge cases, off-by-one errors, swallowed exceptions" + + - name: efficiency + weight: 0.2 + description: Code uses appropriate data structures and algorithms + examples: + good: "O(n) where O(n) is optimal, avoids unnecessary allocations" + bad: "O(n²) when O(n) is possible, creates objects in tight loops" + + - name: maintainability + weight: 0.1 + description: Code is easy to modify and extend + examples: + good: "Single responsibility, low coupling, clear interfaces" + bad: "God functions, tight coupling, magic numbers" +``` + +### Error Messages Rubric (`rubrics/error-messages.yaml`) + +```yaml +name: error-messages +description: Evaluates quality of error messages +passingThreshold: 0.6 + +criteria: + - name: clarity + weight: 0.4 + description: Error message clearly explains what went wrong + + - name: actionability + weight: 0.4 + description: Error message suggests how to fix the problem + + - name: context + weight: 0.2 + description: Error message includes relevant context (file, line, values) +``` + +--- + +## Promptfoo Integration + +### Using `llm-rubric` Assertion + +```yaml +# promptfooconfig.yaml +tests: + - vars: + code_output: "{{generated_code}}" + assert: + - type: llm-rubric + value: | + Evaluate this code for quality: + + {{code_output}} + + Score on: + 1. Readability (0-10) + 2. Correctness (0-10) + 3. Efficiency (0-10) + 4. Maintainability (0-10) + + Provide overall score and specific feedback. + threshold: 0.7 +``` + +### Custom Python Grader + +```python +# graders/rubric_grader.py +import json +from anthropic import Anthropic + +def get_assert(output: str, context: dict) -> dict: + """Grade output using LLM rubric.""" + rubric = context.get('config', {}).get('rubric', 'code-quality') + rubric_def = load_rubric(rubric) + + client = Anthropic() + + prompt = f""" +You are evaluating code quality against this rubric: + +{json.dumps(rubric_def, indent=2)} + +Code to evaluate: +``` +{output} +``` + +For each criterion, provide: +1. Score (0-1) +2. Brief feedback + +Return JSON: +{{ + "scores": {{"criterion_name": {{"score": 0.8, "feedback": "..."}}}}, + "overall": 0.75, + "summary": "..." +}} +""" + + response = client.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=1024, + messages=[{"role": "user", "content": prompt}] + ) + + result = json.loads(response.content[0].text) + + return { + "pass": result["overall"] >= rubric_def["passingThreshold"], + "score": result["overall"], + "reason": result["summary"], + "namedScores": {k: v["score"] for k, v in result["scores"].items()}, + } +``` + +--- + +## Calibration + +LLM graders need calibration to ensure consistency: + +```typescript +interface CalibrationSet { + rubric: string; + examples: CalibrationExample[]; +} + +interface CalibrationExample { + input: string; + expectedScore: number; + expectedFeedback: string[]; +} + +async function calibrate(rubric: Rubric, examples: CalibrationExample[]): Promise { + const results = await Promise.all( + examples.map(ex => gradeWithRubric(ex.input, rubric)) + ); + + const agreement = results.filter((r, i) => + Math.abs(r.score - examples[i].expectedScore) < 0.1 + ).length / results.length; + + return { + agreement, + drift: results.map((r, i) => r.score - examples[i].expectedScore), + needsAdjustment: agreement < 0.8, + }; +} +``` + +--- + +## File Structure + +``` +src/graders/ +├── llm/ +│ ├── index.ts # Main entry +│ ├── provider.ts # Promptfoo custom provider +│ ├── rubric-loader.ts # Load YAML rubrics +│ └── grader.ts # Core grading logic +└── calibration/ + ├── calibrator.ts # Calibration runner + └── examples/ # Calibration datasets + +rubrics/ +├── code-quality.yaml +├── error-messages.yaml +├── documentation.yaml +└── api-design.yaml + +graders/ +└── rubric_grader.py # Python grader for Promptfoo +``` + +--- + +## When to Use LLM vs Deterministic + +| Use LLM Graders | Use Deterministic | +|-----------------|-------------------| +| Subjective quality | Pass/fail assertions | +| Style/readability | Type checking | +| Helpfulness | Value equality | +| Consistency | Error presence | +| User experience | Performance thresholds | + +--- + +## Dependencies + +```json +{ + "js-yaml": "^4.1.0" +} +``` + +--- + +## Success Criteria + +- [ ] Rubrics load from YAML files +- [ ] LLM grader produces consistent scores +- [ ] Calibration detects drift +- [ ] Integrates with Promptfoo `llm-rubric` +- [ ] Custom Python grader works +- [ ] >80% agreement with human judgment diff --git a/docs/06-observability-tracing.md b/docs/06-observability-tracing.md new file mode 100644 index 0000000..6b6a805 --- /dev/null +++ b/docs/06-observability-tracing.md @@ -0,0 +1,364 @@ +# 6. Observability & Tracing - System Design + +> **Priority**: 🟡 HIGH — Debugging is critical +> **Complexity**: Medium +> **Effort Estimate**: 4-6 hours + +--- + +## Overview + +Every eval run produces a **trace** capturing what Claude did and why. No black boxes. When a test fails, you can see: +- What files Claude analyzed +- What questions it asked +- What specs it generated +- The reasoning behind each decision + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Observability Pipeline │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Claude Agent │───▶│ Tracer │───▶│ Trace Store │ │ +│ │ Hooks │ │ (collector) │ │ (.json) │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ Trace Viewer │ │ +│ │ (Promptfoo) │ │ +│ └──────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Core Types + +```typescript +interface EvalTrace { + id: string; // UUID + evalId: string; // Links to EvalSpec + startedAt: string; + completedAt: string; + duration: number; // ms + + status: 'success' | 'partial' | 'failed'; + + introspection: { + filesAnalyzed: string[]; + totalFunctions: number; + totalClasses: number; + duration: number; + }; + + analysis: { + promptTokens: number; + completionTokens: number; + toolCalls: ToolCall[]; + questionsAsked: Question[]; + decisions: Decision[]; + }; + + generation: { + scenariosGenerated: number; + filesWritten: string[]; + }; + + execution: { + testsPassed: number; + testsFailed: number; + testsSkipped: number; + failures: TestFailure[]; + }; + + errors: TraceError[]; +} + +interface ToolCall { + timestamp: string; + tool: string; + input: any; + output: any; + duration: number; +} + +interface Decision { + timestamp: string; + type: 'include' | 'exclude' | 'prioritize' | 'question'; + subject: string; // What was decided about + reasoning: string; // Why + confidence: number; // 0-1 +} + +interface TestFailure { + scenarioId: string; + error: string; + stack?: string; + expected?: any; + actual?: any; +} +``` + +--- + +## Hook-Based Collection + +Use Claude Agent SDK hooks to capture everything: + +```typescript +import { ClaudeAgentOptions } from '@anthropic-ai/claude-agent-sdk'; +import { Tracer } from './tracer'; + +function createTracedOptions(tracer: Tracer): Partial { + return { + hooks: { + preToolUse: [{ + hooks: [async (input) => { + tracer.recordToolStart(input.tool_name, input.tool_input); + return { continue_: true }; + }] + }], + postToolUse: [{ + hooks: [async (input) => { + tracer.recordToolEnd(input.tool_name, input.tool_output); + return {}; + }] + }], + }, + }; +} +``` + +--- + +## Tracer Implementation + +```typescript +class Tracer { + private trace: EvalTrace; + private currentToolCall?: { name: string; input: any; startTime: number }; + + constructor(evalId: string) { + this.trace = { + id: crypto.randomUUID(), + evalId, + startedAt: new Date().toISOString(), + completedAt: '', + duration: 0, + status: 'success', + introspection: { filesAnalyzed: [], totalFunctions: 0, totalClasses: 0, duration: 0 }, + analysis: { promptTokens: 0, completionTokens: 0, toolCalls: [], questionsAsked: [], decisions: [] }, + generation: { scenariosGenerated: 0, filesWritten: [] }, + execution: { testsPassed: 0, testsFailed: 0, testsSkipped: 0, failures: [] }, + errors: [], + }; + } + + recordToolStart(name: string, input: any): void { + this.currentToolCall = { name, input, startTime: Date.now() }; + } + + recordToolEnd(name: string, output: any): void { + if (this.currentToolCall?.name === name) { + this.trace.analysis.toolCalls.push({ + timestamp: new Date().toISOString(), + tool: name, + input: this.currentToolCall.input, + output, + duration: Date.now() - this.currentToolCall.startTime, + }); + this.currentToolCall = undefined; + } + } + + recordQuestion(question: any, answer: string): void { + this.trace.analysis.questionsAsked.push({ + ...question, + answer, + timestamp: new Date().toISOString(), + }); + } + + recordDecision(type: Decision['type'], subject: string, reasoning: string, confidence: number): void { + this.trace.analysis.decisions.push({ + timestamp: new Date().toISOString(), + type, + subject, + reasoning, + confidence, + }); + } + + recordError(error: Error, context?: string): void { + this.trace.errors.push({ + timestamp: new Date().toISOString(), + message: error.message, + stack: error.stack, + context, + }); + this.trace.status = 'failed'; + } + + finalize(): EvalTrace { + this.trace.completedAt = new Date().toISOString(); + this.trace.duration = new Date(this.trace.completedAt).getTime() - new Date(this.trace.startedAt).getTime(); + return this.trace; + } +} +``` + +--- + +## Trace Storage + +```typescript +const TRACES_DIR = '.evaluclaude/traces'; + +async function saveTrace(trace: EvalTrace): Promise { + await fs.mkdir(TRACES_DIR, { recursive: true }); + const filePath = path.join(TRACES_DIR, `${trace.id}.json`); + await fs.writeFile(filePath, JSON.stringify(trace, null, 2)); + return filePath; +} + +async function loadTrace(traceId: string): Promise { + const filePath = path.join(TRACES_DIR, `${traceId}.json`); + const content = await fs.readFile(filePath, 'utf-8'); + return JSON.parse(content); +} + +async function listTraces(evalId?: string): Promise { + const files = await fs.readdir(TRACES_DIR); + const traces = await Promise.all( + files.filter(f => f.endsWith('.json')).map(f => loadTrace(f.replace('.json', ''))) + ); + return evalId ? traces.filter(t => t.evalId === evalId) : traces; +} +``` + +--- + +## Promptfoo Integration + +Link traces to test results: + +```yaml +# promptfooconfig.yaml +defaultTest: + metadata: + traceFile: .evaluclaude/traces/{{evalId}}.json +``` + +--- + +## Trace Viewer CLI + +```typescript +// src/cli/commands/view.ts +import { Command } from 'commander'; +import { loadTrace, listTraces } from '../observability/trace-store'; + +export const viewCommand = new Command('view') + .description('View eval trace') + .argument('[trace-id]', 'Specific trace ID') + .option('--last', 'View most recent trace') + .option('--json', 'Output raw JSON') + .action(async (traceId, options) => { + let trace: EvalTrace; + + if (options.last) { + const traces = await listTraces(); + trace = traces.sort((a, b) => + new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime() + )[0]; + } else { + trace = await loadTrace(traceId); + } + + if (options.json) { + console.log(JSON.stringify(trace, null, 2)); + } else { + displayTrace(trace); + } + }); + +function displayTrace(trace: EvalTrace): void { + console.log(`\n📊 Trace: ${trace.id}`); + console.log(` Status: ${trace.status}`); + console.log(` Duration: ${trace.duration}ms`); + console.log(`\n📂 Introspection:`); + console.log(` Files: ${trace.introspection.filesAnalyzed.length}`); + console.log(` Functions: ${trace.introspection.totalFunctions}`); + console.log(`\n🤖 Analysis:`); + console.log(` Tool calls: ${trace.analysis.toolCalls.length}`); + console.log(` Questions: ${trace.analysis.questionsAsked.length}`); + console.log(` Decisions: ${trace.analysis.decisions.length}`); + console.log(`\n🧪 Execution:`); + console.log(` ✅ Passed: ${trace.execution.testsPassed}`); + console.log(` ❌ Failed: ${trace.execution.testsFailed}`); + + if (trace.execution.failures.length > 0) { + console.log(`\n❌ Failures:`); + trace.execution.failures.forEach(f => { + console.log(` - ${f.scenarioId}: ${f.error}`); + }); + } +} +``` + +--- + +## File Structure + +``` +src/observability/ +├── index.ts # Main exports +├── tracer.ts # Hook-based collection +├── trace-store.ts # Persist to filesystem +├── trace-viewer.ts # Format for display +└── types.ts # EvalTrace interface + +.evaluclaude/ +└── traces/ + ├── abc123.json + ├── def456.json + └── ... +``` + +--- + +## What Gets Traced + +| Phase | Captured | +|-------|----------| +| Introspection | Files parsed, functions/classes found, duration | +| Analysis | Every tool call, questions asked, decisions made | +| Generation | Scenarios created, files written | +| Execution | Test results, failures with context | +| Errors | Any exceptions with stack traces | + +--- + +## Dependencies + +```json +{ + "dependencies": {} +} +``` + +--- + +## Success Criteria + +- [ ] Every eval run produces a trace +- [ ] Traces capture all tool calls +- [ ] Questions and answers are recorded +- [ ] Test failures link to trace +- [ ] CLI viewer displays traces clearly +- [ ] Traces stored efficiently (<1MB each) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4568ba9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3823 @@ +{ + "name": "evaluclaude-harness", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "evaluclaude-harness", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "commander": "^12.1.0", + "glob": "^10.4.0", + "inquirer": "^9.2.0", + "tree-sitter": "^0.21.1", + "tree-sitter-python": "^0.21.0", + "tree-sitter-typescript": "^0.21.0" + }, + "bin": { + "evaluclaude": "dist/cli/index.js" + }, + "devDependencies": { + "@types/inquirer": "^9.0.7", + "@types/node": "^20.14.0", + "eslint": "^8.57.0", + "typescript": "^5.4.5", + "vitest": "^1.6.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "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/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/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.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/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/inquirer": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.9.tgz", + "integrity": "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.28.tgz", + "integrity": "sha512-VyKBr25BuFDzBFCK5sUM6ZXiWfqgCTwTAOK8qzGV/m9FCirXYDlmczJ+d5dXBAQALGCdRRdbteKYfJ84NGEusw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "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/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "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/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "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/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.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-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "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/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "9.3.8", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.8.tgz", + "integrity": "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==", + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.2", + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "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/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/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/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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.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/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_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-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "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/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "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/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-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tree-sitter": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", + "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "node_modules/tree-sitter-python": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/tree-sitter-python/-/tree-sitter-python-0.21.0.tgz", + "integrity": "sha512-IUKx7JcTVbByUx1iHGFS/QsIjx7pqwTMHL9bl/NGyhyyydbfNrpruo2C7W6V4KZrbkkCOlX8QVrCoGOFW5qecg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.1.0", + "node-gyp-build": "^4.8.0" + }, + "peerDependencies": { + "tree-sitter": "^0.21.0" + }, + "peerDependenciesMeta": { + "tree_sitter": { + "optional": true + } + } + }, + "node_modules/tree-sitter-python/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/tree-sitter-typescript": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/tree-sitter-typescript/-/tree-sitter-typescript-0.21.2.tgz", + "integrity": "sha512-/RyNK41ZpkA8PuPZimR6pGLvNR1p0ibRUJwwQn4qAjyyLEIQD/BNlwS3NSxWtGsAWZe9gZ44VK1mWx2+eQVldg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.1" + }, + "peerDependencies": { + "tree-sitter": "^0.21.0" + }, + "peerDependenciesMeta": { + "tree_sitter": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "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/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.2.tgz", + "integrity": "sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "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", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "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", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "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/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "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/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..524af80 --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "name": "evaluclaude-harness", + "version": "0.1.0", + "description": "Zero-to-evals in one command. Claude analyzes codebases and generates functional tests.", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "evaluclaude": "./dist/cli/index.js" + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "start": "node dist/cli/index.js", + "test": "vitest", + "lint": "eslint src --ext .ts", + "typecheck": "tsc --noEmit" + }, + "keywords": [ + "eval", + "claude", + "testing", + "ai", + "code-analysis" + ], + "author": "", + "license": "MIT", + "dependencies": { + "commander": "^12.1.0", + "glob": "^10.4.0", + "inquirer": "^9.2.0", + "tree-sitter": "^0.21.1", + "tree-sitter-python": "^0.21.0", + "tree-sitter-typescript": "^0.21.0" + }, + "devDependencies": { + "@types/inquirer": "^9.0.7", + "@types/node": "^20.14.0", + "eslint": "^8.57.0", + "typescript": "^5.4.5", + "vitest": "^1.6.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/src/cli/commands/intro.ts b/src/cli/commands/intro.ts new file mode 100644 index 0000000..9439562 --- /dev/null +++ b/src/cli/commands/intro.ts @@ -0,0 +1,118 @@ +import { Command } from 'commander'; +import * as path from 'node:path'; +import { analyze, treeToString } from '../../introspector/index.js'; + +export const introCommand = new Command('intro') + .description('Introspect a codebase and output its structure (tree-sitter analysis)') + .argument('[path]', 'Path to the repository to analyze', '.') + .option('-o, --output ', 'Output file for the RepoSummary JSON') + .option('--json', 'Output as JSON (default)') + .option('--summary', 'Output a human-readable summary instead of JSON') + .option('--tree', 'Show file tree structure') + .action(async (repoPath: string, options: { output?: string; json?: boolean; summary?: boolean; tree?: boolean }) => { + const absolutePath = path.resolve(repoPath); + + console.log(`\n🔍 Analyzing: ${absolutePath}\n`); + + try { + const summary = await analyze({ + root: absolutePath, + onProgress: (msg) => console.log(` ${msg}`), + }); + + console.log(''); + + if (options.tree && summary.tree) { + console.log('📁 File Tree:\n'); + console.log(treeToString(summary.tree)); + console.log(''); + } else if (options.summary) { + printHumanSummary(summary); + } else { + const json = JSON.stringify(summary, null, 2); + + if (options.output) { + const fs = await import('node:fs/promises'); + await fs.writeFile(options.output, json); + console.log(`📄 Written to: ${options.output}`); + } else { + console.log(json); + } + } + } catch (error) { + console.error('❌ Error analyzing repository:', error); + process.exit(1); + } + }); + +function printHumanSummary(summary: import('../../introspector/types.js').RepoSummary): void { + console.log('📊 Repository Summary'); + console.log('─'.repeat(50)); + console.log(`📁 Root: ${summary.root}`); + console.log(`🗓️ Analyzed: ${summary.analyzedAt}`); + console.log(`🔤 Languages: ${summary.languages.join(', ') || 'none detected'}`); + + console.log('\n📂 Files:'); + console.log(` Total: ${summary.files.length}`); + console.log(` Source: ${summary.files.filter(f => f.role === 'source').length}`); + console.log(` Test: ${summary.files.filter(f => f.role === 'test').length}`); + console.log(` Config: ${summary.files.filter(f => f.role === 'config').length}`); + + console.log('\n📦 Modules:'); + console.log(` Total: ${summary.modules.length}`); + + const totalExports = summary.modules.reduce((sum, m) => sum + m.exports.length, 0); + const functions = summary.modules.flatMap(m => m.exports.filter(e => e.kind === 'function')); + const classes = summary.modules.flatMap(m => m.exports.filter(e => e.kind === 'class')); + + console.log(` Functions: ${functions.length}`); + console.log(` Classes: ${classes.length}`); + console.log(` Total exports: ${totalExports}`); + + if (summary.config.python) { + console.log('\n🐍 Python:'); + console.log(` Test framework: ${summary.config.python.testFramework}`); + console.log(` pyproject.toml: ${summary.config.python.pyprojectToml ? '✓' : '✗'}`); + console.log(` setup.py: ${summary.config.python.setupPy ? '✓' : '✗'}`); + } + + if (summary.config.typescript) { + console.log('\n📘 TypeScript:'); + console.log(` Test framework: ${summary.config.typescript.testFramework}`); + console.log(` package.json: ${summary.config.typescript.packageJson ? '✓' : '✗'}`); + console.log(` tsconfig.json: ${summary.config.typescript.tsconfig ? '✓' : '✗'}`); + } + + if (summary.git) { + console.log('\n📌 Git:'); + console.log(` Branch: ${summary.git.branch}`); + console.log(` Commit: ${summary.git.currentCommit.slice(0, 8)}`); + + if (summary.git.recentCommits && summary.git.recentCommits.length > 0) { + console.log('\n📜 Recent Commits:'); + for (const commit of summary.git.recentCommits.slice(0, 5)) { + const date = new Date(commit.date).toLocaleDateString(); + console.log(` ${commit.shortHash} ${date} - ${commit.message.slice(0, 50)}${commit.message.length > 50 ? '...' : ''}`); + } + } + + if (summary.git.fileHistory && summary.git.fileHistory.length > 0) { + console.log('\n🔥 Most Active Files (by commit count):'); + for (const file of summary.git.fileHistory.slice(0, 5)) { + console.log(` ${file.path} (${file.commitCount} commits)`); + } + } + } + + // Show top modules by export count + const topModules = [...summary.modules] + .sort((a, b) => b.exports.length - a.exports.length) + .slice(0, 5); + + if (topModules.length > 0) { + console.log('\n🏆 Top modules by exports:'); + for (const mod of topModules) { + console.log(` ${mod.path}: ${mod.exports.length} exports`); + } + } +} diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000..92ae478 --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +import { Command } from 'commander'; +import { introCommand } from './commands/intro.js'; + +const program = new Command(); + +program + .name('evaluclaude') + .description('Zero-to-evals in one command. Claude analyzes codebases and generates functional tests.') + .version('0.1.0'); + +program.addCommand(introCommand); + +program.parse(process.argv); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..869cd76 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export * from './introspector/index.js'; diff --git a/src/introspector/git.ts b/src/introspector/git.ts new file mode 100644 index 0000000..e0c36ff --- /dev/null +++ b/src/introspector/git.ts @@ -0,0 +1,199 @@ +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; +import type { GitInfo, CommitInfo, FileHistoryInfo } from './types.js'; + +const execAsync = promisify(exec); +const MAX_COMMITS = 20; +const MAX_FILE_HISTORY = 50; + +export async function getGitInfo(root: string, lastCommit?: string): Promise { + try { + // Check if it's a git repo + await execAsync('git rev-parse --git-dir', { cwd: root }); + } catch { + return undefined; + } + + try { + const [currentCommitResult, branchResult] = await Promise.all([ + execAsync('git rev-parse HEAD', { cwd: root }), + execAsync('git branch --show-current', { cwd: root }), + ]); + + const currentCommit = currentCommitResult.stdout.trim(); + const branch = branchResult.stdout.trim() || 'HEAD'; + + let changedSince: string[] = []; + if (lastCommit && lastCommit !== currentCommit) { + changedSince = await getChangedFiles(root, lastCommit); + } + + // Fetch recent commits + const recentCommits = await getRecentCommits(root); + + // Fetch file history (most frequently changed files) + const fileHistory = await getFileHistory(root); + + return { + currentCommit, + lastAnalyzedCommit: lastCommit || currentCommit, + changedSince, + branch, + recentCommits, + fileHistory, + }; + } catch { + return undefined; + } +} + +export async function getChangedFiles(root: string, since: string): Promise { + try { + const { stdout } = await execAsync(`git diff --name-only ${since}`, { cwd: root }); + return stdout + .split('\n') + .filter(f => f && isSourceFile(f)); + } catch { + return []; + } +} + +export async function getCurrentCommit(root: string): Promise { + try { + const { stdout } = await execAsync('git rev-parse HEAD', { cwd: root }); + return stdout.trim(); + } catch { + return undefined; + } +} + +export async function isGitRepo(root: string): Promise { + try { + await execAsync('git rev-parse --git-dir', { cwd: root }); + return true; + } catch { + return false; + } +} + +function isSourceFile(filePath: string): boolean { + return /\.(py|ts|tsx|js|jsx)$/.test(filePath); +} + +export async function getRecentCommits(root: string, limit: number = MAX_COMMITS): Promise { + try { + // Format: hash|short|author|date|message|filesChanged + const { stdout } = await execAsync( + `git log -${limit} --pretty=format:"%H|%h|%an|%aI|%s" --shortstat`, + { cwd: root, maxBuffer: 1024 * 1024 } + ); + + const commits: CommitInfo[] = []; + const lines = stdout.split('\n'); + + let i = 0; + while (i < lines.length) { + const line = lines[i]?.trim(); + if (!line) { + i++; + continue; + } + + const parts = line.split('|'); + if (parts.length >= 5) { + // Parse the commit line + const [hash, shortHash, author, date, ...messageParts] = parts; + const message = messageParts.join('|'); // In case message contains | + + // Look for stats line (next non-empty line) + let filesChanged = 0; + if (i + 1 < lines.length) { + const statsLine = lines[i + 1]?.trim(); + if (statsLine) { + const match = statsLine.match(/(\d+) files? changed/); + if (match) { + filesChanged = parseInt(match[1], 10); + i++; // Skip stats line + } + } + } + + commits.push({ + hash, + shortHash, + author, + date, + message, + filesChanged, + }); + } + i++; + } + + return commits; + } catch { + return []; + } +} + +export async function getFileHistory(root: string, limit: number = MAX_FILE_HISTORY): Promise { + try { + // Get the most frequently modified source files + const { stdout } = await execAsync( + `git log --pretty=format: --name-only | grep -E '\\.(py|ts|tsx|js|jsx)$' | sort | uniq -c | sort -rn | head -${limit}`, + { cwd: root, maxBuffer: 1024 * 1024, shell: '/bin/bash' } + ); + + const files: FileHistoryInfo[] = []; + + for (const line of stdout.split('\n')) { + const trimmed = line.trim(); + if (!trimmed) continue; + + const match = trimmed.match(/^\s*(\d+)\s+(.+)$/); + if (match) { + const commitCount = parseInt(match[1], 10); + const filePath = match[2]; + + // Get contributors for this file + const contributors = await getFileContributors(root, filePath); + const lastModified = await getFileLastModified(root, filePath); + + files.push({ + path: filePath, + commitCount, + lastModified, + contributors, + }); + } + } + + return files; + } catch { + return []; + } +} + +async function getFileContributors(root: string, filePath: string): Promise { + try { + const { stdout } = await execAsync( + `git log --pretty=format:"%an" -- "${filePath}" | sort -u | head -5`, + { cwd: root, shell: '/bin/bash' } + ); + return stdout.split('\n').filter(s => s.trim()).slice(0, 5); + } catch { + return []; + } +} + +async function getFileLastModified(root: string, filePath: string): Promise { + try { + const { stdout } = await execAsync( + `git log -1 --pretty=format:"%aI" -- "${filePath}"`, + { cwd: root } + ); + return stdout.trim(); + } catch { + return ''; + } +} diff --git a/src/introspector/index.ts b/src/introspector/index.ts new file mode 100644 index 0000000..a7823fa --- /dev/null +++ b/src/introspector/index.ts @@ -0,0 +1,25 @@ +export { analyze, analyzeIncremental } from './summarizer.js'; +export { scanDirectory, detectConfig } from './scanner.js'; +export { getGitInfo, getChangedFiles, getCurrentCommit, isGitRepo, getRecentCommits, getFileHistory } from './git.js'; +export { buildFileTree, treeToString, getTreeStats } from './tree.js'; +export { PythonParser } from './parsers/python.js'; +export { TypeScriptParser } from './parsers/typescript.js'; + +export type { + RepoSummary, + FileInfo, + ModuleInfo, + ExportInfo, + ConfigInfo, + GitInfo, + CommitInfo, + FileHistoryInfo, + FileTreeNode, + Language, +} from './types.js'; + +import { analyze as analyzeRepo } from './summarizer.js'; + +export async function introspect(repoPath: string): Promise { + return analyzeRepo({ root: repoPath }); +} diff --git a/src/introspector/parsers/base.ts b/src/introspector/parsers/base.ts new file mode 100644 index 0000000..ac8211a --- /dev/null +++ b/src/introspector/parsers/base.ts @@ -0,0 +1,29 @@ +import type { ModuleInfo, ExportInfo } from '../types.js'; + +export interface ParserResult { + exports: ExportInfo[]; + imports: string[]; +} + +export abstract class BaseParser { + abstract readonly language: string; + + abstract parse(source: string, filePath: string): ModuleInfo; + + protected getText(source: string, startIndex: number, endIndex: number): string { + return source.slice(startIndex, endIndex); + } + + protected calculateComplexity(exportCount: number): ModuleInfo['complexity'] { + if (exportCount <= 5) return 'low'; + if (exportCount <= 15) return 'medium'; + return 'high'; + } + + protected extractFirstLineOfDocstring(docstring: string | undefined): string | undefined { + if (!docstring) return undefined; + const trimmed = docstring.trim(); + const firstLine = trimmed.split('\n')[0]; + return firstLine.replace(/^["']{1,3}|["']{1,3}$/g, '').trim() || undefined; + } +} diff --git a/src/introspector/parsers/python.ts b/src/introspector/parsers/python.ts new file mode 100644 index 0000000..26fcaea --- /dev/null +++ b/src/introspector/parsers/python.ts @@ -0,0 +1,167 @@ +import Parser from 'tree-sitter'; +import Python from 'tree-sitter-python'; +import { BaseParser } from './base.js'; +import type { ModuleInfo, ExportInfo } from '../types.js'; + +export class PythonParser extends BaseParser { + readonly language = 'python'; + private parser: Parser; + + constructor() { + super(); + this.parser = new Parser(); + this.parser.setLanguage(Python); + } + + parse(source: string, filePath: string): ModuleInfo { + const tree = this.parser.parse(source); + const rootNode = tree.rootNode; + + const exports: ExportInfo[] = []; + const imports: string[] = []; + + // Walk the tree to extract functions, classes, and imports + this.walkNode(rootNode, source, exports, imports); + + return { + path: filePath, + exports, + imports: [...new Set(imports)], + complexity: this.calculateComplexity(exports.length), + }; + } + + private walkNode( + node: Parser.SyntaxNode, + source: string, + exports: ExportInfo[], + imports: string[] + ): void { + switch (node.type) { + case 'function_definition': + exports.push(this.extractFunction(node, source)); + break; + + case 'class_definition': + exports.push(this.extractClass(node, source)); + break; + + case 'import_statement': + imports.push(...this.extractImport(node, source)); + break; + + case 'import_from_statement': + imports.push(...this.extractFromImport(node, source)); + break; + + default: + // Recurse into children for top-level nodes + if (node.type === 'module' || node.type === 'decorated_definition') { + for (const child of node.children) { + this.walkNode(child, source, exports, imports); + } + } + } + } + + private extractFunction(node: Parser.SyntaxNode, source: string): ExportInfo { + const nameNode = node.childForFieldName('name'); + const paramsNode = node.childForFieldName('parameters'); + const returnTypeNode = node.childForFieldName('return_type'); + const bodyNode = node.childForFieldName('body'); + + const name = nameNode ? this.getText(source, nameNode.startIndex, nameNode.endIndex) : 'unknown'; + + // Build signature + let signature = ''; + if (paramsNode) { + signature = this.getText(source, paramsNode.startIndex, paramsNode.endIndex); + } + if (returnTypeNode) { + signature += ` -> ${this.getText(source, returnTypeNode.startIndex, returnTypeNode.endIndex)}`; + } + + // Check for async + const isAsync = node.children.some(c => c.type === 'async'); + + // Try to extract docstring + let docstring: string | undefined; + if (bodyNode && bodyNode.firstChild?.type === 'expression_statement') { + const exprStmt = bodyNode.firstChild; + const strNode = exprStmt.firstChild; + if (strNode?.type === 'string') { + docstring = this.extractFirstLineOfDocstring( + this.getText(source, strNode.startIndex, strNode.endIndex) + ); + } + } + + return { + name, + kind: 'function', + signature: signature || undefined, + docstring, + lineNumber: node.startPosition.row + 1, + isAsync, + }; + } + + private extractClass(node: Parser.SyntaxNode, source: string): ExportInfo { + const nameNode = node.childForFieldName('name'); + const bodyNode = node.childForFieldName('body'); + + const name = nameNode ? this.getText(source, nameNode.startIndex, nameNode.endIndex) : 'unknown'; + + // Try to extract docstring + let docstring: string | undefined; + if (bodyNode && bodyNode.firstChild?.type === 'expression_statement') { + const exprStmt = bodyNode.firstChild; + const strNode = exprStmt.firstChild; + if (strNode?.type === 'string') { + docstring = this.extractFirstLineOfDocstring( + this.getText(source, strNode.startIndex, strNode.endIndex) + ); + } + } + + // Build a basic signature showing inheritance + let signature: string | undefined; + const superclassNode = node.childForFieldName('superclasses'); + if (superclassNode) { + signature = this.getText(source, superclassNode.startIndex, superclassNode.endIndex); + } + + return { + name, + kind: 'class', + signature, + docstring, + lineNumber: node.startPosition.row + 1, + }; + } + + private extractImport(node: Parser.SyntaxNode, source: string): string[] { + const imports: string[] = []; + + for (const child of node.children) { + if (child.type === 'dotted_name') { + imports.push(this.getText(source, child.startIndex, child.endIndex)); + } else if (child.type === 'aliased_import') { + const nameNode = child.childForFieldName('name'); + if (nameNode) { + imports.push(this.getText(source, nameNode.startIndex, nameNode.endIndex)); + } + } + } + + return imports; + } + + private extractFromImport(node: Parser.SyntaxNode, source: string): string[] { + const moduleNode = node.childForFieldName('module_name'); + if (moduleNode) { + return [this.getText(source, moduleNode.startIndex, moduleNode.endIndex)]; + } + return []; + } +} diff --git a/src/introspector/parsers/typescript.ts b/src/introspector/parsers/typescript.ts new file mode 100644 index 0000000..21a9c2e --- /dev/null +++ b/src/introspector/parsers/typescript.ts @@ -0,0 +1,188 @@ +import Parser from 'tree-sitter'; +import TypeScriptLang from 'tree-sitter-typescript'; +import { BaseParser } from './base.js'; +import type { ModuleInfo, ExportInfo } from '../types.js'; + +const { typescript: TypeScript } = TypeScriptLang; + +export class TypeScriptParser extends BaseParser { + readonly language = 'typescript'; + private parser: Parser; + + constructor() { + super(); + this.parser = new Parser(); + this.parser.setLanguage(TypeScript); + } + + parse(source: string, filePath: string): ModuleInfo { + const tree = this.parser.parse(source); + const rootNode = tree.rootNode; + + const exports: ExportInfo[] = []; + const imports: string[] = []; + + this.walkNode(rootNode, source, exports, imports, false); + + return { + path: filePath, + exports, + imports: [...new Set(imports)], + complexity: this.calculateComplexity(exports.length), + }; + } + + private walkNode( + node: Parser.SyntaxNode, + source: string, + exports: ExportInfo[], + imports: string[], + isExported: boolean + ): void { + switch (node.type) { + case 'function_declaration': + exports.push(this.extractFunction(node, source, isExported)); + break; + + case 'class_declaration': + exports.push(this.extractClass(node, source, isExported)); + break; + + case 'lexical_declaration': + case 'variable_declaration': + exports.push(...this.extractVariables(node, source, isExported)); + break; + + case 'type_alias_declaration': + case 'interface_declaration': + exports.push(this.extractTypeDefinition(node, source, isExported)); + break; + + case 'export_statement': + // Recurse with isExported = true + for (const child of node.children) { + this.walkNode(child, source, exports, imports, true); + } + break; + + case 'import_statement': + imports.push(...this.extractImport(node, source)); + break; + + case 'program': + // Recurse into top-level statements + for (const child of node.children) { + this.walkNode(child, source, exports, imports, false); + } + break; + } + } + + private extractFunction(node: Parser.SyntaxNode, source: string, isExported: boolean): ExportInfo { + const nameNode = node.childForFieldName('name'); + const paramsNode = node.childForFieldName('parameters'); + const returnTypeNode = node.childForFieldName('return_type'); + + const name = nameNode ? this.getText(source, nameNode.startIndex, nameNode.endIndex) : 'unknown'; + + // Build signature + let signature = ''; + if (paramsNode) { + signature = this.getText(source, paramsNode.startIndex, paramsNode.endIndex); + } + if (returnTypeNode) { + signature += `: ${this.getText(source, returnTypeNode.startIndex, returnTypeNode.endIndex)}`; + } + + // Check for async + const isAsync = node.children.some(c => c.type === 'async'); + + return { + name, + kind: 'function', + signature: signature || undefined, + lineNumber: node.startPosition.row + 1, + isAsync, + isExported, + }; + } + + private extractClass(node: Parser.SyntaxNode, source: string, isExported: boolean): ExportInfo { + const nameNode = node.childForFieldName('name'); + const name = nameNode ? this.getText(source, nameNode.startIndex, nameNode.endIndex) : 'unknown'; + + // Get heritage clause for extends/implements + let signature: string | undefined; + const heritageNode = node.children.find(c => c.type === 'class_heritage'); + if (heritageNode) { + signature = this.getText(source, heritageNode.startIndex, heritageNode.endIndex); + } + + return { + name, + kind: 'class', + signature, + lineNumber: node.startPosition.row + 1, + isExported, + }; + } + + private extractVariables(node: Parser.SyntaxNode, source: string, isExported: boolean): ExportInfo[] { + const exports: ExportInfo[] = []; + + for (const child of node.children) { + if (child.type === 'variable_declarator') { + const nameNode = child.childForFieldName('name'); + const valueNode = child.childForFieldName('value'); + + if (nameNode) { + const name = this.getText(source, nameNode.startIndex, nameNode.endIndex); + + // Check if it's a function expression or arrow function + const isFunction = valueNode && ( + valueNode.type === 'arrow_function' || + valueNode.type === 'function_expression' || + valueNode.type === 'function' + ); + + exports.push({ + name, + kind: isFunction ? 'function' : 'constant', + lineNumber: child.startPosition.row + 1, + isExported, + isAsync: valueNode?.children.some(c => c.type === 'async'), + }); + } + } + } + + return exports; + } + + private extractTypeDefinition(node: Parser.SyntaxNode, source: string, isExported: boolean): ExportInfo { + const nameNode = node.childForFieldName('name'); + const name = nameNode ? this.getText(source, nameNode.startIndex, nameNode.endIndex) : 'unknown'; + + return { + name, + kind: 'type', + lineNumber: node.startPosition.row + 1, + isExported, + }; + } + + private extractImport(node: Parser.SyntaxNode, source: string): string[] { + const imports: string[] = []; + + for (const child of node.children) { + if (child.type === 'string') { + // Remove quotes from the import path + const importPath = this.getText(source, child.startIndex, child.endIndex) + .replace(/^["']|["']$/g, ''); + imports.push(importPath); + } + } + + return imports; + } +} diff --git a/src/introspector/scanner.ts b/src/introspector/scanner.ts new file mode 100644 index 0000000..bcdd648 --- /dev/null +++ b/src/introspector/scanner.ts @@ -0,0 +1,213 @@ +import { glob } from 'glob'; +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; +import type { FileInfo } from './types.js'; + +const IGNORE_PATTERNS = [ + 'node_modules/**', + '.git/**', + '__pycache__/**', + '*.pyc', + 'dist/**', + 'build/**', + '.venv/**', + 'venv/**', + '.env/**', + 'env/**', + 'coverage/**', + '.next/**', + '.nuxt/**', +]; + +export async function scanDirectory(root: string): Promise { + const patterns = ['**/*.py', '**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx']; + + const files: string[] = []; + for (const pattern of patterns) { + const matches = await glob(pattern, { + cwd: root, + ignore: IGNORE_PATTERNS, + nodir: true, + }); + files.push(...matches); + } + + const uniqueFiles = [...new Set(files)]; + + const fileInfos = await Promise.all( + uniqueFiles.map(async (relativePath) => { + const fullPath = path.join(root, relativePath); + try { + const stats = await fs.stat(fullPath); + return { + path: relativePath, + lang: detectLanguage(relativePath), + role: detectRole(relativePath), + size: stats.size, + lastModified: stats.mtime.toISOString(), + } satisfies FileInfo; + } catch { + return null; + } + }) + ); + + return fileInfos.filter((f): f is FileInfo => f !== null); +} + +function detectLanguage(filePath: string): FileInfo['lang'] { + const ext = path.extname(filePath).toLowerCase(); + switch (ext) { + case '.py': + return 'python'; + case '.ts': + case '.tsx': + return 'typescript'; + case '.js': + case '.jsx': + return 'typescript'; // Treat JS as TS for parsing + default: + return 'other'; + } +} + +function detectRole(filePath: string): FileInfo['role'] { + const lowerPath = filePath.toLowerCase(); + const fileName = lowerPath.split('/').pop() || ''; + + // Test files - be more specific to avoid false positives + if ( + lowerPath.includes('__tests__') || + lowerPath.includes('/tests/') || + lowerPath.includes('/test/') || + fileName.endsWith('_test.py') || + fileName.endsWith('.test.ts') || + fileName.endsWith('.test.tsx') || + fileName.endsWith('.test.js') || + fileName.endsWith('.spec.ts') || + fileName.endsWith('.spec.tsx') || + fileName.endsWith('.spec.js') || + fileName.startsWith('test_') + ) { + return 'test'; + } + + // Config files + if ( + lowerPath.includes('config') || + lowerPath.includes('settings') || + lowerPath.includes('.env') || + lowerPath.endsWith('conftest.py') || + lowerPath.endsWith('setup.py') || + lowerPath.endsWith('pyproject.toml') + ) { + return 'config'; + } + + // Documentation + if ( + lowerPath.includes('docs') || + lowerPath.includes('doc') || + lowerPath.includes('readme') + ) { + return 'docs'; + } + + return 'source'; +} + +export async function detectConfig(root: string): Promise<{ + python?: { + entryPoints: string[]; + testFramework: 'pytest' | 'unittest' | 'none'; + hasTyping: boolean; + pyprojectToml: boolean; + setupPy: boolean; + }; + typescript?: { + entryPoints: string[]; + testFramework: 'vitest' | 'jest' | 'none'; + hasTypes: boolean; + packageJson: boolean; + tsconfig: boolean; + }; +}> { + const config: ReturnType extends Promise ? T : never = {}; + + // Check for Python project + const hasPyprojectToml = await fileExists(path.join(root, 'pyproject.toml')); + const hasSetupPy = await fileExists(path.join(root, 'setup.py')); + const hasRequirementsTxt = await fileExists(path.join(root, 'requirements.txt')); + + if (hasPyprojectToml || hasSetupPy || hasRequirementsTxt) { + let testFramework: 'pytest' | 'unittest' | 'none' = 'none'; + + // Check for pytest + if (hasPyprojectToml) { + try { + const content = await fs.readFile(path.join(root, 'pyproject.toml'), 'utf-8'); + if (content.includes('pytest')) { + testFramework = 'pytest'; + } + } catch {} + } + + if (testFramework === 'none' && hasRequirementsTxt) { + try { + const content = await fs.readFile(path.join(root, 'requirements.txt'), 'utf-8'); + if (content.includes('pytest')) { + testFramework = 'pytest'; + } + } catch {} + } + + config.python = { + entryPoints: [], + testFramework, + hasTyping: false, + pyprojectToml: hasPyprojectToml, + setupPy: hasSetupPy, + }; + } + + // Check for TypeScript/JavaScript project + const hasPackageJson = await fileExists(path.join(root, 'package.json')); + const hasTsconfig = await fileExists(path.join(root, 'tsconfig.json')); + + if (hasPackageJson || hasTsconfig) { + let testFramework: 'vitest' | 'jest' | 'none' = 'none'; + + if (hasPackageJson) { + try { + const content = await fs.readFile(path.join(root, 'package.json'), 'utf-8'); + const pkg = JSON.parse(content); + const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }; + + if ('vitest' in allDeps) { + testFramework = 'vitest'; + } else if ('jest' in allDeps) { + testFramework = 'jest'; + } + } catch {} + } + + config.typescript = { + entryPoints: [], + testFramework, + hasTypes: hasTsconfig, + packageJson: hasPackageJson, + tsconfig: hasTsconfig, + }; + } + + return config; +} + +async function fileExists(filePath: string): Promise { + try { + await fs.access(filePath); + return true; + } catch { + return false; + } +} diff --git a/src/introspector/summarizer.ts b/src/introspector/summarizer.ts new file mode 100644 index 0000000..fd39f4e --- /dev/null +++ b/src/introspector/summarizer.ts @@ -0,0 +1,134 @@ +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; +import { scanDirectory, detectConfig } from './scanner.js'; +import { PythonParser } from './parsers/python.js'; +import { TypeScriptParser } from './parsers/typescript.js'; +import { getGitInfo, getChangedFiles } from './git.js'; +import { buildFileTree } from './tree.js'; +import type { RepoSummary, ModuleInfo, FileInfo, Language } from './types.js'; + +export interface AnalyzeOptions { + root: string; + incremental?: boolean; + lastCommit?: string; + onlyFiles?: string[]; + onProgress?: (message: string) => void; +} + +export async function analyze(options: AnalyzeOptions): Promise { + const { root, incremental, lastCommit, onlyFiles, onProgress } = options; + + onProgress?.('Scanning directory...'); + let files = await scanDirectory(root); + + // Filter for incremental analysis + if (onlyFiles && onlyFiles.length > 0) { + files = files.filter(f => onlyFiles.includes(f.path)); + onProgress?.(`Filtered to ${files.length} changed files`); + } + + onProgress?.(`Found ${files.length} source files`); + + // Initialize parsers + const pythonParser = new PythonParser(); + const tsParser = new TypeScriptParser(); + + const modules: ModuleInfo[] = []; + const sourceFiles = files.filter(f => f.role === 'source' && f.lang !== 'other'); + + onProgress?.(`Parsing ${sourceFiles.length} modules...`); + + for (const file of sourceFiles) { + const fullPath = path.join(root, file.path); + + try { + const source = await fs.readFile(fullPath, 'utf-8'); + + let moduleInfo: ModuleInfo; + if (file.lang === 'python') { + moduleInfo = pythonParser.parse(source, file.path); + } else if (file.lang === 'typescript') { + moduleInfo = tsParser.parse(source, file.path); + } else { + continue; + } + + modules.push(moduleInfo); + } catch (error) { + // Skip files that can't be parsed + onProgress?.(`Warning: Could not parse ${file.path}`); + } + } + + onProgress?.('Detecting project configuration...'); + const config = await detectConfig(root); + + onProgress?.('Getting git info...'); + const git = await getGitInfo(root, lastCommit); + + onProgress?.('Building file tree...'); + const tree = buildFileTree(files, path.basename(root)); + + // Detect languages used + const languages = detectLanguages(files); + + onProgress?.('Analysis complete'); + + return { + languages, + root, + analyzedAt: new Date().toISOString(), + files, + modules, + config, + git, + tree, + }; +} + +export async function analyzeIncremental( + root: string, + lastCommit: string, + onProgress?: (message: string) => void +): Promise { + onProgress?.('Getting changed files since last commit...'); + const changedFiles = await getChangedFiles(root, lastCommit); + + if (changedFiles.length === 0) { + onProgress?.('No files changed'); + // Return minimal summary + return { + languages: [], + root, + analyzedAt: new Date().toISOString(), + files: [], + modules: [], + config: {}, + git: await getGitInfo(root, lastCommit), + }; + } + + onProgress?.(`Found ${changedFiles.length} changed files`); + + return analyze({ + root, + incremental: true, + lastCommit, + onlyFiles: changedFiles, + onProgress, + }); +} + +function detectLanguages(files: FileInfo[]): Language[] { + const languages = new Set(); + + for (const file of files) { + if (file.lang === 'python') { + languages.add('python'); + } else if (file.lang === 'typescript') { + languages.add('typescript'); + } + } + + return [...languages]; +} diff --git a/src/introspector/tree.ts b/src/introspector/tree.ts new file mode 100644 index 0000000..50e1acb --- /dev/null +++ b/src/introspector/tree.ts @@ -0,0 +1,157 @@ +import type { FileInfo, FileTreeNode } from './types.js'; + +export function buildFileTree(files: FileInfo[], rootName: string = '.'): FileTreeNode { + const root: FileTreeNode = { + name: rootName, + path: '', + type: 'directory', + children: [], + }; + + // Build a map for quick lookup + const nodeMap = new Map(); + nodeMap.set('', root); + + // Sort files to ensure parents are created before children + const sortedFiles = [...files].sort((a, b) => a.path.localeCompare(b.path)); + + for (const file of sortedFiles) { + const parts = file.path.split('/'); + let currentPath = ''; + + // Create all parent directories + for (let i = 0; i < parts.length - 1; i++) { + const parentPath = currentPath; + currentPath = currentPath ? `${currentPath}/${parts[i]}` : parts[i]; + + if (!nodeMap.has(currentPath)) { + const dirNode: FileTreeNode = { + name: parts[i], + path: currentPath, + type: 'directory', + children: [], + }; + nodeMap.set(currentPath, dirNode); + + // Add to parent + const parent = nodeMap.get(parentPath); + if (parent && parent.children) { + parent.children.push(dirNode); + } + } + } + + // Create the file node + const fileName = parts[parts.length - 1]; + const fileNode: FileTreeNode = { + name: fileName, + path: file.path, + type: 'file', + lang: file.lang, + role: file.role, + }; + + // Add to parent directory + const parentPath = parts.slice(0, -1).join('/'); + const parent = nodeMap.get(parentPath); + if (parent && parent.children) { + parent.children.push(fileNode); + } + } + + // Sort children alphabetically (directories first) + sortTreeRecursive(root); + + return root; +} + +function sortTreeRecursive(node: FileTreeNode): void { + if (node.children) { + node.children.sort((a, b) => { + // Directories first + if (a.type !== b.type) { + return a.type === 'directory' ? -1 : 1; + } + return a.name.localeCompare(b.name); + }); + + for (const child of node.children) { + sortTreeRecursive(child); + } + } +} + +export function treeToString(node: FileTreeNode, prefix: string = '', isLast: boolean = true): string { + const lines: string[] = []; + + const connector = isLast ? '└── ' : '├── '; + const extension = isLast ? ' ' : '│ '; + + if (node.path === '') { + // Root node + lines.push(node.name); + } else { + const icon = node.type === 'directory' ? '📁' : getFileIcon(node.lang, node.role); + lines.push(`${prefix}${connector}${icon} ${node.name}`); + } + + if (node.children) { + const children = node.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + const childIsLast = i === children.length - 1; + const newPrefix = node.path === '' ? '' : prefix + extension; + lines.push(treeToString(child, newPrefix, childIsLast)); + } + } + + return lines.join('\n'); +} + +function getFileIcon(lang?: string, role?: string): string { + if (role === 'test') return '🧪'; + if (role === 'config') return '⚙️'; + if (role === 'docs') return '📄'; + + switch (lang) { + case 'python': return '🐍'; + case 'typescript': return '📘'; + default: return '📄'; + } +} + +export function getTreeStats(node: FileTreeNode): { + directories: number; + files: number; + byLang: Record; + byRole: Record; +} { + const stats = { + directories: 0, + files: 0, + byLang: {} as Record, + byRole: {} as Record, + }; + + function traverse(n: FileTreeNode): void { + if (n.type === 'directory') { + stats.directories++; + if (n.children) { + for (const child of n.children) { + traverse(child); + } + } + } else { + stats.files++; + if (n.lang) { + stats.byLang[n.lang] = (stats.byLang[n.lang] || 0) + 1; + } + if (n.role) { + stats.byRole[n.role] = (stats.byRole[n.role] || 0) + 1; + } + } + } + + traverse(node); + return stats; +} diff --git a/src/introspector/types.ts b/src/introspector/types.ts new file mode 100644 index 0000000..e4c80ef --- /dev/null +++ b/src/introspector/types.ts @@ -0,0 +1,88 @@ +export interface RepoSummary { + languages: ('python' | 'typescript')[]; + root: string; + analyzedAt: string; + files: FileInfo[]; + modules: ModuleInfo[]; + config: ConfigInfo; + git?: GitInfo; + tree?: FileTreeNode; +} + +export interface FileInfo { + path: string; + lang: 'python' | 'typescript' | 'other'; + role: 'source' | 'test' | 'config' | 'docs'; + size: number; + lastModified: string; +} + +export interface ModuleInfo { + path: string; + exports: ExportInfo[]; + imports: string[]; + complexity: 'low' | 'medium' | 'high'; +} + +export interface ExportInfo { + name: string; + kind: 'function' | 'class' | 'constant' | 'type'; + signature?: string; + docstring?: string; + lineNumber: number; + isAsync?: boolean; + isExported?: boolean; +} + +export interface ConfigInfo { + python?: { + entryPoints: string[]; + testFramework: 'pytest' | 'unittest' | 'none'; + hasTyping: boolean; + pyprojectToml: boolean; + setupPy: boolean; + }; + typescript?: { + entryPoints: string[]; + testFramework: 'vitest' | 'jest' | 'none'; + hasTypes: boolean; + packageJson: boolean; + tsconfig: boolean; + }; +} + +export interface GitInfo { + lastAnalyzedCommit: string; + currentCommit: string; + changedSince: string[]; + branch: string; + recentCommits?: CommitInfo[]; + fileHistory?: FileHistoryInfo[]; +} + +export interface CommitInfo { + hash: string; + shortHash: string; + author: string; + date: string; + message: string; + filesChanged: number; +} + +export interface FileHistoryInfo { + path: string; + commitCount: number; + lastModified: string; + contributors: string[]; +} + +export interface FileTreeNode { + name: string; + path: string; + type: 'file' | 'directory'; + children?: FileTreeNode[]; + lang?: 'python' | 'typescript' | 'other'; + role?: 'source' | 'test' | 'config' | 'docs'; +} + +export type Language = 'python' | 'typescript'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..90f5ac4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +}