diff --git a/lib/use-available-models.ts b/lib/use-available-models.ts new file mode 100644 index 0000000000000000000000000000000000000000..a25d9cbba3cc79f5c1f5f29e6bb1e588e390d6dc --- /dev/null +++ b/lib/use-available-models.ts @@ -0,0 +1,58 @@ +"use client" + +import { useCallback, useEffect, useState } from "react" +import { fetchModelsFromProvider, setDeploymentModel, setDiscoveredBrowserModel, type AISettings } from "@/lib/ai-settings" +import { sanitizeModels, type AvailableModel } from "@/lib/model-selection" + +/** Shared registry for settings and the main UI. Discovery never changes selection. */ +export function useAvailableModels(settings: AISettings) { + const [models, setModels] = useState([]) + const [defaultModel, setDefaultModel] = useState("") + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [revision, setRevision] = useState(0) + const refresh = useCallback(() => setRevision(n => n + 1), []) + + useEffect(() => { + setModels([]) + setDefaultModel("") + setDiscoveredBrowserModel("") + }, [settings.apiKey, settings.provider, settings.customBaseUrl]) + + useEffect(() => { + let cancelled = false + setLoading(true) + setError(null) + const timer = setTimeout(async () => { + try { + if (settings.apiKey.trim()) { + const items = await fetchModelsFromProvider(settings.provider, settings.apiKey.trim(), settings.customBaseUrl) + if (!cancelled) { + const available = sanitizeModels(items) + setModels(available) + setDiscoveredBrowserModel(available[0]?.id || "") + } + } else { + const response = await fetch("/api/v1/llm/models", { cache: "no-store" }) + if (!response.ok) throw new Error(response.status === 401 ? "Sign in to retrieve models." : "Could not retrieve the provider model list.") + const data = await response.json() + if (!cancelled) { + setDeploymentModel(data.defaultModel || data.models?.[0]?.id || "") + setDefaultModel(data.defaultModel || "") + setModels(sanitizeModels(data.models, data.defaultModel)) + setError(data.discoveryStatus === "available" ? null : + data.discoveryStatus === "auth_error" ? "Model provider authentication failed. Check OPENAI_API in deployment secrets." : + !data.configured ? "No model provider configured. Set OPENAI_URL, OPENAI_MODEL and OPENAI_API in the deployment environment." : + `Could not retrieve the provider model list. Using configured default model: ${data.defaultModel || "none"}`) + } + } + } catch { + if (!cancelled) setError("Could not retrieve the provider model list. Your selected model is unchanged.") + } finally { + if (!cancelled) setLoading(false) + } + }, 300) + return () => { cancelled = true; clearTimeout(timer) } + }, [settings.apiKey, settings.provider, settings.customBaseUrl, revision]) + return { models, defaultModel, loading, error, refresh } +} diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..d6e444e91a08badef838949c0e32a4a35a9dd9bd --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,40 @@ +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' +import { useState, useEffect } from 'react' +import type { TextBlock } from '@/components/tile-card' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} + +/** + * Returns the platform modifier key symbol: '⌘' on macOS/iOS, 'Ctrl' elsewhere. + * Starts as '⌘' on the server (SSR) and corrects on the client after mount. + */ +export function useModKey(): string { + const [mod, setMod] = useState('⌘') + useEffect(() => { + const isMac = /Mac|iPhone|iPad|iPod/.test(navigator.platform) + if (!isMac) setMod('Ctrl') + }, []) + return mod +} + +/** + * Returns the set of block IDs that are "connected" to the hovered block, + * based on shared category or influencedBy relationships. + * Used by tiling-area and kanban-area for the connection-hover dimming effect. + */ +export function getRelatedIds(hoveredId: string, blocks: TextBlock[]): Set { + const hovered = blocks.find(b => b.id === hoveredId) + if (!hovered) return new Set() + + const related = new Set([hoveredId]) + blocks.forEach(b => { + if (b.id === hoveredId) return + const hoveredPointsToB = hovered.influencedBy?.includes(b.id) ?? false + const bPointsToHovered = b.influencedBy?.includes(hoveredId) ?? false + if (hoveredPointsToB || bPointsToHovered) related.add(b.id) + }) + return related +} diff --git a/lib/validation/index.ts b/lib/validation/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..3234acf3ba1d5d1b9a7ab381392905b2e90c03c8 --- /dev/null +++ b/lib/validation/index.ts @@ -0,0 +1,29 @@ +import { KNOWLEDGE_EDGE_TYPES, KNOWLEDGE_NODE_KINDS, type EvidenceRecord, type HypothesisRecord, type KnowledgeEdge, type KnowledgeNode, type SynthesisResult, type Workspace } from "../domain/types" +import { canPromote, qualificationForScore, scoreHypothesis } from "../scoring/hypothesis" +export interface ValidationResult { valid:boolean; errors:string[] } +const result=(errors:string[]):ValidationResult=>({valid:errors.length===0,errors}) +export function validateNode(n:KnowledgeNode){const e:string[]=[];if(!n?.id)e.push("node id required");if(!KNOWLEDGE_NODE_KINDS.includes(n?.kind))e.push("invalid node kind");if(!n?.workspaceId)e.push("workspace required");if(!n?.title)e.push("title required");if(!n?.provenance?.origin||!n.provenance.createdAt)e.push("provenance required");if(!Number.isInteger(n?.version)||n.version<1)e.push("positive version required");return result(e)} +export function validateEdge(e:KnowledgeEdge,nodeIds?:Set){const x:string[]=[];if(!e?.id||!e.sourceId||!e.targetId)x.push("edge identity/endpoints required");if(!KNOWLEDGE_EDGE_TYPES.includes(e?.type))x.push("invalid edge type");if(e.sourceId===e.targetId)x.push("self edge prohibited");if(nodeIds&&(!nodeIds.has(e.sourceId)||!nodeIds.has(e.targetId)))x.push("unresolved endpoint");if(!e?.provenance?.origin)x.push("edge provenance required");return result(x)} +export function validateEvidence(e:EvidenceRecord){const x:string[]=[];if(!e.id||!e.workspaceId||!e.claim.trim())x.push("evidence identity/claim required");if(!Number.isInteger(e.strength)||e.strength<1||e.strength>5)x.push("strength must be 1..5");if(!Number.isFinite(e.weight)||e.weight<0)x.push("non-negative weight required");if(!e.sourceType)x.push("source type required");if(!e.ingestedAt||!e.provenance?.origin)x.push("evidence provenance required");return result(x)} +export function validateTheme(n:KnowledgeNode){const x=validateNode(n).errors;if(n.kind!=="theme")x.push("theme kind required");if(typeof n.metadata.clusterKey!=="string")x.push("clusterKey required");return result(x)} +export function validateSynthesis(s:SynthesisResult){const x:string[]=[];if(!/^S\d{3}$/.test(s.synthesisType))x.push("invalid synthesis type");if(!s.inputHash||!s.runId||!s.provenance?.origin)x.push("hash/run/provenance required");if(!Array.isArray(s.derivedFrom)||!Array.isArray(s.unknowns)||!Array.isArray(s.generatedHypotheses))x.push("synthesis arrays required");if(s.generationConfidence<0||s.generationConfidence>100)x.push("generation confidence out of range");return result(x)} +export function validateHypothesis(h:HypothesisRecord){const x:string[]=[];if(!h.id||!h.statement.trim()||!h.hypothesisType)x.push("hypothesis identity/statement/type required");if(h.evidenceScore< -100||h.evidenceScore>100)x.push("score out of range");if(h.confidence<0||h.confidence>100)x.push("confidence out of range");if(h.qualification!==qualificationForScore(h.evidenceScore))x.push("qualification mismatch");return result(x)} +export function validateExperiment(n:KnowledgeNode){const x=validateNode(n).errors;if(n.kind!=="experiment")x.push("experiment kind required");if(!n.metadata.threshold&&!n.metadata.passThreshold)x.push("experiment threshold required");return result(x)} +export function validateDecision(n:KnowledgeNode){const x=validateNode(n).errors;if(n.kind!=="decision")x.push("decision kind required");if(!n.metadata.rationale)x.push("decision rationale required");return result(x)} +export function validateBrainPromotion(w:Workspace){const x:string[]=[];for(const h of w.hypotheses.filter(h=>w.brain.promotedIds.includes(h.id)))if(!canPromote(h))x.push(`${h.id}: promotion gates not met`);return result(x)} +export function validateAdjudications(w: Workspace): ValidationResult { + const errors: string[] = [], ids = new Set() + const evidenceIds = new Set(w.evidence.flatMap(e => [e.id,...(e.aliases || [])])) + for (const a of w.adjudications || []) { + if (!a || !a.id || ids.has(a.id)) { errors.push("invalid/duplicate adjudication identity"); continue } + ids.add(a.id) + if (!w.hypotheses.some(h=>h.id===a.hypothesisId) || !evidenceIds.has(a.evidenceId)) errors.push(`adjudication ${a.id}: unresolved target`) + if (!["supports","contradicts","neutral","uncertain"].includes(a.relation) || !["direct","indirect","contextual"].includes(a.directness)) errors.push(`adjudication ${a.id}: invalid interpretation`) + if (![a.relevance,a.confidence].every(n=>Number.isFinite(n)&&n>=0&&n<=100)) errors.push(`adjudication ${a.id}: invalid interpretation certainty`) + if (!a.inputHash || !a.runId || !a.provider || !a.promptVersion || !a.schemaVersion || !Number.isFinite(Date.parse(a.createdAt)) || !Array.isArray(a.sourceEvidenceIds) || !Array.isArray(a.semanticContextIds)) errors.push(`adjudication ${a.id}: provenance required`) + } + return result(errors) +} +export function validateWorkspace(w:Workspace):ValidationResult{const errors:string[]=[];if(w.schemaVersion!==1)errors.push("unsupported schema");const ids=new Set(w.nodes.map(n=>n.id));for(const n of w.nodes)errors.push(...validateNode(n).errors.map(e=>`node ${n.id}: ${e}`));for(const e of w.edges)errors.push(...validateEdge(e,ids).errors.map(x=>`edge ${e.id}: ${x}`));const evid=new Set(w.evidence.map(e=>e.id));for(const e of w.evidence)errors.push(...validateEvidence(e).errors.map(x=>`evidence ${e.id}: ${x}`));for(const h of w.hypotheses){errors.push(...validateHypothesis(h).errors.map(x=>`hypothesis ${h.id}: ${x}`));for(const id of[...h.supportingEvidence,...h.contradictingEvidence])if(!evid.has(id))errors.push(`hypothesis ${h.id}: missing evidence ${id}`);const recomputed=scoreHypothesis(h,w.evidence,Date.parse(h.lastEvaluatedAt??w.updatedAt));if(recomputed.evidenceScore!==h.evidenceScore)errors.push(`hypothesis ${h.id}: score mismatch`)}for(const s of w.syntheses)errors.push(...validateSynthesis(s).errors.map(x=>`synthesis ${s.id}: ${x}`));errors.push(...validateBrainPromotion(w).errors,...validateAdjudications(w).errors);return result(errors)} +export const validateGitSnapshot=(w:Workspace)=>validateWorkspace(w) +export function validateWorkflowContracts(w:Workspace){const x:string[]=[];for(const t of w.researchTasks){if(!t.question||!t.objective||!Array.isArray(t.hypothesisIds))x.push(`task ${t.id}: invalid contract`);if(t.attempts>t.maxAttempts)x.push(`task ${t.id}: attempts exceeded`)}return result(x)} diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 0000000000000000000000000000000000000000..1d983713dbe011accb98263fa5c26a2097caefc4 --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,68 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: "standalone", + typescript: { + // Build errors are intentionally ignored — see CLAUDE.md + ignoreBuildErrors: true, + }, + images: { + unoptimized: true, + }, + async headers() { + return [ + { + // Apply security headers to every route + source: "/(.*)", + headers: [ + { + // Prevent framing (clickjacking) + key: "X-Frame-Options", + value: "DENY", + }, + { + // Stop MIME-type sniffing + key: "X-Content-Type-Options", + value: "nosniff", + }, + { + // Limit referrer info sent to third-party origins + key: "Referrer-Policy", + value: "strict-origin-when-cross-origin", + }, + { + // Permissions policy — disable features the app doesn't use + key: "Permissions-Policy", + value: "camera=(), microphone=(), geolocation=()", + }, + { + // Content Security Policy + // - default-src self: everything defaults to same-origin + // - script-src: Next.js needs 'unsafe-inline' + 'unsafe-eval' in dev; + // nonces are the proper fix but require custom server — this is the + // pragmatic baseline for a static/Vercel deployment. + // - connect-src: https: allows any user-configured custom endpoint + // - img-src: data URIs for inline images, blob for canvas exports + // - style-src unsafe-inline: Tailwind injects inline styles at runtime + key: "Content-Security-Policy", + value: [ + "default-src 'self'", + "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cloud.umami.is", + "style-src 'self' 'unsafe-inline'", + // Allow all HTTPS so user-configured custom base URLs (arbitrary + // OpenAI-compatible endpoints) are not blocked by CSP. Enumerating + // specific provider domains is incompatible with a custom-URL feature. + // http://localhost:* covers local providers (Ollama, LM Studio, vLLM). + "connect-src 'self' https: http://localhost:*", + "img-src 'self' data: blob: https://i.ytimg.com", + "font-src 'self' data:", + "frame-src https://www.youtube-nocookie.com https://www.youtube.com", + "frame-ancestors 'none'", + ].join("; "), + }, + ], + }, + ] + }, +} + +export default nextConfig diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..67b8f1225d71632650df25d9ebadb4fc67629c74 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,15172 @@ +{ + "name": "nodepad", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nodepad", + "version": "0.1.0", + "dependencies": { + "@hookform/resolvers": "^3.9.1", + "@radix-ui/react-accordion": "1.2.12", + "@radix-ui/react-alert-dialog": "1.1.15", + "@radix-ui/react-aspect-ratio": "1.1.8", + "@radix-ui/react-avatar": "1.1.11", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-context-menu": "2.2.16", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-hover-card": "1.1.15", + "@radix-ui/react-label": "2.1.8", + "@radix-ui/react-menubar": "1.1.16", + "@radix-ui/react-navigation-menu": "1.2.14", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-progress": "1.1.8", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-scroll-area": "1.2.10", + "@radix-ui/react-select": "2.2.6", + "@radix-ui/react-separator": "1.1.8", + "@radix-ui/react-slider": "1.3.6", + "@radix-ui/react-slot": "1.2.4", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tabs": "1.1.13", + "@radix-ui/react-toast": "1.2.15", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-toggle-group": "1.1.11", + "@radix-ui/react-tooltip": "1.2.8", + "@tailwindcss/typography": "^0.5.19", + "@types/d3": "^7.4.3", + "autoprefixer": "^10.4.20", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "1.1.1", + "d3": "^7.9.0", + "date-fns": "4.1.0", + "embla-carousel-react": "8.6.0", + "framer-motion": "^11.18.0", + "input-otp": "1.4.2", + "lucide-react": "^0.564.0", + "next": "^16.2.2", + "next-themes": "^0.4.6", + "react": "19.2.4", + "react-day-picker": "9.13.2", + "react-dom": "19.2.4", + "react-hook-form": "^7.54.1", + "react-markdown": "^10.1.0", + "react-resizable-panels": "^2.1.7", + "recharts": "2.15.0", + "remark-gfm": "^4.0.1", + "sonner": "^1.7.1", + "tailwind-merge": "^3.3.1", + "vaul": "^1.1.2", + "zod": "^3.24.1" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.2.0", + "@types/node": "^22", + "@types/react": "19.2.14", + "@types/react-dom": "19.2.3", + "eslint": "^9.39.5", + "eslint-config-next": "^16.3.8", + "postcss": "^8.5", + "shadcn": "^4.0.0", + "tailwindcss": "^4.2.0", + "tsx": "^4.23.15", + "tw-animate-css": "1.3.3", + "typescript": "5.7.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@antfu/ni": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-25.0.0.tgz", + "integrity": "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.0.0", + "fzf": "^0.5.2", + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "na": "bin/na.mjs", + "nci": "bin/nci.mjs", + "ni": "bin/ni.mjs", + "nlx": "bin/nlx.mjs", + "nr": "bin/nr.mjs", + "nun": "bin/nun.mjs", + "nup": "bin/nup.mjs" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@date-fns/tz": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", + "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", + "license": "MIT" + }, + "node_modules/@dotenvx/dotenvx": { + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.54.1.tgz", + "integrity": "sha512-41gU3q7v05GM92QPuPUf4CmUw+mmF8p4wLUh6MCRlxpCkJ9ByLcY9jUf6MwrMNmiKyG/rIckNxj9SCfmNCmCqw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "commander": "^11.1.0", + "dotenv": "^17.2.1", + "eciesjs": "^0.4.10", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "picomatch": "^4.0.2", + "which": "^4.0.0" + }, + "bin": { + "dotenvx": "src/cli/dotenvx.js" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/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==", + "dev": true, + "license": "ISC" + }, + "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@ecies/ciphers": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.5.tgz", + "integrity": "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A==", + "dev": true, + "license": "MIT", + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + }, + "peerDependencies": { + "@noble/ciphers": "^1.0.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.2", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "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/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@hono/node-server": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@hookform/resolvers": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", + "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.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/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", + "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.3.tgz", + "integrity": "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@next/env": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.2.tgz", + "integrity": "sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.8.tgz", + "integrity": "sha512-eCR9RcLTZrVS+K3+VOhi0xMdUUGNuriCHHhU1ZoABC/QQ/Gp0OWbLKQxV2/9DkmihYdYRcQWFmZXW8M1AMUXYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "4.9.1", + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/eslint-plugin-next/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/@next/eslint-plugin-next/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/@next/eslint-plugin-next/node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.2.tgz", + "integrity": "sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.2.tgz", + "integrity": "sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.2.tgz", + "integrity": "sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.2.tgz", + "integrity": "sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.2.tgz", + "integrity": "sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.2.tgz", + "integrity": "sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.2.tgz", + "integrity": "sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.2.tgz", + "integrity": "sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.8.tgz", + "integrity": "sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz", + "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", + "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.1.tgz", + "integrity": "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "postcss": "^8.5.6", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", + "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.3", + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/validate-npm-package-name": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", + "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.0.tgz", + "integrity": "sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/type-utils": "8.71.0", + "@typescript-eslint/utils": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.71.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.12.tgz", + "integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.0.tgz", + "integrity": "sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.0.tgz", + "integrity": "sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.71.0", + "@typescript-eslint/types": "^8.71.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.0.tgz", + "integrity": "sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.0.tgz", + "integrity": "sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.0.tgz", + "integrity": "sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/utils": "8.71.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.0.tgz", + "integrity": "sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.0.tgz", + "integrity": "sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.71.0", + "@typescript-eslint/tsconfig-utils": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.0.tgz", + "integrity": "sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.0.tgz", + "integrity": "sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.2.0.tgz", + "integrity": "sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "es-shim-unscopables": "^1.1.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", + "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/caniuse-lite": { + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/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==", + "dev": true, + "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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "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/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-jalali": { + "version": "4.1.0-0", + "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", + "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "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/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eciesjs": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.18.tgz", + "integrity": "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ecies/ciphers": "^0.2.5", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0" + }, + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "license": "ISC" + }, + "node_modules/embla-carousel": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "license": "MIT" + }, + "node_modules/embla-carousel-react": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz", + "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==", + "license": "MIT", + "dependencies": { + "embla-carousel": "8.6.0", + "embla-carousel-reactive-utils": "8.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz", + "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "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.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.8.tgz", + "integrity": "sha512-81vovwMe6NGnoFsl0KUJWzlS+y239i3fdsxs49gSEX3pDQW2cuKy+fxUPXNkQqqx6OCPheYPtBlSufc9aZ0k+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.3.8", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "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/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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/eslint/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/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "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/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuzzysort": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", + "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fzf": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fzf/-/fzf-0.5.2.tgz", + "integrity": "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphql": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.1.tgz", + "integrity": "sha512-gGgrVCoDKlIZ8fIqXBBb0pPKqDgki0Z/FSKNiQzSGj2uEYHr1tq5wmBegGwJx6QB5S5cM0khSBpi/JFHMCvsmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/hono": { + "version": "4.12.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz", + "integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.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==", + "dev": true, + "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/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/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/input-otp": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", + "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", + "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.564.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.564.0.tgz", + "integrity": "sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "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/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "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==", + "license": "MIT" + }, + "node_modules/msw": { + "version": "2.12.10", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.10.tgz", + "integrity": "sha512-G3VUymSE0/iegFnuipujpwyTM2GuZAKXNeerUSrG2+Eg391wW63xFs5ixWsK9MWzr1AGoSkYGmyAzNgbR3+urw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.41.2", + "@open-draft/deferred-promise": "^2.2.0", + "@types/statuses": "^2.0.6", + "cookie": "^1.0.2", + "graphql": "^16.12.0", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "rettime": "^0.10.1", + "statuses": "^2.0.2", + "strict-event-emitter": "^0.5.1", + "tough-cookie": "^6.0.0", + "type-fest": "^5.2.0", + "until-async": "^3.0.2", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.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==", + "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/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_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/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.2.tgz", + "integrity": "sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A==", + "license": "MIT", + "dependencies": { + "@next/env": "16.2.2", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.2.2", + "@next/swc-darwin-x64": "16.2.2", + "@next/swc-linux-arm64-gnu": "16.2.2", + "@next/swc-linux-arm64-musl": "16.2.2", + "@next/swc-linux-x64-gnu": "16.2.2", + "@next/swc-linux-x64-musl": "16.2.2", + "@next/swc-win32-arm64-msvc": "16.2.2", + "@next/swc-win32-x64-msvc": "16.2.2", + "sharp": "^0.34.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "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/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "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": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-day-picker": { + "version": "9.13.2", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.2.tgz", + "integrity": "sha512-IMPiXfXVIAuR5Yk58DDPBC8QKClrhdXV+Tr/alBrwrHUw0qDDYB1m5zPNuTnnPIr/gmJ4ChMxmtqPdxm8+R4Eg==", + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.4.1", + "date-fns": "^4.1.0", + "date-fns-jalali": "^4.1.0-0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-hook-form": { + "version": "7.71.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz", + "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-resizable-panels": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz", + "integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recharts": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.0.tgz", + "integrity": "sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rettime": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz", + "integrity": "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==", + "dev": true, + "license": "MIT" + }, + "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/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shadcn": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.0.5.tgz", + "integrity": "sha512-z0SOHEU1+ADam1UJHrgxJhUsOb0/jBoYc+u9mhWs071KrnORq48X7uCwG3mD2ysQEBtOfeK/MxMGsmzL5Jt+Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/ni": "^25.0.0", + "@babel/core": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/plugin-transform-typescript": "^7.28.0", + "@babel/preset-typescript": "^7.27.1", + "@dotenvx/dotenvx": "^1.48.4", + "@modelcontextprotocol/sdk": "^1.26.0", + "@types/validate-npm-package-name": "^4.0.2", + "browserslist": "^4.26.2", + "commander": "^14.0.0", + "cosmiconfig": "^9.0.0", + "dedent": "^1.6.0", + "deepmerge": "^4.3.1", + "diff": "^8.0.2", + "execa": "^9.6.0", + "fast-glob": "^3.3.3", + "fs-extra": "^11.3.1", + "fuzzysort": "^3.1.0", + "https-proxy-agent": "^7.0.6", + "kleur": "^4.1.5", + "msw": "^2.10.4", + "node-fetch": "^3.3.2", + "open": "^11.0.0", + "ora": "^8.2.0", + "postcss": "^8.5.6", + "postcss-selector-parser": "^7.1.0", + "prompts": "^2.4.2", + "recast": "^0.23.11", + "stringify-object": "^5.0.0", + "tailwind-merge": "^3.0.1", + "ts-morph": "^26.0.0", + "tsconfig-paths": "^4.2.0", + "validate-npm-package-name": "^7.0.1", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "shadcn": "dist/index.js" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sonner": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", + "integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.4", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", + "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-keys": "^1.0.0", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/stringify-object?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "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/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.25.tgz", + "integrity": "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.25" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.25.tgz", + "integrity": "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-morph": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", + "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.27.0", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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/tsx": { + "version": "4.23.15", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tw-animate-css": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.3.tgz", + "integrity": "sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "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-fest": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", + "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.5.tgz", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.0.tgz", + "integrity": "sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.71.0", + "@typescript-eslint/parser": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/utils": "8.71.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/until-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/kettanaito" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.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/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vaul": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..d269f2ed066332873fe8bfdb964f1b652ef0669d --- /dev/null +++ b/package.json @@ -0,0 +1,90 @@ +{ + "name": "nodepad", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint .", + "test": "tsx --test tests/**/*.test.ts", + "test:registry": "tsx --test tests/synthesis-registry.test.ts", + "validate:fixture": "tsx scripts/validate-fixture.ts", + "heartbeat": "tsx worker/heartbeat.ts", + "dev:all": "sh -c 'next dev & tsx worker/heartbeat.ts; wait'", + "test:predeploy:unit": "tsx --import ./scripts/predeploy-env.ts --test tests/**/*.test.ts", + "test:predeploy:smoke": "tsx scripts/predeploy-smoke.ts", + "test:predeploy:docker": "tsx scripts/predeploy-docker.ts", + "test:predeploy": "tsx scripts/predeploy.ts" + }, + "dependencies": { + "@hookform/resolvers": "^3.9.1", + "@radix-ui/react-accordion": "1.2.12", + "@radix-ui/react-alert-dialog": "1.1.15", + "@radix-ui/react-aspect-ratio": "1.1.8", + "@radix-ui/react-avatar": "1.1.11", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-context-menu": "2.2.16", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-hover-card": "1.1.15", + "@radix-ui/react-label": "2.1.8", + "@radix-ui/react-menubar": "1.1.16", + "@radix-ui/react-navigation-menu": "1.2.14", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-progress": "1.1.8", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-scroll-area": "1.2.10", + "@radix-ui/react-select": "2.2.6", + "@radix-ui/react-separator": "1.1.8", + "@radix-ui/react-slider": "1.3.6", + "@radix-ui/react-slot": "1.2.4", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tabs": "1.1.13", + "@radix-ui/react-toast": "1.2.15", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-toggle-group": "1.1.11", + "@radix-ui/react-tooltip": "1.2.8", + "@tailwindcss/typography": "^0.5.19", + "@types/d3": "^7.4.3", + "autoprefixer": "^10.4.20", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "1.1.1", + "d3": "^7.9.0", + "date-fns": "4.1.0", + "embla-carousel-react": "8.6.0", + "framer-motion": "^11.18.0", + "input-otp": "1.4.2", + "lucide-react": "^0.564.0", + "next": "^16.2.2", + "next-themes": "^0.4.6", + "react": "19.2.4", + "react-day-picker": "9.13.2", + "react-dom": "19.2.4", + "react-hook-form": "^7.54.1", + "react-markdown": "^10.1.0", + "react-resizable-panels": "^2.1.7", + "recharts": "2.15.0", + "remark-gfm": "^4.0.1", + "sonner": "^1.7.1", + "tailwind-merge": "^3.3.1", + "vaul": "^1.1.2", + "zod": "^3.24.1" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.2.0", + "@types/node": "^22", + "@types/react": "19.2.14", + "@types/react-dom": "19.2.3", + "eslint": "^9.39.5", + "eslint-config-next": "^16.3.8", + "postcss": "^8.5", + "shadcn": "^4.0.0", + "tailwindcss": "^4.2.0", + "tsx": "^4.23.15", + "tw-animate-css": "1.3.3", + "typescript": "5.7.3" + } +} diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000000000000000000000000000000000000..a869506ee48c89838ee770863c5d99ba97eeb930 --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + '@tailwindcss/postcss': {}, + }, +} + +export default config diff --git a/product-eval/.claude-plugin/marketplace.json b/product-eval/.claude-plugin/marketplace.json new file mode 100644 index 0000000000000000000000000000000000000000..54d17d9159b6104f5c846ac38ad5d33566cd23b9 --- /dev/null +++ b/product-eval/.claude-plugin/marketplace.json @@ -0,0 +1,16 @@ +{ + "name": "product-eval-marketplace", + "owner": { + "name": "Narek Gevorgyan" + }, + "plugins": [ + { + "name": "product-eval", + "source": "./", + "version": "0.14.2", + "description": "Evidence-gated product decisions: scope product context and evidence, then rank opportunities, decide on one bet, pressure-test plans, and write briefs/specs/scorecards.", + "homepage": "https://github.com/sparkline-ventures/product-eval", + "repository": "https://github.com/sparkline-ventures/product-eval" + } + ] +} diff --git a/product-eval/.claude-plugin/plugin.json b/product-eval/.claude-plugin/plugin.json new file mode 100644 index 0000000000000000000000000000000000000000..9551f8ea581a0361eb5b0bace115c8177952ba90 --- /dev/null +++ b/product-eval/.claude-plugin/plugin.json @@ -0,0 +1,19 @@ +{ + "name": "product-eval", + "version": "0.14.2", + "description": "Evidence-gated product decisions that start by scoping product context, evidence sources, and the desired deliverable before ranking opportunities, deciding on bets, pressure-testing plans, or writing outputs. Keeps evidence gathering, problem framing, Value/Confidence scoring, readiness gates, and audit files behind a simpler user experience.", + "author": { + "name": "Narek Gevorgyan" + }, + "license": "MIT", + "homepage": "https://github.com/sparkline-ventures/product-eval", + "repository": "https://github.com/sparkline-ventures/product-eval", + "keywords": [ + "product-management", + "prioritization", + "evidence", + "roadmap", + "decision-readiness", + "pressure-test" + ] +} diff --git a/product-eval/.codex-plugin/plugin.json b/product-eval/.codex-plugin/plugin.json new file mode 100644 index 0000000000000000000000000000000000000000..fdfd3d47864da63c05ec5a9c8b3e143008912e6a --- /dev/null +++ b/product-eval/.codex-plugin/plugin.json @@ -0,0 +1,36 @@ +{ + "name": "product-eval", + "version": "0.14.2", + "description": "Evidence-gated product decisions that start by scoping product context, evidence sources, and the desired deliverable before ranking opportunities, deciding on bets, pressure-testing plans, or writing outputs.", + "author": { + "name": "Narek Gevorgyan" + }, + "license": "MIT", + "homepage": "https://github.com/sparkline-ventures/product-eval", + "repository": "https://github.com/sparkline-ventures/product-eval", + "keywords": [ + "product-management", + "prioritization", + "evidence", + "roadmap", + "decision-readiness", + "pressure-test" + ], + "skills": "./skills/", + "interface": { + "displayName": "product-eval", + "shortDescription": "Scoped, evidence-gated product decisions", + "longDescription": "Set up product context and evidence sources, then rank opportunities, decide whether one bet is ready, pressure-test plans, and write briefs, build briefs, or scorecards from scoped product evidence.", + "developerName": "Narek Gevorgyan", + "category": "Productivity", + "capabilities": [ + "Interactive", + "Write" + ], + "defaultPrompt": [ + "Set up product-eval for my product.", + "Help me decide what to build next.", + "Help me prepare a decision report." + ] + } +} diff --git a/product-eval/.gitignore b/product-eval/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4d617af8b1d0d3105ef177f8ecb51c7564b6c3fb --- /dev/null +++ b/product-eval/.gitignore @@ -0,0 +1,12 @@ +# OS / editor cruft +.DS_Store + +# Python +__pycache__/ +*.pyc +.venv/ + +# Note: product-eval.plugin (the built package) is intentionally committed. +# evals/checks/validate_plugin.py verifies it byte-matches the source tree, +# so rebuild it after any change: rm -f product-eval.plugin && \ +# zip -r -X -q /tmp/pe.plugin . -x '*.plugin' && mv /tmp/pe.plugin product-eval.plugin diff --git a/product-eval/CONNECTORS.md b/product-eval/CONNECTORS.md new file mode 100644 index 0000000000000000000000000000000000000000..9f79b9e891a150413fee5af4c09e4c720ac58c92 --- /dev/null +++ b/product-eval/CONNECTORS.md @@ -0,0 +1,21 @@ +# Connectors + +This plugin is tool-agnostic in spirit: it works in terms of **evidence source categories**, and uses whatever you connect in each. Nothing is required, `discover-sources` detects what's available, CSV/upload is a universal fallback, and the plugin honestly caps decision confidence by the sources you actually have. + +The full category → example-tool mapping lives in `skills/discover-sources/references/source-catalog.md`. Summary: + +| Category | Example tools | Evidence role | +|---|---|---| +| Product / behavioral analytics | Amplitude, PostHog, Mixpanel, GA4 | first-party quantitative (strongest) | +| Reliability / logs / errors | Datadog, Sentry, New Relic | first-party quantitative | +| Support / tickets | Zendesk, Intercom, Freshdesk | ticket clusters | +| Voice of customer / calls | Fireflies, Gong, churn & NPS surveys | transcripts / surveys | +| CRM / sales | Salesforce, HubSpot | PFRs, lost-deal reasons | +| Feedback / idea mgmt | Productboard, Canny, Aha! | feature requests | +| Reviews / app stores | G2, Capterra, App/Play Store | third-party qualitative | +| Community | Reddit, Hacker News, Discord | third-party qualitative | +| Market / SEO / demand | Semrush, Ahrefs, SimilarWeb | market signal | +| Issue trackers / changelogs | GitHub, GitLab | bug/feature signal + freshness checks | +| Identity (CDP) | Segment, RudderStack | resolves the same user/account across the above | + +Connect any subset. The more first-party quantitative sources you connect (analytics, logs) and the more source-type diversity you have, the higher the confidence ceiling the plugin can reach. diff --git a/product-eval/DATA-CONTRACT.md b/product-eval/DATA-CONTRACT.md new file mode 100644 index 0000000000000000000000000000000000000000..ce940ada8d15d866cbf6fa41344098ca60839405 --- /dev/null +++ b/product-eval/DATA-CONTRACT.md @@ -0,0 +1,149 @@ +# Data Contract + +The file shapes every skill reads and writes inside an investigation scope at `.product-eval//`. Keeping these stable is what lets skills hand off reliably. Files are markdown with YAML front-matter (or YAML lists), human-readable and machine-parseable. + +All paths below are relative to `.product-eval//`, except `.product-eval/calibration.yml`, which is workspace-level. Exact computed fields (`weight`, `confidence`, `value`) are stored for audit, calibration, and scorecards. User-facing output should lead with bands and verdicts, not bare point estimates. + +## context.md (front-matter) +```yaml +--- +scope: tula-q3-eval +job: evaluate # explore | evaluate | validate | pressure-test | draft +stage: evaluate # sets the crispness bar: explore = lenient, evaluate/validate = strict +product: "one-line description" +north_star: "metric" +personas: [admin, end_user, buyer] +updated: 2026-06-26 +--- +``` + +## evidence/.md (one file per item) +```yaml +--- +id: E1 +claim: "state the pain, not the solution" +source_type: product_analytics # taxonomy below +source_ref: "Amplitude › Activation funnel" +persona: [client] +funnel_stage: activation +impact_type: activation +strength: 5 # 1-5 (evidence-quality rubric) +recency_days: 20 +weight: 12 # computed: base-by-strength × recency factor (evidence-quality rubric) +person_id: null +account_id: acct_acme # if identity-resolved +corroborated_by: [E2] +stale: false # true if a changelog/release supersedes it +--- +optional quote / notes +``` +`source_type` taxonomy: product_analytics, support_ticket, sales_call, churn_survey, customer_interview, internal_data, internal_observation, feature_request, app_review, community_post, social_media, competitor_analysis, market_research, blog_or_article. + +## identities.md (list) +```yaml +- entity: acct_acme + grain: account # person | account + matched_records: [intercom:org_812, hubspot:acct_55, amplitude:grp_acme] + method: email_domain # email | user_id | email_domain | fuzzy + match_confidence: high # high | medium | low +``` + +## themes.md (list) +```yaml +- id: T1 + name: "problem-framed, 5-10 words" + members: [E1, E2] + personas: [client] + distinct_accounts: 9 + total_weight: 19 + source_types: [product_analytics, support_ticket] + trend: up # emerging | up | flat | down | regression + corroboration: cross-source + contradictions: [] + confidence: 67 # computed (scoring-framework Confidence formula) + confidence_band: moderate # low | moderate | high; preferred for chat output + gaps: ["spam vs ignored?", "funnel confound: agency-abandoned projects"] +``` + +## scores.md (list) +```yaml +- problem: T1 + value: 72 # 0-100 (scoring-framework: persona + funnel + frequency + competitive) + value_inputs: {persona: 15, funnel: 22, frequency: 25, competitive: 10} + value_band: high # low | moderate | high; preferred for chat output + confidence: 67 # 0-100 (scoring-framework Confidence formula) + confidence_band: moderate # low | moderate | high; preferred for chat output + recommendation: "Run a research sprint" # ranking tier from the score-to-recommendation matrix + rationale: "short reasoning" + override: null # {field, to, reason} if a human overrides +``` + +## decisions-log.md (append-only entries) +```yaml +- date: 2026-06-26 + item: T1 + event: readiness # readiness | override | critique | drift | doc + verdict: "Run a research sprint" + confidence: 67 + confidence_band: moderate + evidence: [E1, E2] + open: ["spam vs ignored", "funnel confound"] + contested: [] + note: "short note" + outcome: # optional; appended later once the bet has closed, read by calibrate + problem_was_real: confirmed # confirmed | refuted | partial (the calibration target) + solution_outcome: succeeded # succeeded | failed | mixed (context only, does not tune constants) +``` + +`outcome` is added to a `readiness` entry only when the result is known (often a quarter later). `calibrate` reads it to score the constants: it tunes against `problem_was_real`, never `solution_outcome`. See the calibrate reference. + +## calibration.yml (workspace-level, optional) + +Lives at `.product-eval/calibration.yml`, workspace/product level, **not** per-scope. Overrides the shipped scoring defaults for this team/product; written by `calibrate`, read by the scoring skills on top of the defaults (anything omitted → shipped default). Schema in the calibrate reference. Absent → the universal defaults apply. + +## Computed fields, single sources of truth +- `strength` (1-5) and `weight`, `skills/decision-readiness/references/evidence-quality.md`. +- `confidence` (0-100), `skills/prioritize-backlog/references/scoring-framework.md` (the Confidence formula); stored exactly, displayed as `confidence_band` (Low/Moderate/High) with an optional bounded ±10 logged adjustment, and overridable via `.product-eval/calibration.yml`. +- `value` (0-100) and the recommendation matrix, same scoring-framework reference; store exact `value`, display `value_band` in chat. + +Keep these schemas **additive**: skills may add fields, but must not rename or remove the ones above. + +## Validation (preflight conformance check) + +Skills trust each other's files. A malformed `evidence/`, `themes.md`, or `scores.md` must fail **loudly**, not degrade a downstream skill silently. So every consuming skill runs this preflight on the files it is about to read, and if any check fails it **stops and reports the exact file, field, and violation** rather than guessing past it or proceeding on a partial read. The checks are mechanical (no judgement), so a team can also run them as a script; this section is the single source of truth for what "conformant" means. + +**Band rule (used everywhere):** `band(x)` = Low if `x < 40`, Moderate if `40 <= x <= 69`, High if `x >= 70`. A stored `*_band` that disagrees with its stored number is a violation. + +**`evidence/.md`** +- Required keys present: `id`, `claim`, `source_type`, `strength`, `weight`, `stale`. +- `id` is unique across the folder and matches the filename (`E1` in `E1.md`). +- `source_type` is in the taxonomy above; `strength` is an integer 1 to 5; `weight` is a number `>= 0`; `recency_days` (if present) is a non-negative integer; `stale` is a boolean. +- Every id in `corroborated_by` / `contradicts` resolves to a real evidence file. +- `person_id` / `account_id`, when not null, resolve to an entity in `identities.md`. + +**`themes.md`** +- Each theme has a unique `id`, a `name`, and a non-empty `members` list; every member resolves to a real evidence id. +- `total_weight` equals the sum of member weights within rounding tolerance (`+/- 1`). +- `source_types` is a subset of the taxonomy and is consistent with the members' source types. +- `confidence` is present (0 to 100) and `confidence_band` satisfies the band rule. +- Every id in `contradictions` resolves to a real evidence id. + +**`scores.md`** +- Each entry's `problem` resolves to a theme id in `themes.md`. +- `value` is 0 to 100 and `value_inputs` (`persona + funnel + frequency + competitive`) sums to `value`. +- `value_band` and `confidence_band` satisfy the band rule. +- `confidence` equals the same item's `confidence` in `themes.md` (one source of truth; readiness reads, it does not recompute a different number). +- `recommendation` is one of the four verdicts (Decide now / Run a research sprint / Do not commit yet / Deprioritize) and matches what the score-to-recommendation matrix produces from (`value`, `confidence`), unless `override` is set and carries a reason. +- `override` is null or has `{field, to, reason}`. + +**`decisions-log.md`** +- Append-only: existing entries are never edited or deleted, only added to. +- Each entry has `date`, `item`, `event`, `verdict`; `verdict` is in the four-verdict vocabulary; `event` is one of readiness / override / critique / drift / doc. +- Every id in `evidence` / `contested` resolves to a real evidence id; `confidence_band` satisfies the band rule. +- If `outcome` is present: `problem_was_real` is one of confirmed / refuted / partial, and `solution_outcome` (if present) is one of succeeded / failed / mixed. + +**Cross-file** +- Every theme referenced by `scores.md` and `decisions-log.md` exists in `themes.md`. +- `context.md` front-matter is present and has at least `scope`, `job`, `stage`. + +A passing preflight means the files are structurally sound and internally consistent; it does **not** vouch for whether the evidence is true. These checks are also implemented as an optional script, `scripts/validate_scope.py` (run `python scripts/validate_scope.py .product-eval//`; needs PyYAML, exits non-zero on any violation), for CI or a pre-commit hook. `examples/activation-q3/` is a conformant fixture to test the checks against. diff --git a/product-eval/LICENSE b/product-eval/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d2440b6b6cbe8d3920e7b6aeeee14cf7bda2acae --- /dev/null +++ b/product-eval/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Narek Gevorgyan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/product-eval/README.md b/product-eval/README.md new file mode 100644 index 0000000000000000000000000000000000000000..25590eff2a857b4b7bc256a70d6cd4500bd0d98c --- /dev/null +++ b/product-eval/README.md @@ -0,0 +1,156 @@ +# product-eval + +**Find what's worth building next, with the evidence to prove it.** + +![A product-eval decision scorecard, generated from real support tickets](docs/scorecard.png) + +product-eval discovers and analyzes real customer problems from the tools where they already live: support tickets (Zendesk), CRM (Salesforce, HubSpot), issue trackers (Jira), product analytics, and reviews. It weighs the evidence, ranks what matters, and builds a prioritized, evidence-backed narrative of the problem worth solving. + +When a bet is ready, hand it off: export a brief or decision memo, or pass a build spec straight to a wireframing tool or an AI coding agent like Codex or Claude Code to ship an MVP. It runs in both Claude Code and Codex, and keeps the rigor (evidence weighting, readiness gates, audit files) behind a simple, outcome-first chat. + +It helps you answer four practical questions: + +- **Rank opportunities:** what should we build next? +- **Decide on one bet:** is this worth building now, or do we need more proof? +- **Pressure-test a plan:** where is this roadmap, PRD, or strategy weak before we commit? +- **Write the output:** turn an approved decision into a brief, decision memo, build spec, or visual scorecard. + +The plugin keeps the rigor behind the scenes: it can discover available sources, gather and weight evidence, synthesize themes into buildable problems, score Value and Confidence, check decision readiness, and produce decision artifacts. Users should not need to choose those internal steps directly; they can describe the outcome they want. + +## Install + +![The product-eval plugin and its 14 skills](docs/install-page.png) + +**Cowork:** open the `.plugin` file. It renders as an installable card; click to add it. + +**Claude Code:** add this directory or its hosting repo as a plugin marketplace, then install: + +```text +/plugin marketplace add +/plugin install product-eval +``` + +**Codex:** this source is Codex-compatible through `.codex-plugin/plugin.json`. For Codex marketplace installation, place or symlink this folder as `/plugins/product-eval`, make sure that marketplace root is registered with Codex, then install `product-eval` from that marketplace. This repo cleanup does not create or edit global Codex marketplace config such as `~/.agents/plugins/marketplace.json`. + +**Quick install (either tool, no marketplace):** clone this repo and run `./install.sh`. It symlinks the 14 skills into `~/.claude/skills` and `~/.agents/skills`, so a later `git pull` keeps them current. In Codex, invoke a skill explicitly with `$` (for example `$start`). + +After installing, start by setting up the product context and decision: + +- "Set up product-eval for my product." +- "Help me decide what to build next." +- "Help me prepare a decision report." +- "Pressure-test this Q3 roadmap using the evidence in our current scope." + +On first run, product-eval should ask for the product, target users, decision goal, scope/time horizon, available evidence sources, and the metric or business constraint that should shape the recommendation. It should not rank a vague area like "onboarding" or judge a bet like "SSO" until the product context and evidence plan are clear, unless you explicitly ask for an outside-in first pass with a lower confidence ceiling. + +Each step should end with a **Next move** that points toward delivery: upload the evidence, rank the candidates, run the readiness call, pressure-test the plan, write the decision memo, render the scorecard, or hand a build brief to Codex/design. + +## 60-second demo + +Point it at your customer signal and ask for the outcome. For example, hand it a support-ticket export in Codex: + +> "These are our support tickets. Can you analyze them?" + +product-eval runs the full chain (gather and weight evidence, synthesize themes, score Value and Confidence, run the readiness gate) and returns a ranked shortlist with a verdict on each. + +![Ranking opportunities from a Zendesk ticket export, in Codex](docs/demo-rank.png) + +Ask for a scorecard and it builds a self-contained, shareable HTML dashboard: + +> "Can you build a scorecard?" + +![product-eval generating a decision scorecard from the evidence](docs/demo-scorecard-codex.png) + +No data of your own? It also works top-down from a domain ("research the observability tooling space and tell me what's worth building"), reasoning from public signal and stating the resulting confidence ceiling. You can also open a ready-made example with zero setup: `examples/activation-q3/scorecard.html`. + +## Outcome Modes + +### Rank Opportunities + +Use this when you have a product area, evidence pile, or backlog and want a ranked shortlist. The plugin gathers or reads evidence, turns themes into buildable problem statements, scores each item by Value and Confidence, and returns a ranked table with the best next move. + +### Decide On One Bet + +Use this when you have one idea or problem and need a yes/no readiness call. The output leads with one of three outcomes: + +- **Decide now:** enough credible, diverse evidence to commit. +- **Run a research sprint:** promising, but one specific proof gap should be closed first. +- **Do not commit yet:** evidence is too weak, vague, stale, or single-source. + +Evidence math is kept visible for audit, but the decision is presented as a banded verdict rather than false precision. + +### Pressure-Test A Plan + +Use this when a roadmap, PRD, strategy, or bet already exists and you want the weak spots surfaced before committing. The output is blocking issues, suggestions, strengths, and a ready / needs-fixes / not-ready verdict. + +### Write The Output + +Use this after a decision has enough evidence or when you need a decision artifact: + +- **Opportunity Brief:** persuade and align humans around the problem, why now, impact, and ask. +- **Decision Memo:** force a specific yes/no or option choice with criteria, metrics, tradeoffs, and deadline. +- **Build Brief:** hand an approved decision to a code or design agent with testable requirements. +- **Scorecard:** render ranked opportunities and verdicts as a shareable HTML dashboard. + +Use **critique** when you want an independent adversarial read of findings, a problem statement, a brief, or an FAQ. Use **pressure-test** when you already have a roadmap, PRD, plan, or bet and want to find the weak spots before committing. + +## Decision Model + +A decision passes a two-stage gate: + +1. **Sufficiency gate:** blocks if evidence weight is too low, all evidence is weak, evidence comes from only one independent source type, or the problem statement is vague. +2. **Confidence verdict:** combines evidence weight, source diversity, contradictions, and Value into a recommendation. + +The chat output should show **bands first**: Low / Moderate / High Confidence and the resulting verdict. Exact Value and Confidence numbers are stored in `.product-eval//` files for audit, calibration, and scorecards. + +## State: `.product-eval//` + +All investigation state lives inside the user's project repo so it versions with code and can be reviewed in pull requests. Each investigation gets its own scope: + +```text +.product-eval/ + / + context.md business context: product, north star, personas + sources.md discovered sources, coverage, confidence ceiling + evidence/ one file per evidence item + identities.md resolved person/account map across sources + themes.md problem-framed themes with gaps and confidence + scores.md exact Value + Confidence numbers for audit + decisions-log.md append-only verdicts, overrides, drift, docs + snapshots/ timestamped copies for drift checks + docs/ briefs and decision memos + build/ agent-ready build/design briefs + calibration.yml optional workspace-level scoring overrides +``` + +If `.product-eval//` does not exist, the first run creates or proposes a short scope name. Skills degrade gracefully when files or sources are missing and state the resulting confidence ceiling. + +## Validation + +Run the plugin/package checks: + +```text +python3 evals/checks/validate_plugin.py +``` + +Run the scope fixture validator after installing its dependency: + +```text +python3 -m pip install -r scripts/requirements.txt +python3 scripts/validate_scope.py examples/activation-q3 +``` + +## Design Principles + +- **Outcome-first UX.** Users ask for Rank, Decide, Pressure-test, or Write; internal skills do the routing. +- **Evidence as described, not ground truth.** The plugin rates credibility, diversity, and recency. It does not prove that every cited number is objectively true. +- **Honest ceilings.** Every verdict states the maximum confidence the connected or provided sources allow and how to raise it. +- **Evidence is weighted, not counted.** Strong first-party findings outweigh many weak anecdotes. +- **Plain files, no daemon.** Skills read and write markdown/YAML under `.product-eval//`; git provides history. + +## Roadmap + +- Calibration and reality-testing against more closed decisions. +- Trigger eval hardening for the outcome modes and scorecard. +- Cross-scope portfolio view: a roll-up of what is decided, waiting on evidence, or drifting across investigations. +- Scale-out clustering for large evidence corpora when map-reduce synthesis is not enough. diff --git a/product-eval/evals/checks/validate_plugin.py b/product-eval/evals/checks/validate_plugin.py new file mode 100644 index 0000000000000000000000000000000000000000..059263db79e733ea8c490da173bd28cff972a878 --- /dev/null +++ b/product-eval/evals/checks/validate_plugin.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 +"""Validate product-eval plugin source, evals, and packaged artifact.""" + +from __future__ import annotations + +import json +import re +import sys +import zipfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +PACKAGE = ROOT / "product-eval.plugin" +PARENT_PACKAGE = ROOT.parent / "product-eval.plugin" +CLAUDE_MANIFEST = ROOT / ".claude-plugin" / "plugin.json" +CLAUDE_MARKETPLACE = ROOT / ".claude-plugin" / "marketplace.json" +CODEX_MANIFEST = ROOT / ".codex-plugin" / "plugin.json" +EXCLUDED_PATH_PREFIXES = ( + "skills/critique/", + "evals/trigger/critique.json", + "evals/checks/validate_plugin.py", +) +IGNORED_SOURCE_NAMES = {".DS_Store", "product-eval.plugin"} +LEGACY_PM_PATHS = ( + ".pm/context", + ".pm/sources", + ".pm/evidence", + ".pm/identities", + ".pm/themes", + ".pm/scores", + ".pm/decisions-log", + ".pm/snapshots", +) +ALLOWED_FRONTMATTER = {"name", "description", "license", "allowed-tools", "metadata"} + + +def fail(message: str) -> None: + print(f"ERROR: {message}", file=sys.stderr) + raise SystemExit(1) + + +def load_json(path: Path): + try: + with path.open("r", encoding="utf-8") as handle: + return json.load(handle) + except Exception as exc: # noqa: BLE001 - validation script should report exact file. + fail(f"invalid JSON in {path.relative_to(ROOT)}: {exc}") + + +def read_archive_json(archive: zipfile.ZipFile, path: str): + try: + return json.loads(archive.read(path)) + except Exception as exc: # noqa: BLE001 - package validator should report exact file. + fail(f"packaged {path} missing or invalid: {exc}") + + +def manifest_identity(payload: dict, label: str) -> tuple[str, str]: + name = payload.get("name") + version = payload.get("version") + if not isinstance(name, str) or not name.strip(): + fail(f"{label} missing name") + if not isinstance(version, str) or not version.strip(): + fail(f"{label} missing version") + return name, version + + +def marketplace_entry(payload: dict, label: str, expected_name: str) -> dict: + plugins = payload.get("plugins") + if not isinstance(plugins, list) or not plugins: + fail(f"{label} missing plugins array") + for entry in plugins: + if isinstance(entry, dict) and entry.get("name") == expected_name: + return entry + if len(plugins) == 1 and isinstance(plugins[0], dict): + return plugins[0] + fail(f"{label} missing plugin entry for {expected_name!r}") + + +def validate_identity(label: str, name: str, version: str, expected_name: str, expected_version: str) -> None: + if name != expected_name: + fail(f"{label} name mismatch: {name!r} != {expected_name!r}") + if version != expected_version: + fail(f"{label} version mismatch: {version!r} != {expected_version!r}") + + +def validate_json() -> None: + for path in sorted((ROOT / "evals").rglob("*.json")): + load_json(path) + + +def validate_manifest_identity() -> None: + if not CODEX_MANIFEST.exists(): + fail("missing .codex-plugin/plugin.json") + + claude_manifest = load_json(CLAUDE_MANIFEST) + codex_manifest = load_json(CODEX_MANIFEST) + marketplace = load_json(CLAUDE_MARKETPLACE) + + expected_name, expected_version = manifest_identity( + claude_manifest, ".claude-plugin/plugin.json" + ) + if ROOT.name != expected_name: + fail(f"plugin folder name {ROOT.name!r} does not match manifest name {expected_name!r}") + + codex_name, codex_version = manifest_identity(codex_manifest, ".codex-plugin/plugin.json") + validate_identity( + ".codex-plugin/plugin.json", + codex_name, + codex_version, + expected_name, + expected_version, + ) + + entry = marketplace_entry( + marketplace, ".claude-plugin/marketplace.json", expected_name + ) + entry_name = entry.get("name") + entry_version = entry.get("version") + validate_identity( + ".claude-plugin/marketplace.json plugin entry", + entry_name, + entry_version, + expected_name, + expected_version, + ) + + +def validate_route_coverage() -> None: + skills = sorted(path.parent.name for path in (ROOT / "skills").glob("*/SKILL.md")) + triggering = load_json(ROOT / "evals" / "triggering.json") + listed = sorted(triggering.get("skills", [])) + if listed != skills: + fail(f"triggering.json skills do not match source skills: listed={listed}, source={skills}") + + trigger_files = sorted(path.stem for path in (ROOT / "evals" / "trigger").glob("*.json")) + if trigger_files != skills: + fail(f"per-skill trigger evals do not match source skills: evals={trigger_files}, source={skills}") + + for skill in skills: + cases = load_json(ROOT / "evals" / "trigger" / f"{skill}.json") + if not isinstance(cases, list) or not cases: + fail(f"missing trigger cases for {skill}") + positives = [case for case in cases if case.get("should_trigger") is True] + negatives = [case for case in cases if case.get("should_trigger") is False] + if not positives or not negatives: + fail(f"{skill} trigger eval must include positive and negative cases") + + +def parse_simple_frontmatter(path: Path) -> dict[str, str]: + text = path.read_text(encoding="utf-8") + if not text.startswith("---\n"): + fail(f"{path.relative_to(ROOT)} missing YAML frontmatter") + end = text.find("\n---", 4) + if end == -1: + fail(f"{path.relative_to(ROOT)} has unterminated YAML frontmatter") + + data: dict[str, str] = {} + for raw_line in text[4:end].splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + if ":" not in line: + fail(f"{path.relative_to(ROOT)} has unsupported frontmatter line: {raw_line!r}") + key, value = line.split(":", 1) + key = key.strip() + if key not in ALLOWED_FRONTMATTER: + fail(f"{path.relative_to(ROOT)} has unexpected frontmatter key {key!r}") + data[key] = value.strip().strip('"').strip("'") + return data + + +def validate_skill_frontmatter() -> None: + for skill_md in sorted((ROOT / "skills").glob("*/SKILL.md")): + rel = skill_md.relative_to(ROOT) + frontmatter = parse_simple_frontmatter(skill_md) + name = frontmatter.get("name") + description = frontmatter.get("description") + if not name: + fail(f"{rel} missing name") + if not re.fullmatch(r"[a-z0-9-]{1,64}", name): + fail(f"{rel} has invalid skill name {name!r}") + if name != skill_md.parent.name: + fail(f"{rel} name {name!r} does not match folder {skill_md.parent.name!r}") + if not description: + fail(f"{rel} missing description") + if "<" in description or ">" in description: + fail(f"{rel} description contains angle brackets") + if len(description) > 1024: + fail(f"{rel} description is too long ({len(description)} chars)") + lower_description = description.lower() + if "do not trigger" in lower_description or "deprecated" in lower_description: + fail(f"{rel} is still packaged as a routable skill but its description says not to trigger it") + + +def validate_pm_paths() -> None: + for path in sorted(ROOT.rglob("*")): + if not path.is_file(): + continue + rel = path.relative_to(ROOT).as_posix() + if rel.startswith(EXCLUDED_PATH_PREFIXES): + continue + if path.name in IGNORED_SOURCE_NAMES: + continue + try: + text = path.read_text(encoding="utf-8") + except UnicodeDecodeError: + continue + for legacy in LEGACY_PM_PATHS: + if legacy in text: + fail(f"legacy unscoped path {legacy!r} found in {rel}") + + +def source_files() -> set[str]: + files: set[str] = set() + for path in ROOT.rglob("*"): + if not path.is_file(): + continue + if path.name in IGNORED_SOURCE_NAMES: + continue + files.add(path.relative_to(ROOT).as_posix()) + return files + + +def validate_package() -> None: + if PARENT_PACKAGE.exists(): + fail(f"stale parent-level product-eval.plugin exists at {PARENT_PACKAGE}") + if not PACKAGE.exists(): + fail("product-eval.plugin is missing") + + source = source_files() + with zipfile.ZipFile(PACKAGE) as archive: + packaged_files = { + name + for name in archive.namelist() + if not name.endswith("/") + and Path(name).name not in IGNORED_SOURCE_NAMES + } + missing = sorted(source - packaged_files) + extra = sorted(packaged_files - source) + if missing or extra: + fail(f"package/source file mismatch: missing={missing}, extra={extra}") + + for rel in sorted(source): + if archive.read(rel) != (ROOT / rel).read_bytes(): + fail(f"package/source content mismatch: {rel}") + + packaged_claude_manifest = read_archive_json(archive, ".claude-plugin/plugin.json") + packaged_codex_manifest = read_archive_json(archive, ".codex-plugin/plugin.json") + packaged_marketplace = read_archive_json(archive, ".claude-plugin/marketplace.json") + + source_manifest = load_json(CLAUDE_MANIFEST) + expected_name, expected_version = manifest_identity( + source_manifest, ".claude-plugin/plugin.json" + ) + for label, payload in ( + ("packaged .claude-plugin/plugin.json", packaged_claude_manifest), + ("packaged .codex-plugin/plugin.json", packaged_codex_manifest), + ): + name, version = manifest_identity(payload, label) + validate_identity(label, name, version, expected_name, expected_version) + + packaged_entry = marketplace_entry( + packaged_marketplace, "packaged .claude-plugin/marketplace.json", expected_name + ) + validate_identity( + "packaged .claude-plugin/marketplace.json plugin entry", + packaged_entry.get("name"), + packaged_entry.get("version"), + expected_name, + expected_version, + ) + + +def main() -> None: + validate_json() + validate_manifest_identity() + validate_route_coverage() + validate_skill_frontmatter() + validate_pm_paths() + validate_package() + print("product-eval validation passed") + + +if __name__ == "__main__": + main() diff --git a/product-eval/evals/trigger/calibrate.json b/product-eval/evals/trigger/calibrate.json new file mode 100644 index 0000000000000000000000000000000000000000..efc5bc05952646d367d4bfec4cdd684cb9b58793 --- /dev/null +++ b/product-eval/evals/trigger/calibrate.json @@ -0,0 +1,90 @@ +[ + { + "query": "the confidence numbers feel too generous lately, can we tune the thresholds against these 8 past launches where we already know how they turned out", + "should_trigger": true + }, + { + "query": "our model keeps flashing Go on stuff that later flopped. here are 12 shipped bets with what actually happened, fix the weights so it stops doing that", + "should_trigger": true + }, + { + "query": "i think the gate cutoff is too loose. check it against past calls and tell me what number to move it to", + "should_trigger": true + }, + { + "query": "validate the scoring against calibration/cases.md and flag where the verdict didn't match the real outcome", + "should_trigger": true + }, + { + "query": "we've got a sheet of 15 old feature decisions with their actual results. use them to see if our evidence weights are even right", + "should_trigger": true + }, + { + "query": "the crispness bar feels off, sanity-check it against the problems we actually shipped vs the ones we killed", + "should_trigger": true + }, + { + "query": "three things it scored Research turned out great. that's a false-negative pattern, right? adjust the constants", + "should_trigger": true + }, + { + "query": "backtest the scoring formula on our 2025 launches and recommend new threshold values", + "should_trigger": true + }, + { + "query": "are the diversity factors and the gate threshold actually calibrated? here are 10 past decisions with known outcomes to test against", + "should_trigger": true + }, + { + "query": "i don't trust the confidence math anymore. we have a stack of past bets and their outcomes, figure out where the scoring is systematically wrong", + "should_trigger": true + }, + { + "query": "is it worth building SSO before our enterprise push, and do we even have enough data to make that call yet?", + "should_trigger": false + }, + { + "query": "does the SSO bet have enough evidence behind it to greenlight, and what's still missing?", + "should_trigger": false + }, + { + "query": "the confidence score on this one opportunity looks way too high. is it actually ready to decide or not?", + "should_trigger": false + }, + { + "query": "rank these 8 feature ideas by what's most worth doing, we've got analytics plus support data", + "should_trigger": false + }, + { + "query": "review our north-star and activation metrics for the month and flag what moved", + "should_trigger": false + }, + { + "query": "our activation rate dropped 4 points last week, dig into the numbers and tell me why", + "should_trigger": false + }, + { + "query": "poke holes in our Q3 roadmap before the leadership review on friday", + "should_trigger": false + }, + { + "query": "grade how crisp this problem statement is: 'onboarding is bad'", + "should_trigger": false + }, + { + "query": "has our top opportunity drifted since last month, did confidence actually improve or are we just spinning?", + "should_trigger": false + }, + { + "query": "pull the recurring complaints from zendesk, our g2 reviews, and the churn survey and grade how solid each signal is", + "should_trigger": false + }, + { + "query": "turn this pile of 30 user interview transcripts into the top themes ranked by how often they came up", + "should_trigger": false + }, + { + "query": "build me a scorecard of this quarter's product metrics with the trends called out for the exec review", + "should_trigger": false + } +] diff --git a/product-eval/evals/trigger/critique.json b/product-eval/evals/trigger/critique.json new file mode 100644 index 0000000000000000000000000000000000000000..effd012b31e408b2309684dace4bdbca7944f726 --- /dev/null +++ b/product-eval/evals/trigger/critique.json @@ -0,0 +1,90 @@ +[ + { + "query": "grade how crisp this problem statement is: 'onboarding is bad'", + "should_trigger": true + }, + { + "query": "is this one-liner actually sharp enough to take into a brief? 'we need better reporting'", + "should_trigger": true + }, + { + "query": "poke holes in the findings from our churn investigation before I write them up", + "should_trigger": true + }, + { + "query": "red-team this FAQ we drafted for the SSO opportunity brief, attack the weak answers", + "should_trigger": true + }, + { + "query": "is this crisp enough? 'users want a faster dashboard' - score it and tell me what's missing", + "should_trigger": true + }, + { + "query": "before we move from evidence to scoring, can someone fresh tear into our themes handoff and flag what's not ready", + "should_trigger": true + }, + { + "query": "put these findings in front of a skeptical customer, a CFO, and an engineer and see where they fall apart", + "should_trigger": true + }, + { + "query": "gate this opportunity brief before it advances to the decision stage, don't rubber-stamp it", + "should_trigger": true + }, + { + "query": "my one-liner is 'reduce friction in checkout' - how tight is it and can you rewrite it sharper", + "should_trigger": true + }, + { + "query": "give the conclusions in this synthesis doc a fresh adversarial read from someone who wasn't in the room", + "should_trigger": true + }, + { + "query": "does this problem statement sit at the right altitude and actually name a persona, or is it mush?", + "should_trigger": true + }, + { + "query": "poke holes in our Q3 roadmap before the leadership review on friday", + "should_trigger": false + }, + { + "query": "stress-test our whole go-to-market plan for the enterprise tier before we commit to it", + "should_trigger": false + }, + { + "query": "adversarially pressure-test this PRD and the bet behind it before we green-light the sprint", + "should_trigger": false + }, + { + "query": "let's brainstorm some new directions for our analytics product, i want a sharp thinking partner", + "should_trigger": false + }, + { + "query": "be my sparring partner and help me explore the problem space around activation, throw ideas at me", + "should_trigger": false + }, + { + "query": "is it worth building SSO before our enterprise push? do we even have the data to call it?", + "should_trigger": false + }, + { + "query": "synthesize the evidence we just pulled into the main problem-themes", + "should_trigger": false + }, + { + "query": "i have a pile of 30 user-interview transcripts in a folder, pull out the main themes and insights for me", + "should_trigger": false + }, + { + "query": "here are our raw survey responses and a stack of support tickets, make sense of them into findings", + "should_trigger": false + }, + { + "query": "pull the recurring complaints from zendesk, our g2 reviews, and the churn survey and grade how solid each signal is", + "should_trigger": false + }, + { + "query": "reprioritize the roadmap into now/next/later now that the SSO bet jumped the queue", + "should_trigger": false + } +] diff --git a/product-eval/evals/trigger/decision-readiness.json b/product-eval/evals/trigger/decision-readiness.json new file mode 100644 index 0000000000000000000000000000000000000000..475e50ddf713a863cf166064dc2f7f6f47944818 --- /dev/null +++ b/product-eval/evals/trigger/decision-readiness.json @@ -0,0 +1,82 @@ +[ + { + "query": "is it worth building SSO before our enterprise push? do we even have the data to call it?", + "should_trigger": true + }, + { + "query": "do we have enough evidence to greenlight the in-app notifications rebuild, or are we just guessing?", + "should_trigger": true + }, + { + "query": "are we ready to commit to the billing rework? tell me what's still missing before i say yes", + "should_trigger": true + }, + { + "query": "we've got ~40 intercom tickets and an amplitude drop-off chart pointing at onboarding step 3. is that actually enough to decide to fix it?", + "should_trigger": true + }, + { + "query": "honestly, what's the evidence behind shipping a mobile app this half? strong enough to pull the trigger or not?", + "should_trigger": true + }, + { + "query": "before i tell eng to start, can you judge whether the dark-mode request has enough signal to justify it", + "should_trigger": true + }, + { + "query": "i keep going back and forth on the API rate-limit feature. do we have the right to make this call yet, and what gaps should i close first?", + "should_trigger": true + }, + { + "query": "gut check: is team-permissions a defensible build given what we actually know, or do we need more data before committing?", + "should_trigger": true + }, + { + "query": "we think churned accounts wanted Slack alerts. is that conviction earned by the evidence, or wishful thinking? want a yes/no and the holes", + "should_trigger": true + }, + { + "query": "my PM swears the saved-views feature is a slam dunk. can you sanity-check whether the evidence actually backs committing to it?", + "should_trigger": true + }, + { + "query": "rank these 8 feature ideas by what's most worth doing, we have analytics + support data", + "should_trigger": false + }, + { + "query": "which of our three enterprise bets has the strongest case? line them up and score them so we know where to spend", + "should_trigger": false + }, + { + "query": "poke holes in our Q3 roadmap before the leadership review on friday", + "should_trigger": false + }, + { + "query": "stress-test this PRD for the new onboarding flow before we commit to it next sprint", + "should_trigger": false + }, + { + "query": "review our north-star and activation metrics for the month and flag what moved", + "should_trigger": false + }, + { + "query": "how did retention and DAU trend last quarter vs target? give me the scorecard", + "should_trigger": false + }, + { + "query": "i have a pile of 30 user-interview transcripts in a folder, pull out the main themes and insights for me", + "should_trigger": false + }, + { + "query": "here are 200 raw survey responses, make sense of them and rank the findings by frequency", + "should_trigger": false + }, + { + "query": "pull the recurring complaints from zendesk, our g2 reviews, and the churn survey and grade how solid each signal is", + "should_trigger": false + }, + { + "query": "grade how crisp this problem statement is: 'onboarding is bad'", + "should_trigger": false + } +] diff --git a/product-eval/evals/trigger/design-test.json b/product-eval/evals/trigger/design-test.json new file mode 100644 index 0000000000000000000000000000000000000000..945cab1ef3db60bcd2abc101722483917cfef06b --- /dev/null +++ b/product-eval/evals/trigger/design-test.json @@ -0,0 +1,82 @@ +[ + { + "query": "decision-readiness says run a research sprint for SSO — design the cheapest test to close the gap", + "should_trigger": true + }, + { + "query": "how do we validate this billing bet before engineering starts building?", + "should_trigger": true + }, + { + "query": "what's the cheapest experiment that would de-risk the notifications idea?", + "should_trigger": true + }, + { + "query": "turn these readiness gaps into a concrete validation test with a pass fail threshold", + "should_trigger": true + }, + { + "query": "we need a smoke test for the enterprise audit logs bet before committing the quarter", + "should_trigger": true + }, + { + "query": "design a quick fake-door test to see if customers actually want scheduled csv exports", + "should_trigger": true + }, + { + "query": "the idea is promising but evidence is thin; tell me the fastest research sprint to run", + "should_trigger": true + }, + { + "query": "what would prove or disprove this onboarding hypothesis in two weeks?", + "should_trigger": true + }, + { + "query": "give me the riskiest assumption, method, sample, instrumentation, duration and pass fail bar", + "should_trigger": true + }, + { + "query": "de-risk this feature idea with a small experiment before we write a spec", + "should_trigger": true + }, + { + "query": "pull the recurring complaints from zendesk and g2 and grade how solid each signal is", + "should_trigger": false + }, + { + "query": "is SSO worth building before the enterprise push, do we have enough evidence?", + "should_trigger": false + }, + { + "query": "rank these 8 feature ideas by what's most worth doing", + "should_trigger": false + }, + { + "query": "write the opportunity brief for the approved billing decision", + "should_trigger": false + }, + { + "query": "turn the approved decision into a build spec for claude code", + "should_trigger": false + }, + { + "query": "pressure-test our Q3 roadmap before leadership review", + "should_trigger": false + }, + { + "query": "what product-data sources do I have connected?", + "should_trigger": false + }, + { + "query": "synthesize the evidence we pulled into problem themes", + "should_trigger": false + }, + { + "query": "make a scorecard from the ranked opportunities", + "should_trigger": false + }, + { + "query": "update our Now Next Later roadmap with the new SSO epic", + "should_trigger": false + } +] diff --git a/product-eval/evals/trigger/discover-sources.json b/product-eval/evals/trigger/discover-sources.json new file mode 100644 index 0000000000000000000000000000000000000000..1e87709297517269fd3193ed9d268662733d1634 --- /dev/null +++ b/product-eval/evals/trigger/discover-sources.json @@ -0,0 +1,86 @@ +[ + { + "query": "what data do I even have to make product decisions? scan my connectors and tell me the confidence ceiling", + "should_trigger": true + }, + { + "query": "scan my sources and tell me where product insights could realistically come from", + "should_trigger": true + }, + { + "query": "what can I connect for product decisions right now? show me whats callable and whats missing", + "should_trigger": true + }, + { + "query": "im starting fresh in this repo, before we frame anything map out where my product data actually lives", + "should_trigger": true + }, + { + "query": "do i have intercom, amplitude, and zendesk hooked up? give me an inventory of my product-data sources and how strong each can get", + "should_trigger": true + }, + { + "query": "whats the best decision confidence i can realistically hit given only the sources i have access to today?", + "should_trigger": true + }, + { + "query": "build me a source map of my product evidence and flag what i'd have to upload as a CSV since it cant be connected", + "should_trigger": true + }, + { + "query": "i just want the landscape of where signal could come from, not the actual data pulled yet", + "should_trigger": true + }, + { + "query": "refresh the source inventory in .product-eval/onboarding/sources.md, i think i wired up linear and pendo since we last looked", + "should_trigger": true + }, + { + "query": "new project, what's the ceiling on how confident any call could be with what i've got connected?", + "should_trigger": true + }, + { + "query": "pull the recurring complaints from zendesk, our g2 reviews, and the churn survey and grade how solid each signal is", + "should_trigger": false + }, + { + "query": "now that you know where my data lives, go ahead and pull everything from intercom and amplitude into the scope", + "should_trigger": false + }, + { + "query": "collect the last 90 days of support tickets and reviews and normalize them so we can analyze", + "should_trigger": false + }, + { + "query": "synthesize the evidence we just gathered into the main problem themes", + "should_trigger": false + }, + { + "query": "i have a folder of 30 user-interview transcripts, pull out the main themes and insights for me", + "should_trigger": false + }, + { + "query": "here are 200 raw survey responses we just got back, make sense of them and rank the findings", + "should_trigger": false + }, + { + "query": "review our north-star and activation metrics for the month and flag what moved", + "should_trigger": false + }, + { + "query": "put together our monthly product scorecard and call out the biggest trend changes", + "should_trigger": false + }, + { + "query": "give me a competitive brief on Linear vs Jira for our sales battlecard", + "should_trigger": false + }, + { + "query": "update our Now/Next/Later roadmap, move the billing rework to Next and add the SSO epic to Now", + "should_trigger": false + }, + { + "query": "is it worth building SSO before our enterprise push? do we even have enough evidence to call it?", + "should_trigger": false + } +] diff --git a/product-eval/evals/trigger/drift-check.json b/product-eval/evals/trigger/drift-check.json new file mode 100644 index 0000000000000000000000000000000000000000..c805c3220946c9e027c309348588581c4c41d4d0 --- /dev/null +++ b/product-eval/evals/trigger/drift-check.json @@ -0,0 +1,26 @@ +[ + {"query": "what changed since last month", "should_trigger": true}, + {"query": "has this investigation drifted since we last looked at it", "should_trigger": true}, + {"query": "compare the current state of the onboarding scope to the snapshot from last quarter and tell me what moved", "should_trigger": true}, + {"query": "is the confidence trajectory on this bet actually improving, or are we just churning without new evidence coming in", "should_trigger": true}, + {"query": "audit this scope before we re-decide — what's changed since the last decision was logged", "should_trigger": true}, + {"query": "did our problem statement drift away from the evidence since the first version of the brief", "should_trigger": true}, + {"query": "diff the billing investigation now versus the prior version — scores, evidence, and framing", "should_trigger": true}, + {"query": "we snapshotted this scope a month ago — what evidence got added or retired, and how did Value and Confidence move", "should_trigger": true}, + {"query": "are we accumulating real confidence on the activation problem over time, or has it just been flat churn", "should_trigger": true}, + {"query": "show me what moved in this investigation since the last git ref", "should_trigger": true}, + {"query": "has the narrative on the enterprise-churn bet been quietly reframed compared to the earlier brief", "should_trigger": true}, + {"query": "set up a weekly digest of what moved across my investigations", "should_trigger": true}, + {"query": "between the snapshot and now, which themes are new, which faded, and which got merged for this scope", "should_trigger": true}, + {"query": "some of our evidence may have gone stale after the last release — compare against the prior snapshot and flag what got retired", "should_trigger": true}, + + {"query": "what's trending across the evidence we just gathered — which themes are getting worse month over month", "should_trigger": false}, + {"query": "cluster the evidence in this scope into problem themes with confidence reasoning", "should_trigger": false}, + {"query": "review our product metrics dashboard for the month and flag what moved on activation and retention", "should_trigger": false}, + {"query": "our north-star metric dropped 8% this week, dig into what changed in the funnel", "should_trigger": false}, + {"query": "rank these opportunities by value and confidence and tell me what to build next", "should_trigger": false}, + {"query": "pull the latest zendesk tickets and g2 reviews into the scope", "should_trigger": false}, + {"query": "do we have enough evidence to decide on the SSO bet yet", "should_trigger": false}, + {"query": "let's brainstorm some new directions for our analytics product", "should_trigger": false}, + {"query": "compare how our competitors solved onboarding versus how we did", "should_trigger": false} +] diff --git a/product-eval/evals/trigger/gather-evidence.json b/product-eval/evals/trigger/gather-evidence.json new file mode 100644 index 0000000000000000000000000000000000000000..42024297f3125b21e8ee3b6720ce8b2e6a63eefd --- /dev/null +++ b/product-eval/evals/trigger/gather-evidence.json @@ -0,0 +1,82 @@ +[ + { + "query": "pull the recurring complaints from zendesk, our g2 reviews, and the churn survey and grade how solid each signal is", + "should_trigger": true + }, + { + "query": "dig into the onboarding drop-off for me - go through intercom, amplitude, and our linear issues and tell me which findings actually hold up", + "should_trigger": true + }, + { + "query": "collect data on the billing complaints across our support tickets and app store reviews, last 90 days", + "should_trigger": true + }, + { + "query": "find signal for the slow-export problem - pull from wherever we have data and rate how strong each piece is", + "should_trigger": true + }, + { + "query": "i exported ~150 intercom convos to a csv plus a g2 dump, parse them and turn them into evidence items with strengths", + "should_trigger": true + }, + { + "query": "ok discover-sources is done, now actually go pull the tickets and analytics for the checkout friction area", + "should_trigger": true + }, + { + "query": "scrape the recent reddit + community threads about our competitor and our own churn survey, normalize it and dedupe the same users across sources", + "should_trigger": true + }, + { + "query": "we keep hearing notifications are noisy - go get me the actual receipts from pendo, the nps verbatims, and support, and tell me how much to trust each", + "should_trigger": true + }, + { + "query": "pull every lost-deal reason from the crm and the support tickets tagged 'sso' and weight them into evidence cards", + "should_trigger": true + }, + { + "query": "harvest signal on mobile crashes: error groups from datadog, app reviews, and the 40 support tickets, then strength-rate and resolve duplicate reporters", + "should_trigger": true + }, + { + "query": "synthesize the evidence we just pulled into themes", + "should_trigger": false + }, + { + "query": "what data do I even have to make product decisions? scan my connectors and tell me the confidence ceiling", + "should_trigger": false + }, + { + "query": "i have a pile of 30 user-interview transcripts in a folder, pull out the main themes and insights for me", + "should_trigger": false + }, + { + "query": "cluster this into the main problems - what patterns pop out of the signal we already collected?", + "should_trigger": false + }, + { + "query": "give me a competitive brief on Linear vs Jira for our sales battlecard", + "should_trigger": false + }, + { + "query": "do a teardown of Notion vs us - where are we losing on features and what do I put on the sales one-pager?", + "should_trigger": false + }, + { + "query": "review our north-star and activation metrics for the month and flag what moved", + "should_trigger": false + }, + { + "query": "which of our connected sources actually have usable product data, and how high can confidence get given what's available?", + "should_trigger": false + }, + { + "query": "is it worth building SSO before our enterprise push? do we even have the data to call it?", + "should_trigger": false + }, + { + "query": "new churn data came in - reshuffle our Now/Next/Later and tell me what slips to make room for the retention work", + "should_trigger": false + } +] diff --git a/product-eval/evals/trigger/pressure-test.json b/product-eval/evals/trigger/pressure-test.json new file mode 100644 index 0000000000000000000000000000000000000000..0d4e576e62a33bf28d0415e759ee6f43a38935ac --- /dev/null +++ b/product-eval/evals/trigger/pressure-test.json @@ -0,0 +1,23 @@ +[ + {"query": "poke holes in our Q3 roadmap before the leadership review on friday", "should_trigger": true}, + {"query": "stress-test this bet before we commit the whole quarter to it", "should_trigger": true}, + {"query": "pressure-test the PRD for the new billing system, where's it weak", "should_trigger": true}, + {"query": "red-team our go-to-market plan for the enterprise launch", "should_trigger": true}, + {"query": "challenge the assumptions baked into this roadmap, i think we're kidding ourselves", "should_trigger": true}, + {"query": "before we greenlight building SSO, tear the plan apart and find the holes", "should_trigger": true}, + {"query": "we're about to commit to the mobile rewrite, what are the hidden assumptions and scope risks", "should_trigger": true}, + {"query": "give this plan a hard critic review, find the weak grounding before we ship it", "should_trigger": true}, + {"query": "what could go wrong with this roadmap? surface the soft spots before we present it", "should_trigger": true}, + {"query": "play devil's advocate on our Q4 plan, what are we not seeing", "should_trigger": true}, + + {"query": "let's brainstorm some new directions for our analytics product, i want a sharp thinking partner", "should_trigger": false}, + {"query": "help me come up with fresh feature ideas for our onboarding flow, just spitball with me", "should_trigger": false}, + {"query": "i want to explore the problem space around developer retention and generate some options", "should_trigger": false}, + {"query": "be my thinking partner, let's ideate on what we could build next in the observability space", "should_trigger": false}, + {"query": "grade how crisp this problem statement is: 'onboarding is bad'", "should_trigger": false}, + {"query": "is it worth building SSO before our enterprise push? do we even have the data to call it?", "should_trigger": false}, + {"query": "update our Now/Next/Later roadmap, move the billing rework to Next and add the SSO epic to Now", "should_trigger": false}, + {"query": "write a PRD for the new CSV export feature so engineering can pick it up", "should_trigger": false}, + {"query": "review our north-star and activation metrics for the month and flag what moved", "should_trigger": false}, + {"query": "write the weekly stakeholder update for leadership about the platform migration", "should_trigger": false} +] diff --git a/product-eval/evals/trigger/prioritize-backlog.json b/product-eval/evals/trigger/prioritize-backlog.json new file mode 100644 index 0000000000000000000000000000000000000000..3a311b65981d3793a15fa3ca6bc5f65747d4b5b6 --- /dev/null +++ b/product-eval/evals/trigger/prioritize-backlog.json @@ -0,0 +1,23 @@ +[ + {"query": "rank these 8 feature ideas by what's most worth doing, we have analytics and support data", "should_trigger": true}, + {"query": "here's a list of 12 opportunities from our backlog, score them by value and confidence and tell me what to build", "should_trigger": true}, + {"query": "prioritize my backlog for next quarter based on the evidence we've gathered", "should_trigger": true}, + {"query": "i've got 15 problems competing for the same eng team, which ones are actually worth doing first", "should_trigger": true}, + {"query": "score these opportunities and map each to a recommendation: build now, gather more, or drop", "should_trigger": true}, + {"query": "we have evidence for some of these and basically nothing for others, rank them anyway and flag the low-confidence ones", "should_trigger": true}, + {"query": "sequence these feature ideas by what's most worth doing given the data we have", "should_trigger": true}, + {"query": "out of these 9 candidate bets, which should we tackle first, use value and confidence", "should_trigger": true}, + {"query": "prioritize these: SSO, audit logs, SAML, bulk export, SCIM, rank by evidence-backed value", "should_trigger": true}, + {"query": "what's the highest-leverage thing to build from this list of opportunities", "should_trigger": true}, + + {"query": "update our Now/Next/Later roadmap, move the billing rework to Next and add the SSO epic to Now", "should_trigger": false}, + {"query": "reprioritize our roadmap, bump the mobile redesign up and push the API v2 work to later", "should_trigger": false}, + {"query": "build me a Now / Next / Later roadmap view from scratch for the platform team", "should_trigger": false}, + {"query": "the analytics dependency slipped two weeks, shift the roadmap timelines accordingly", "should_trigger": false}, + {"query": "plan our next sprint, size the backlog against the team's capacity, we've got two people on PTO", "should_trigger": false}, + {"query": "is it worth building SSO before our enterprise push? do we even have the data to call it?", "should_trigger": false}, + {"query": "ok i've got ~200 intercom tickets exported and our amplitude funnel open, what should we actually build next quarter for onboarding", "should_trigger": false}, + {"query": "review our north-star and activation metrics for the month and flag what moved", "should_trigger": false}, + {"query": "write a PRD for the new CSV export feature so engineering can pick it up", "should_trigger": false}, + {"query": "give me a competitive brief on Linear vs Jira for our sales battlecard", "should_trigger": false} +] diff --git a/product-eval/evals/trigger/scorecard.json b/product-eval/evals/trigger/scorecard.json new file mode 100644 index 0000000000000000000000000000000000000000..ee204340614ab8dbbd369388a1beef301da3f8a3 --- /dev/null +++ b/product-eval/evals/trigger/scorecard.json @@ -0,0 +1,82 @@ +[ + { + "query": "make a scorecard from the ranked opportunities in this product-eval scope", + "should_trigger": true + }, + { + "query": "turn the prioritization results into a visual dashboard i can share with leadership", + "should_trigger": true + }, + { + "query": "show me the ranked opportunities as an interactive html scorecard with value, confidence, verdicts and gaps", + "should_trigger": true + }, + { + "query": "after that backlog ranking, create the scorecard view with the value-confidence quadrant", + "should_trigger": true + }, + { + "query": "render a product decision dashboard from .product-eval/onboarding/scores.md and decisions-log.md", + "should_trigger": true + }, + { + "query": "give me a rich visual output for the readiness verdicts and opportunity ranking", + "should_trigger": true + }, + { + "query": "build a shareable html page of the opportunities, confidence bands, evidence weight, and open gaps", + "should_trigger": true + }, + { + "query": "make this prioritization easier to scan with cards, bands, and the top customer quotes", + "should_trigger": true + }, + { + "query": "open a scorecard for the current investigation so PMs can sort and act on the top bets", + "should_trigger": true + }, + { + "query": "can you generate the decision dashboard after running prioritize-backlog", + "should_trigger": true + }, + { + "query": "review our north-star and activation metrics for the month and flag what moved", + "should_trigger": false + }, + { + "query": "build me a scorecard of this quarter's product metrics with trends called out for the exec review", + "should_trigger": false + }, + { + "query": "rank these 8 feature ideas by what's most worth doing, we have analytics and support data", + "should_trigger": false + }, + { + "query": "is it worth building SSO before our enterprise push? do we have enough evidence to call it?", + "should_trigger": false + }, + { + "query": "write the decision memo for the billing rework with options and success criteria", + "should_trigger": false + }, + { + "query": "turn the approved decision into a spec i can hand straight to claude code", + "should_trigger": false + }, + { + "query": "poke holes in our Q3 roadmap before the leadership review on friday", + "should_trigger": false + }, + { + "query": "what data do I have connected for product decisions?", + "should_trigger": false + }, + { + "query": "pull the recurring complaints from zendesk and g2 and grade how solid each signal is", + "should_trigger": false + }, + { + "query": "draft the opportunity brief for the notifications problem", + "should_trigger": false + } +] diff --git a/product-eval/evals/trigger/start.json b/product-eval/evals/trigger/start.json new file mode 100644 index 0000000000000000000000000000000000000000..7d5d8f37f5795cea68cde91a27cb331d76681be5 --- /dev/null +++ b/product-eval/evals/trigger/start.json @@ -0,0 +1,102 @@ +[ + { + "query": "start", + "should_trigger": true + }, + { + "query": "set up product-eval for my product", + "should_trigger": true + }, + { + "query": "help me decide what to build next, but first ask me what context and data you need", + "should_trigger": true + }, + { + "query": "help me prepare a decision report and set up the product context first", + "should_trigger": true + }, + { + "query": "hey just installed this, help me get going — where do i even start?", + "should_trigger": true + }, + { + "query": "what can this plugin actually do for me?", + "should_trigger": true + }, + { + "query": "i want to build something in the observability tooling space but don't know what — help me find the real problems worth solving", + "should_trigger": true + }, + { + "query": "should we build an AI changelog generator? honestly not sure, just point me in the right direction", + "should_trigger": true + }, + { + "query": "i'm a PM at a fintech trying to find our next big bet — how does this thing even work?", + "should_trigger": true + }, + { + "query": "help me figure out what to work on next, i don't even know which step to begin with", + "should_trigger": true + }, + { + "query": "explore the API-monitoring market for me and show me where the gaps are", + "should_trigger": true + }, + { + "query": "we want to figure out what to build next for our payments product — take it from the top", + "should_trigger": true + }, + { + "query": "give me the lay of the land here and kick off an investigation into the CI/CD space", + "should_trigger": true + }, + { + "query": "synthesize the evidence we just pulled into the main problem themes", + "should_trigger": false + }, + { + "query": "cluster the signal we already gathered into a handful of themes", + "should_trigger": false + }, + { + "query": "rank these 8 feature ideas by what's most worth building — we already have the analytics and support data", + "should_trigger": false + }, + { + "query": "poke holes in our Q3 roadmap before the leadership review on friday", + "should_trigger": false + }, + { + "query": "red-team this PRD and tell me where it falls apart before we commit", + "should_trigger": false + }, + { + "query": "what product data do i even have to work with? scan my connectors and tell me the confidence ceiling", + "should_trigger": false + }, + { + "query": "draft the opportunity brief for the notifications problem — both the narrative and the FAQ version", + "should_trigger": false + }, + { + "query": "let's brainstorm some fresh directions for our analytics product, i want a sharp thinking partner", + "should_trigger": false + }, + { + "query": "be my sounding board for a bit — i want to riff on new ideas for our developer tools", + "should_trigger": false + }, + { + "query": "i've got 30 user-interview transcripts in a folder, pull out the main themes and insights for me", + "should_trigger": false + }, + { + "query": "update our Now/Next/Later roadmap — move the billing rework to Next and add the SSO epic to Now", + "should_trigger": false + }, + { + "query": "reprioritize the roadmap, that dependency slipped so push the search revamp to Later", + "should_trigger": false + } +] diff --git a/product-eval/evals/trigger/synthesize.json b/product-eval/evals/trigger/synthesize.json new file mode 100644 index 0000000000000000000000000000000000000000..fae1a1f400ed0ceec23222947849910372a56764 --- /dev/null +++ b/product-eval/evals/trigger/synthesize.json @@ -0,0 +1,28 @@ +[ + {"query": "synthesize the evidence we just pulled into themes", "should_trigger": true}, + {"query": "ok we ran gather-evidence yesterday and pulled a bunch of tickets, reviews and analytics into the scope, now cluster all that into the main problem themes", "should_trigger": true}, + {"query": "what are the recurring patterns across the evidence we collected for the onboarding scope, and how confident should we be in each theme", "should_trigger": true}, + {"query": "group the weighted evidence items in .product-eval/onboarding/evidence into problem clusters for me", "should_trigger": true}, + {"query": "we've got the signal gathered for this investigation, what's trending over time and what themes pop out", "should_trigger": true}, + {"query": "turn the evidence we gathered into themes i can feed into prioritization", "should_trigger": true}, + {"query": "now that the data's in for this scope, find the clusters and tell me where the gaps are", "should_trigger": true}, + {"query": "what problem-themes emerge from all the evidence in this investigation, and which ones are getting worse month over month", "should_trigger": true}, + {"query": "consolidate everything we collected on the billing pain into a few clear themes with confidence reasoning", "should_trigger": true}, + {"query": "we pulled g2 reviews, intercom and amplitude into the scope last week, summarize the patterns into themes", "should_trigger": true}, + {"query": "can you frame this up properly? right now all we've got is 'onboarding feels clunky' and i need it sharp enough that someone could actually build against it", "should_trigger": true}, + {"query": "users keep dropping off somewhere in the signup flow. help me turn that into a real problem statement", "should_trigger": true}, + {"query": "the eng lead handed me 'add SSO' as the ask, but that feels like a solution dressed up as a requirement. what's the real underlying problem here?", "should_trigger": true}, + {"query": "everyone keeps saying 'make the dashboard better' and it's going nowhere because it's so broad. reshape it into an actual problem we could solve", "should_trigger": true}, + {"query": "'enterprise customers churn' is one giant blob. split it into a few separate problems that are each small enough to build for", "should_trigger": true}, + + {"query": "i have a pile of 30 user-interview transcripts in a folder, pull out the main themes and insights for me", "should_trigger": false}, + {"query": "just got back from 12 customer interviews, here are my raw notes, help me make sense of them and pull out themes", "should_trigger": false}, + {"query": "we ran a survey and got 400 open-text responses in a csv, synthesize them into key insights ranked by frequency", "should_trigger": false}, + {"query": "here's a dump of 200 support tickets exported from zendesk, what are the common themes and what should we prioritize", "should_trigger": false}, + {"query": "pull the recurring complaints from zendesk, our g2 reviews, and the churn survey and grade how solid each signal is", "should_trigger": false}, + {"query": "rank these 8 feature ideas by what's most worth doing, we have analytics and support data", "should_trigger": false}, + {"query": "update our Now/Next/Later roadmap, move the billing rework to Next and add the SSO epic to Now", "should_trigger": false}, + {"query": "review our north-star and activation metrics for the month and flag what moved", "should_trigger": false}, + {"query": "let's brainstorm some new directions for our analytics product, i want a sharp thinking partner", "should_trigger": false}, + {"query": "draft the opportunity brief for the notifications problem, both the narrative and the FAQ version", "should_trigger": false} +] diff --git a/product-eval/evals/trigger/to-build-brief.json b/product-eval/evals/trigger/to-build-brief.json new file mode 100644 index 0000000000000000000000000000000000000000..fafaf68e47d2cffd6e229adb77746cf23d657b36 --- /dev/null +++ b/product-eval/evals/trigger/to-build-brief.json @@ -0,0 +1,90 @@ +[ + { + "query": "the SSO bet got approved, can you turn it into a spec i can hand straight to claude code?", + "should_trigger": true + }, + { + "query": "the notifications brief got approved yesterday — make it buildable, give me an agent-ready build spec", + "should_trigger": true + }, + { + "query": "convert our approved opportunity brief into a build spec with given/when/then acceptance criteria", + "should_trigger": true + }, + { + "query": "we signed off on the csv export bet — strip the prose and emit a structured spec claude code can just execute", + "should_trigger": true + }, + { + "query": "run the design mode on the approved onboarding decision so i can hand the screen-by-screen breakdown to figma", + "should_trigger": true + }, + { + "query": "we greenlit the billing rework, now produce the implementation spec for an AI builder with scope in/out and an MVP slice first", + "should_trigger": true + }, + { + "query": "take this approved brief and make a figma-ready spec: every screen, its empty/loading/error states, and the primary CTA per view", + "should_trigger": true + }, + { + "query": "decision's been signed off, just give me a literal testable build doc to drop into codex, no narrative", + "should_trigger": true + }, + { + "query": "hand this approved billing brief to claude code with the data model, edge cases, success metrics and instrumentation", + "should_trigger": true + }, + { + "query": "it passed readiness last week, can you turn it into something an agent can actually build from?", + "should_trigger": true + }, + { + "query": "the export feature got the go-ahead — break it into screens and states for our designer to mock in figma", + "should_trigger": true + }, + { + "query": "write a PRD for the new CSV export feature so engineering can pick it up", + "should_trigger": false + }, + { + "query": "draft a feature spec with goals, non-goals and acceptance criteria so the devs can start the export work", + "should_trigger": false + }, + { + "query": "draft the opportunity brief for the notifications problem, both the narrative and the FAQ version", + "should_trigger": false + }, + { + "query": "write up the human-readable brief for the SSO opportunity — problem, why now, impact, the ask", + "should_trigger": false + }, + { + "query": "write a decision memo for the SSO bet — options, falsifiable criteria, metrics, the ask and a deadline", + "should_trigger": false + }, + { + "query": "put together the memo so leadership can decide on billing rework: the options, what would prove us wrong, and the deadline", + "should_trigger": false + }, + { + "query": "is it worth building SSO before our enterprise push? do we even have the evidence to call it yet?", + "should_trigger": false + }, + { + "query": "poke holes in our Q3 roadmap and the export PRD before the leadership review on friday", + "should_trigger": false + }, + { + "query": "update our Now/Next/Later roadmap, move the billing rework to Next and add the SSO epic to Now", + "should_trigger": false + }, + { + "query": "implement this approved figma design as react code for the onboarding flow", + "should_trigger": false + }, + { + "query": "i have a pile of 30 user-interview transcripts in a folder, pull out the main themes and insights for me", + "should_trigger": false + } +] diff --git a/product-eval/evals/trigger/write-brief.json b/product-eval/evals/trigger/write-brief.json new file mode 100644 index 0000000000000000000000000000000000000000..dea45f5a7d64c087017459e535c317a0f722f907 --- /dev/null +++ b/product-eval/evals/trigger/write-brief.json @@ -0,0 +1,26 @@ +[ + {"query": "draft the opportunity brief for the notifications problem, both the narrative and the FAQ version", "should_trigger": true}, + {"query": "write the opportunity brief for the onboarding pain now that it passed the readiness gate", "should_trigger": true}, + {"query": "draft the narrative version of the brief: problem, why now, impact, the ask", "should_trigger": true}, + {"query": "write the FAQ version of the brief for the billing opportunity", "should_trigger": true}, + {"query": "put together the human decision doc for leadership on the SSO opportunity", "should_trigger": true}, + {"query": "we decided to move forward, write up the brief that explains the problem and what we're asking for", "should_trigger": true}, + {"query": "turn this investigation into an opportunity brief i can circulate to the exec team", "should_trigger": true}, + {"query": "write the brief: problem statement, why-now, expected impact, and the ask", "should_trigger": true}, + {"query": "draft both versions of the opportunity brief for the churn problem, report and FAQ", "should_trigger": true}, + {"query": "i need the opportunity brief for the analytics problem written up as a narrative for the team", "should_trigger": true}, + {"query": "write the decision memo for the SSO bet, options, success/failure criteria, metrics scorecard, and an explicit ask for friday", "should_trigger": true}, + {"query": "draft a recommendation memo on whether to build the in-app notifications center, weigh at least 3 options including do-nothing and recommend one", "should_trigger": true}, + {"query": "i need an options memo for the onboarding rework: choices, tradeoffs, and what i'm asking the exec team to decide by july 15", "should_trigger": true}, + {"query": "write the doc that forces a build-vs-buy call on the analytics pipeline, with options, what we'd measure, and a recommendation", "should_trigger": true}, + + {"query": "write a PRD for the new CSV export feature so engineering can pick it up", "should_trigger": false}, + {"query": "turn this feature idea into a spec with goals, non-goals, and acceptance criteria for the eng team", "should_trigger": false}, + {"query": "scope the bulk-export feature into a PRD with success metrics and phased milestones", "should_trigger": false}, + {"query": "write the engineering spec for the new webhooks system so the devs can start", "should_trigger": false}, + {"query": "turn the approved decision into a spec i can hand straight to claude code", "should_trigger": false}, + {"query": "write the weekly stakeholder update for leadership about the platform migration", "should_trigger": false}, + {"query": "give me a competitive brief on Linear vs Jira for our sales battlecard", "should_trigger": false}, + {"query": "ok i've got ~200 intercom tickets exported and our amplitude funnel open, what should we actually build next quarter for onboarding", "should_trigger": false}, + {"query": "review our north-star and activation metrics for the month and flag what moved", "should_trigger": false} +] diff --git a/product-eval/evals/triggering.json b/product-eval/evals/triggering.json new file mode 100644 index 0000000000000000000000000000000000000000..f3e3751c404d2a1bf51ec91168706b516cd5fc5e --- /dev/null +++ b/product-eval/evals/triggering.json @@ -0,0 +1,33 @@ +{ + "purpose": "Routing / collision evals. Each query maps to the skill that SHOULD win. 'anthropic-pm:*' and 'none' mark cases that must NOT trigger one of our skills (near-misses against Anthropic's product-management plugin). To run the per-skill description optimizer (skill-creator run_loop.py), transform per skill: queries whose expected == are should_trigger:true, all others false.", + "skills": ["start","discover-sources","gather-evidence","synthesize","prioritize-backlog","decision-readiness","design-test","pressure-test","critique","write-brief","to-build-brief","scorecard","drift-check","calibrate"], + "cases": [ + {"id": 1, "query": "ok i've got ~200 intercom tickets exported and our amplitude funnel open — what should we actually build next quarter for onboarding?", "expected": "start", "note": "setup, then evaluate job and chains"}, + {"id": 2, "query": "is it worth building SSO before our enterprise push? do we even have the data to call it?", "expected": "decision-readiness"}, + {"id": 3, "query": "rank these 8 feature ideas by what's most worth doing — we have analytics + support data", "expected": "prioritize-backlog"}, + {"id": 4, "query": "synthesize the evidence we just pulled into themes", "expected": "synthesize"}, + {"id": 5, "query": "i have a pile of 30 user-interview transcripts in a folder, pull out the main themes and insights for me", "expected": "anthropic-pm:synthesize-research", "note": "raw research outside our pipeline — must NOT trigger synthesize"}, + {"id": 6, "query": "poke holes in our Q3 roadmap before the leadership review on friday", "expected": "pressure-test"}, + {"id": 7, "query": "let's brainstorm some new directions for our analytics product, i want a sharp thinking partner", "expected": "anthropic-pm:product-brainstorming", "note": "generative — must NOT trigger pressure-test/critique"}, + {"id": 8, "query": "update our Now/Next/Later roadmap — move the billing rework to Next and add the SSO epic to Now", "expected": "anthropic-pm:roadmap-update", "note": "roadmap edit — must NOT trigger prioritize-backlog"}, + {"id": 9, "query": "write a PRD for the new CSV export feature so engineering can pick it up", "expected": "anthropic-pm:write-spec", "note": "engineering spec — must NOT trigger write-brief"}, + {"id": 10, "query": "draft the opportunity brief for the notifications problem — both the narrative and the FAQ version", "expected": "write-brief"}, + {"id": 11, "query": "grade how crisp this problem statement is: 'onboarding is bad'", "expected": "critique", "note": "crispness grader"}, + {"id": 12, "query": "what data do I even have to make product decisions? scan my connectors and tell me the confidence ceiling", "expected": "discover-sources"}, + {"id": 13, "query": "i want to build something in the observability tooling space but not sure what — find the real problems worth solving", "expected": "start", "note": "explore job"}, + {"id": 14, "query": "turn the approved decision into a spec i can hand straight to claude code", "expected": "to-build-brief"}, + {"id": 15, "query": "has our top opportunity drifted since last month? did confidence actually improve or are we spinning?", "expected": "drift-check"}, + {"id": 16, "query": "the confidence numbers feel too generous — tune the thresholds against these 8 past launches we know the outcomes of", "expected": "calibrate"}, + {"id": 17, "query": "give me a competitive brief on Linear vs Jira for our sales battlecard", "expected": "anthropic-pm:competitive-brief", "note": "must NOT trigger gather-evidence/pressure-test"}, + {"id": 18, "query": "review our north-star and activation metrics for the month and flag what moved", "expected": "anthropic-pm:metrics-review", "note": "must NOT trigger drift-check"}, + {"id": 19, "query": "write the weekly stakeholder update for leadership about the platform migration", "expected": "anthropic-pm:stakeholder-update", "note": "none of ours"}, + {"id": 20, "query": "pull the recurring complaints from zendesk, our g2 reviews, and the churn survey and grade how solid each signal is", "expected": "gather-evidence"}, + {"id": 21, "query": "decision-readiness says run a research sprint for SSO — design the cheapest test to close the gap", "expected": "design-test"}, + {"id": 22, "query": "make a scorecard from the ranked opportunities in this product-eval scope", "expected": "scorecard"}, + {"id": 23, "query": "write the decision memo for the SSO bet — options, metrics, falsifiable criteria, and the explicit ask", "expected": "write-brief"}, + {"id": 24, "query": "what should we build next for activation if we have a csv of support tickets and some funnel data?", "expected": "start", "note": "Rank opportunities outcome with setup"}, + {"id": 25, "query": "users keep dropping off somewhere in signup; turn that into a real problem statement", "expected": "synthesize", "note": "problem framing is now inside synthesize"}, + {"id": 26, "query": "set up product-eval for my product", "expected": "start", "note": "first-run onboarding"}, + {"id": 27, "query": "help me prepare a decision report and ask me what context you need first", "expected": "start", "note": "write outcome but missing source/audience setup"} + ] +} diff --git a/product-eval/examples/README.md b/product-eval/examples/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b31304869f8a33e2de2a3e08a812de0c2ff7d49d --- /dev/null +++ b/product-eval/examples/README.md @@ -0,0 +1,39 @@ +# Examples + +## `activation-q3/`: a complete sample investigation + +A synthetic, self-contained investigation scope for the fictional product "Pulse" (a B2B product-analytics workspace). It mirrors exactly what the plugin writes to `.product-eval//` in a real project, and it serves two purposes: + +1. **A `scorecard` reference output.** `activation-q3/scorecard.html` is the rendered decision scorecard for this scope: open it in a browser to see the KPI cards, the Value by Confidence quadrant, the four color-coded verdict cards (with verbatim quotes, top complaint, the ask, and "why it ranks here"), the glossary, and the honest footer. +2. **A conformance fixture.** Every file conforms to `DATA-CONTRACT.md`, and every number was computed from the shipped scoring defaults, so it is the fixture the validation preflight (and `scripts/validate_scope.py`) is tested against. + +### What's inside + +| File | Role | +|---|---| +| `context.md` | scope front-matter (product, north star, personas) | +| `evidence/E1.md` ... `E12.md` | 12 normalized, weighted evidence items (E12 is a flagged contradiction) | +| `identities.md` | resolved cross-system identities (same account across Zendesk + CRM + analytics; same email across Zendesk + CRM) | +| `themes.md` | 4 problem-framed themes with computed confidence | +| `scores.md` | Value/Confidence and a recommendation per theme | +| `decisions-log.md` | readiness verdicts (append-only) | +| `scorecard.html` | the rendered scorecard | + +### The four opportunities (one of each verdict) + +| Theme | Value | Confidence | Verdict | +|---|---|---|---| +| T1 New admins can't finish setup without CS help | 72 (High) | 89 (High) | **Decide now** | +| T2 Teams can't share saved views across the org | 70 (High) | 63 (Moderate) | **Run a research sprint** | +| T3 Alert noise pushes users to mute critical alerts | 60 (Moderate) | 24 (Low) | **Do not commit yet** | +| T4 Users want a dark-mode dashboard | 35 (Low) | 12 (Low) | **Deprioritize** | + +T3 shows how a directly contradicting first-party signal (E12) drives confidence below the gate even when value is real. + +### Verify it + +``` +python ../scripts/validate_scope.py activation-q3/ # -> OK (needs PyYAML) +``` + +All numbers are illustrative. No real customer data. diff --git a/product-eval/examples/activation-q3/context.md b/product-eval/examples/activation-q3/context.md new file mode 100644 index 0000000000000000000000000000000000000000..f921c82543c5cb29349062f89b5a8d5918ba58d6 --- /dev/null +++ b/product-eval/examples/activation-q3/context.md @@ -0,0 +1,13 @@ +--- +scope: activation-q3 +job: evaluate +stage: evaluate +product: "Pulse, a B2B product-analytics workspace for SaaS teams" +north_star: "weekly activated workspaces" +personas: [admin, end_user, buyer] +updated: 2026-06-29 +--- + +Synthetic example investigation shipped with the product-eval plugin. All numbers +are illustrative and were computed from the shipped scoring defaults so the files +can double as a validation fixture. No real customer data. diff --git a/product-eval/examples/activation-q3/decisions-log.md b/product-eval/examples/activation-q3/decisions-log.md new file mode 100644 index 0000000000000000000000000000000000000000..0f6c7a98770dc879a87bdfa1854678d59d5b2f12 --- /dev/null +++ b/product-eval/examples/activation-q3/decisions-log.md @@ -0,0 +1,34 @@ +# Decisions log for activation-q3 (append-only) + +- date: 2026-06-29 + item: T1 + event: readiness + verdict: "Decide now" + confidence: 89 + confidence_band: high + evidence: [E1, E2, E3, E4] + open: ["which connector is worst", "trial-spam check on the 38%"] + contested: [] + note: "Passed the sufficiency gate; first-party analytics + tickets + interviews + churn agree." + +- date: 2026-06-29 + item: T2 + event: readiness + verdict: "Run a research sprint" + confidence: 63 + confidence_band: moderate + evidence: [E5, E6, E7] + open: ["how many seats would share", "no first-party re-creation data"] + contested: [] + note: "High value, but the cheapest validating test should run before committing (see design-test)." + +- date: 2026-06-29 + item: T3 + event: readiness + verdict: "Do not commit yet" + confidence: 24 + confidence_band: low + evidence: [E8, E9] + open: ["measure actual mute-rate"] + contested: [E12] + note: "Hypothesis contradicted by first-party analytics; do not act until measured." diff --git a/product-eval/examples/activation-q3/evidence/E1.md b/product-eval/examples/activation-q3/evidence/E1.md new file mode 100644 index 0000000000000000000000000000000000000000..1be8d94394651ab5ad4cba7515af0f67659f8193 --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E1.md @@ -0,0 +1,17 @@ +--- +id: E1 +claim: "38% of new workspaces never finish setup, dropping at the data-source connection step (up from 24% last quarter)" +source_type: product_analytics +source_ref: "Amplitude > Activation funnel" +persona: [admin] +funnel_stage: activation +impact_type: activation +strength: 5 +recency_days: 20 +weight: 12 +person_id: null +account_id: acct_acme +corroborated_by: [E2, E3] +stale: false +--- +Funnel: signup to first-connected-source. The cliff is the integrations step, not signup. diff --git a/product-eval/examples/activation-q3/evidence/E10.md b/product-eval/examples/activation-q3/evidence/E10.md new file mode 100644 index 0000000000000000000000000000000000000000..bbf9712e453e82d224e1850204b9177d5409eaf1 --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E10.md @@ -0,0 +1,17 @@ +--- +id: E10 +claim: "6 scattered requests for a dark-mode dashboard over the past year" +source_type: feature_request +source_ref: "Productboard > misc (6 requests)" +persona: [end_user] +funnel_stage: engagement +impact_type: engagement +strength: 2 +recency_days: 300 +weight: 1 +person_id: null +account_id: null +corroborated_by: [] +stale: false +--- +Low frequency, spread thin over 12 months. diff --git a/product-eval/examples/activation-q3/evidence/E11.md b/product-eval/examples/activation-q3/evidence/E11.md new file mode 100644 index 0000000000000000000000000000000000000000..4101354fa5342e285e382b040821546f0db8d7a2 --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E11.md @@ -0,0 +1,17 @@ +--- +id: E11 +claim: "Occasional app-store style reviews ask for dark mode" +source_type: app_review +source_ref: "Capterra (sparse)" +persona: [end_user] +funnel_stage: engagement +impact_type: engagement +strength: 2 +recency_days: 200 +weight: 1 +person_id: null +account_id: null +corroborated_by: [] +stale: false +--- +Aesthetic preference, no business-pain signal. diff --git a/product-eval/examples/activation-q3/evidence/E12.md b/product-eval/examples/activation-q3/evidence/E12.md new file mode 100644 index 0000000000000000000000000000000000000000..433ace08a5d66b08005547822efe3ee2e47fa9c5 --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E12.md @@ -0,0 +1,19 @@ +--- +id: E12 +claim: "Alert-channel engagement is flat quarter over quarter, not declining" +source_type: product_analytics +source_ref: "Amplitude > Alerts engagement" +persona: [end_user] +funnel_stage: engagement +impact_type: engagement +strength: 4 +recency_days: 25 +weight: 7 +person_id: null +account_id: null +corroborated_by: [] +stale: false +contradicts: [E8] +--- +Direct first-party data that contradicts the "users are muting alerts" hypothesis (E8). +Kept and flagged: this is the unresolved material contradiction that drives T3's confidence penalty. diff --git a/product-eval/examples/activation-q3/evidence/E2.md b/product-eval/examples/activation-q3/evidence/E2.md new file mode 100644 index 0000000000000000000000000000000000000000..24bdd2b62c8087bb0c4e18a9dccb285c90e67f3a --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E2.md @@ -0,0 +1,17 @@ +--- +id: E2 +claim: "Admins get stuck at the data-source connection step and open tickets to finish setup" +source_type: support_ticket +source_ref: "Zendesk > tag:onboarding (27 tickets / 6 weeks)" +persona: [admin] +funnel_stage: activation +impact_type: activation +strength: 4 +recency_days: 30 +weight: 7 +person_id: null +account_id: acct_acme +corroborated_by: [E1, E3] +stale: false +--- +"I connected Postgres but nothing shows up, what am I missing?" Recurring across 27 tickets. diff --git a/product-eval/examples/activation-q3/evidence/E3.md b/product-eval/examples/activation-q3/evidence/E3.md new file mode 100644 index 0000000000000000000000000000000000000000..caef50bf7aa10bb37a927247c17361b9a76edbef --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E3.md @@ -0,0 +1,17 @@ +--- +id: E3 +claim: "In 4 onboarding calls, admins needed CS hand-holding to complete setup" +source_type: customer_interview +source_ref: "Onboarding calls (4 accounts)" +persona: [admin] +funnel_stage: activation +impact_type: activation +strength: 4 +recency_days: 15 +weight: 7 +person_id: person_jordan +account_id: acct_acme +corroborated_by: [E1, E2] +stale: false +--- +"We almost gave up before your CS person walked us through the connectors." diff --git a/product-eval/examples/activation-q3/evidence/E4.md b/product-eval/examples/activation-q3/evidence/E4.md new file mode 100644 index 0000000000000000000000000000000000000000..f02755d32482eba48f833a27487cac010f54309b --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E4.md @@ -0,0 +1,17 @@ +--- +id: E4 +claim: "3 churned accounts named 'never got it set up' as the reason for leaving" +source_type: churn_survey +source_ref: "Churn survey Q2 (3 of 11 responses)" +persona: [admin, buyer] +funnel_stage: retention +impact_type: retention +strength: 4 +recency_days: 120 +weight: 5 +person_id: null +account_id: null +corroborated_by: [] +stale: false +--- +Setup friction reads through to retention, not just activation. diff --git a/product-eval/examples/activation-q3/evidence/E5.md b/product-eval/examples/activation-q3/evidence/E5.md new file mode 100644 index 0000000000000000000000000000000000000000..2e29d2be30b29638ba80977dea756f32b5ee1556 --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E5.md @@ -0,0 +1,17 @@ +--- +id: E5 +claim: "19 feature requests asking for views/dashboards shared across the whole org" +source_type: feature_request +source_ref: "Productboard > top requests" +persona: [admin, end_user] +funnel_stage: expansion +impact_type: expansion +strength: 3 +recency_days: 25 +weight: 4 +person_id: null +account_id: null +corroborated_by: [E6, E7] +stale: false +--- +Today views are per-user; teams rebuild the same view repeatedly. diff --git a/product-eval/examples/activation-q3/evidence/E6.md b/product-eval/examples/activation-q3/evidence/E6.md new file mode 100644 index 0000000000000000000000000000000000000000..b17332fcf470a240ef68391d6113b9092e45457d --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E6.md @@ -0,0 +1,17 @@ +--- +id: E6 +claim: "Two mid-market prospects asked about org-shared views during deals" +source_type: sales_call +source_ref: "Gong > deal notes (2 deals)" +persona: [buyer] +funnel_stage: expansion +impact_type: competitive +strength: 4 +recency_days: 18 +weight: 7 +person_id: null +account_id: acct_northwind +corroborated_by: [E5] +stale: false +--- +Raised as a 'will our team adopt this' question, an expansion signal. diff --git a/product-eval/examples/activation-q3/evidence/E7.md b/product-eval/examples/activation-q3/evidence/E7.md new file mode 100644 index 0000000000000000000000000000000000000000..2d5d955f733109ee81cdd3f60c2846c979d68b0a --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E7.md @@ -0,0 +1,17 @@ +--- +id: E7 +claim: "A G2 review wants team-shared saved views" +source_type: app_review +source_ref: "G2 review (4-star)" +persona: [end_user] +funnel_stage: expansion +impact_type: competitive +strength: 3 +recency_days: 140 +weight: 3 +person_id: null +account_id: null +corroborated_by: [E5] +stale: false +--- +"Love it solo, but my team can't see my views." diff --git a/product-eval/examples/activation-q3/evidence/E8.md b/product-eval/examples/activation-q3/evidence/E8.md new file mode 100644 index 0000000000000000000000000000000000000000..78c4b758466169808a122b530eed6c6d54d893cd --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E8.md @@ -0,0 +1,17 @@ +--- +id: E8 +claim: "CS suspects users mute alert channels because of noise; one large account muted everything" +source_type: internal_observation +source_ref: "CS Slack thread + 1 account" +persona: [end_user] +funnel_stage: retention +impact_type: retention +strength: 3 +recency_days: 22 +weight: 4 +person_id: null +account_id: acct_acme +corroborated_by: [] +stale: false +--- +Hypothesis, not measured. Flagged as contradicted by E-contra below. diff --git a/product-eval/examples/activation-q3/evidence/E9.md b/product-eval/examples/activation-q3/evidence/E9.md new file mode 100644 index 0000000000000000000000000000000000000000..08672530b9b37b35f042103e10c24bdaeeb71ebc --- /dev/null +++ b/product-eval/examples/activation-q3/evidence/E9.md @@ -0,0 +1,17 @@ +--- +id: E9 +claim: "A low-engagement community thread complains about alert spam" +source_type: community_post +source_ref: "Reddit r/devops (low engagement)" +persona: [end_user] +funnel_stage: engagement +impact_type: cost +strength: 2 +recency_days: 150 +weight: 1 +person_id: null +account_id: null +corroborated_by: [] +stale: false +--- +Thin signal; included for completeness. diff --git a/product-eval/examples/activation-q3/identities.md b/product-eval/examples/activation-q3/identities.md new file mode 100644 index 0000000000000000000000000000000000000000..edabf02246e43e84c4138f5860875a27c01bc69f --- /dev/null +++ b/product-eval/examples/activation-q3/identities.md @@ -0,0 +1,25 @@ +# Resolved identities for activation-q3 +# Shows the cross-system mapping: the same account/person stitched across +# Zendesk + CRM + analytics so multi-touchpoint signal collapses to one entity. + +- entity: acct_acme + grain: account + matched_records: [zendesk:org_1183, hubspot:acct_55, amplitude:grp_acme] + method: email_domain # acme.io + match_confidence: high + # Appears in E1 (analytics), E2 (ticket), E3 (interview), E8 (observation): + # one account, four touchpoints, escalating -> a severity signal, not four voices. + +- entity: person_jordan + grain: person + matched_records: [zendesk:user_991, hubspot:contact_77] + method: email # jordan@acme.io present verbatim in both systems + match_confidence: high + # The canonical case: same email in a Zendesk ticket AND the CRM -> one person, + # enriched with CRM context (admin at Acme, mid-market, renewal in 90 days). + +- entity: acct_northwind + grain: account + matched_records: [gong:acct_nw, hubspot:acct_204] + method: email_domain # northwind.com + match_confidence: high diff --git a/product-eval/examples/activation-q3/scorecard.html b/product-eval/examples/activation-q3/scorecard.html new file mode 100644 index 0000000000000000000000000000000000000000..bd6a364f9064ef62beb49db5159a2d3ea4913d4e --- /dev/null +++ b/product-eval/examples/activation-q3/scorecard.html @@ -0,0 +1,305 @@ + + + + + +Decision Scorecard, activation-q3 (Pulse) + + + +
+
+
+
product-eval  /  decision scorecard
+

activation-q3

+
Pulse, a B2B product-analytics workspace. North star: weekly activated workspaces. Generated 2026-06-29.
+
+
+ +
+ +
+ +
+

Value × Confidence

+

Each opportunity is plotted by how big it could be (Value, vertical) against how sure we are it is real (Confidence, horizontal). The gate lines sit at 70. The top-right box is where a bet is both worth it and proven enough to commit.

+
+ + + + + + + + + + + conf gate 70 + value 70 + + Confidence → + Value → + 0 + 100 + + + + T1 + + T2 + + T3 + + T4 + + +
+
Decide now
+
Run a research sprint
+
Do not commit yet
+
Deprioritize
+
+
+
+ +
+ Sort by + + + + +
+ +
+ +
+ How to read this +
+
Value (0 to 100)
+
If this problem is real, how big is it? Built from persona weight, funnel location, frequency, and competitive pressure (each 0 to 25). It is about potential impact, not certainty.
+
Confidence (0 to 100)
+
How sure are we the problem is real and worth acting on? Computed from total evidence weight, source-type diversity, and unresolved contradictions. Reported as a band (Low / Moderate / High); the number is for audit.
+
Evidence weight
+
Quality-weighted volume of evidence, deduplicated by account. Three strong first-party findings outweigh fifty vague mentions, so weight, not raw count, drives Confidence.
+
Accounts affected
+
Distinct accounts after identity resolution (one customer across Zendesk, CRM, and analytics counts once), so a single loud account is not mistaken for a widespread problem.
+
The recommendation tiers
+
Decide now: worth building and well supported. Run a research sprint: high value but needs more proof, design the cheapest validating test. Do not commit yet: too unproven to act on. Deprioritize: prize too small to pursue.
+
+
+ +
+
+ + + + diff --git a/product-eval/examples/activation-q3/scores.md b/product-eval/examples/activation-q3/scores.md new file mode 100644 index 0000000000000000000000000000000000000000..ad28d4ca62669c70185d57c6736373f62acaad9d --- /dev/null +++ b/product-eval/examples/activation-q3/scores.md @@ -0,0 +1,41 @@ +# Scores for activation-q3 (exact 0-100 stored for audit; chat leads with bands) + +- problem: T1 + value: 72 + value_inputs: {persona: 20, funnel: 22, frequency: 20, competitive: 10} + value_band: high + confidence: 89 + confidence_band: high + recommendation: "Decide now" + rationale: "High-value activation cliff, corroborated across 4 first-party source types; the math supports committing." + override: null + +- problem: T2 + value: 70 + value_inputs: {persona: 20, funnel: 15, frequency: 15, competitive: 20} + value_band: high + confidence: 63 + confidence_band: moderate + recommendation: "Run a research sprint" + rationale: "High value and competitors solved it, but no first-party usage data; firm up proof before committing." + override: null + +- problem: T3 + value: 60 + value_inputs: {persona: 15, funnel: 25, frequency: 10, competitive: 10} + value_band: moderate + confidence: 24 + confidence_band: low + recommendation: "Do not commit yet" + rationale: "Retention-relevant if true, but the core hypothesis is directly contradicted by first-party analytics (E12)." + override: null + +- problem: T4 + value: 35 + value_inputs: {persona: 10, funnel: 10, frequency: 10, competitive: 5} + value_band: low + confidence: 12 + confidence_band: low + recommendation: "Deprioritize" + rationale: "Prize too small: secondary persona, engagement-only, no competitive pressure." + override: null diff --git a/product-eval/examples/activation-q3/themes.md b/product-eval/examples/activation-q3/themes.md new file mode 100644 index 0000000000000000000000000000000000000000..e3c5070b39bc6b69a502d117b4412bd6455e38cb --- /dev/null +++ b/product-eval/examples/activation-q3/themes.md @@ -0,0 +1,57 @@ +# Problem-framed themes for activation-q3 + +- id: T1 + name: "New admins can't finish workspace setup without CS help" + members: [E1, E2, E3, E4] + personas: [admin, buyer] + distinct_accounts: 9 + total_weight: 31 + source_types: [product_analytics, support_ticket, customer_interview, churn_survey] + trend: up + corroboration: cross-source + contradictions: [] + confidence: 89 # base 89 (W=31) x 1.0 (4 source types) x 1.0 (no contradictions) + confidence_band: high + gaps: ["which connector is worst (Postgres vs Snowflake)?", "is the 38% inflated by trial spam?"] + +- id: T2 + name: "Teams can't share saved views across the org" + members: [E5, E6, E7] + personas: [admin, end_user, buyer] + distinct_accounts: 6 + total_weight: 14 + source_types: [feature_request, sales_call, app_review] + trend: emerging + corroboration: cross-source + contradictions: [] + confidence: 63 # base 63 (W=14) x 1.0 (3 source types) x 1.0 + confidence_band: moderate + gaps: ["how many seats would actually share?", "no first-party usage data on view re-creation"] + +- id: T3 + name: "Alert noise pushes users to mute critical alerts" + members: [E8, E9] + personas: [end_user] + distinct_accounts: 2 + total_weight: 5 + source_types: [internal_observation, community_post] + trend: flat + corroboration: single-source-ish + contradictions: [E12] # analytics show alert engagement flat -> 1 unresolved material contradiction + confidence: 24 # base 30 (W=5) x 0.9 (2 source types) x 0.9 (1 contradiction) + confidence_band: low + gaps: ["no measured mute-rate", "E8 hypothesis directly contradicted by E12"] + +- id: T4 + name: "Users want a dark-mode dashboard" + members: [E10, E11] + personas: [end_user] + distinct_accounts: 4 + total_weight: 2 + source_types: [feature_request, app_review] + trend: flat + corroboration: weak + contradictions: [] + confidence: 12 # base 13 (W=2) x 0.9 (2 source types) x 1.0 + confidence_band: low + gaps: ["no business-impact signal", "aesthetic preference, low frequency"] diff --git a/product-eval/install.sh b/product-eval/install.sh new file mode 100644 index 0000000000000000000000000000000000000000..2b34f458abf350d7b3cf4730e57def3e5daef31d --- /dev/null +++ b/product-eval/install.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# product-eval: install the skills for Claude Code and Codex (user scope). +# This is the no-marketplace path. It symlinks each skill into the tools' skill +# directories, so a later `git pull` keeps every skill current automatically. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SKILLS="$HERE/skills" +[ -d "$SKILLS" ] || { echo "error: skills/ not found next to install.sh" >&2; exit 1; } + +link_into() { + local dest="$1" n=0 + mkdir -p "$dest" + for skill in "$SKILLS"/*/; do + [ -f "${skill}SKILL.md" ] || continue + ln -sfn "${skill%/}" "$dest/$(basename "$skill")" + n=$((n + 1)) + done + printf ' linked %d skills -> %s\n' "$n" "$dest" +} + +echo "Installing product-eval skills (user scope)..." +link_into "$HOME/.claude/skills" # Claude Code +link_into "$HOME/.agents/skills" # Codex (documented cross-tool skills path) +link_into "$HOME/.codex/skills" # Codex (alternate path some builds scan) + +echo +echo "Done. Start a new Claude Code or Codex session to load them." +echo "In Codex, invoke a skill explicitly with \$, for example \$start." diff --git a/product-eval/scripts/requirements.txt b/product-eval/scripts/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..c1a201db2d109f81e70e67070ce654a2e64825a9 --- /dev/null +++ b/product-eval/scripts/requirements.txt @@ -0,0 +1 @@ +PyYAML>=6.0 diff --git a/product-eval/scripts/validate_scope.py b/product-eval/scripts/validate_scope.py new file mode 100644 index 0000000000000000000000000000000000000000..7f7b069c190aa6abc69d7d3b1b76faf7e9f704c4 --- /dev/null +++ b/product-eval/scripts/validate_scope.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +"""Validate a product-eval investigation scope against DATA-CONTRACT.md. + +This is the optional, deterministic form of the "Validation (preflight conformance +check)" section of DATA-CONTRACT.md. The skills apply the same checks by hand on +every read; this script lets a team run them mechanically (in CI, a pre-commit hook, +or ad hoc) so a malformed write fails loudly instead of degrading downstream. + +Usage: + python scripts/validate_scope.py .product-eval// + +Exit codes: 0 = conformant, 1 = violations found, 2 = could not run (e.g. PyYAML +missing or path not found). Requires PyYAML (`python3 -m pip install -r scripts/requirements.txt`). +""" +import sys +import os + +try: + import yaml +except ModuleNotFoundError: + print( + "ERROR: this validator needs PyYAML. Install it with: " + "python3 -m pip install -r scripts/requirements.txt", + file=sys.stderr, + ) + sys.exit(2) + +SOURCE_TYPES = { + # DATA-CONTRACT taxonomy + "product_analytics", "support_ticket", "sales_call", "churn_survey", + "customer_interview", "internal_observation", "feature_request", "app_review", + "community_post", "competitor_analysis", "market_research", + # extras defined in evidence-quality.md + "internal_data", "social_media", "blog_or_article", +} +VERDICTS = {"Decide now", "Run a research sprint", "Do not commit yet", "Deprioritize"} +EVENTS = {"readiness", "override", "critique", "drift", "doc"} + + +def band(x): + return "low" if x < 40 else ("moderate" if x <= 69 else "high") + + +def expected_recommendation(value, confidence): + if value < 40: + return "Deprioritize" + if confidence >= 70: + return "Decide now" + if confidence >= 40: + return "Run a research sprint" + return "Do not commit yet" + + +class Report: + def __init__(self): + self.errors = [] + + def err(self, where, msg): + self.errors.append((where, msg)) + + def ok(self): + return not self.errors + + +def load_front_matter(path): + """Return the YAML front-matter dict of a markdown file (between the first two ---).""" + text = open(path, encoding="utf-8").read() + if not text.lstrip().startswith("---"): + return None + body = text.split("---", 2) + if len(body) < 3: + return None + return yaml.safe_load(body[1]) or {} + + +def load_yaml_list(path): + """Load a markdown file whose body is a YAML list (comments and headings ignored).""" + data = yaml.safe_load(open(path, encoding="utf-8").read()) + return data if isinstance(data, list) else [] + + +def validate(scope_dir): + r = Report() + scope = scope_dir.rstrip("/") + p = lambda *a: os.path.join(scope, *a) + + # ---- context.md ---- + ctx_path = p("context.md") + if not os.path.exists(ctx_path): + r.err("context.md", "missing") + else: + fm = load_front_matter(ctx_path) + if not fm: + r.err("context.md", "no YAML front-matter") + else: + for k in ("scope", "job", "stage"): + if k not in fm: + r.err("context.md", f"missing required key '{k}'") + + # ---- identities.md (optional) ---- + entities = set() + id_path = p("identities.md") + if os.path.exists(id_path): + for e in load_yaml_list(id_path): + if isinstance(e, dict) and e.get("entity"): + entities.add(e["entity"]) + + # ---- evidence/*.md ---- + ev_dir = p("evidence") + ev_ids = set() + ev_weight = {} # id -> weight + ev_refs = {} # id -> set of referenced ids (corroborated_by + contradicts) + if not os.path.isdir(ev_dir): + r.err("evidence/", "missing directory") + else: + for fn in sorted(os.listdir(ev_dir)): + if not fn.endswith(".md"): + continue + where = f"evidence/{fn}" + fm = load_front_matter(os.path.join(ev_dir, fn)) + if not fm: + r.err(where, "no YAML front-matter") + continue + for k in ("id", "claim", "source_type", "strength", "weight", "stale"): + if k not in fm: + r.err(where, f"missing required key '{k}'") + eid = fm.get("id") + if eid: + if eid in ev_ids: + r.err(where, f"duplicate id '{eid}'") + ev_ids.add(eid) + if fn != f"{eid}.md": + r.err(where, f"id '{eid}' does not match filename") + st = fm.get("source_type") + if st is not None and st not in SOURCE_TYPES: + r.err(where, f"unknown source_type '{st}'") + strg = fm.get("strength") + if not (isinstance(strg, int) and 1 <= strg <= 5): + r.err(where, f"strength must be an int 1..5, got {strg!r}") + wt = fm.get("weight") + if not (isinstance(wt, (int, float)) and wt >= 0): + r.err(where, f"weight must be a number >= 0, got {wt!r}") + elif eid: + ev_weight[eid] = wt + rd = fm.get("recency_days") + if rd is not None and not (isinstance(rd, int) and rd >= 0): + r.err(where, f"recency_days must be a non-negative int, got {rd!r}") + if not isinstance(fm.get("stale"), bool): + r.err(where, f"stale must be a boolean, got {fm.get('stale')!r}") + refs = set() + for key in ("corroborated_by", "contradicts"): + for ref in (fm.get(key) or []): + refs.add(ref) + ev_refs[eid] = refs + for key in ("person_id", "account_id"): + val = fm.get(key) + if val not in (None, "null") and entities and val not in entities: + r.err(where, f"{key} '{val}' not in identities.md") + # resolve evidence cross-refs now that all ids are known + for eid, refs in ev_refs.items(): + for ref in refs: + if ref not in ev_ids: + r.err(f"evidence/{eid}.md", f"references unknown evidence id '{ref}'") + + # ---- themes.md ---- + theme_ids = set() + theme_conf = {} + th_path = p("themes.md") + if not os.path.exists(th_path): + r.err("themes.md", "missing") + else: + for t in load_yaml_list(th_path): + if not isinstance(t, dict): + continue + tid = t.get("id") + where = f"themes.md[{tid}]" + if not tid: + r.err("themes.md", "theme missing 'id'") + continue + if tid in theme_ids: + r.err(where, "duplicate theme id") + theme_ids.add(tid) + if not t.get("name"): + r.err(where, "missing 'name'") + members = t.get("members") or [] + if not members: + r.err(where, "empty 'members'") + for m in members: + if m not in ev_ids: + r.err(where, f"member '{m}' is not a real evidence id") + tw = t.get("total_weight") + if isinstance(tw, (int, float)) and members and all(m in ev_weight for m in members): + member_sum = sum(ev_weight[m] for m in members) + if abs(member_sum - tw) > 1: + r.err(where, f"total_weight {tw} != sum of member weights {member_sum} (tolerance +/-1)") + conf = t.get("confidence") + cb = t.get("confidence_band") + if conf is not None: + theme_conf[tid] = conf + if cb and band(conf) != cb: + r.err(where, f"confidence_band '{cb}' inconsistent with confidence {conf} (expected {band(conf)})") + for c in (t.get("contradictions") or []): + if c not in ev_ids: + r.err(where, f"contradiction '{c}' is not a real evidence id") + for stype in (t.get("source_types") or []): + if stype not in SOURCE_TYPES: + r.err(where, f"unknown source_type '{stype}'") + + # ---- scores.md ---- + sc_path = p("scores.md") + if os.path.exists(sc_path): + for s in load_yaml_list(sc_path): + if not isinstance(s, dict): + continue + prob = s.get("problem") + where = f"scores.md[{prob}]" + if prob not in theme_ids: + r.err(where, f"problem '{prob}' has no matching theme") + value = s.get("value") + conf = s.get("confidence") + if not (isinstance(value, (int, float)) and 0 <= value <= 100): + r.err(where, f"value out of range: {value!r}") + if not (isinstance(conf, (int, float)) and 0 <= conf <= 100): + r.err(where, f"confidence out of range: {conf!r}") + vi = s.get("value_inputs") or {} + if vi: + tot = sum(vi.get(k, 0) for k in ("persona", "funnel", "frequency", "competitive")) + if isinstance(value, (int, float)) and tot != value: + r.err(where, f"value_inputs sum to {tot}, but value is {value}") + vb, cb = s.get("value_band"), s.get("confidence_band") + if isinstance(value, (int, float)) and vb and band(value) != vb: + r.err(where, f"value_band '{vb}' inconsistent with value {value} (expected {band(value)})") + if isinstance(conf, (int, float)) and cb and band(conf) != cb: + r.err(where, f"confidence_band '{cb}' inconsistent with confidence {conf} (expected {band(conf)})") + if prob in theme_conf and conf is not None and theme_conf[prob] != conf: + r.err(where, f"confidence {conf} disagrees with themes.md value {theme_conf[prob]} (one source of truth)") + rec = s.get("recommendation") + if rec not in VERDICTS: + r.err(where, f"recommendation '{rec}' not in the four-verdict vocabulary") + elif isinstance(value, (int, float)) and isinstance(conf, (int, float)) and not s.get("override"): + exp = expected_recommendation(value, conf) + if rec != exp: + r.err(where, f"recommendation '{rec}' disagrees with matrix '{exp}' for value={value}, confidence={conf}") + ov = s.get("override") + if ov not in (None,) and not (isinstance(ov, dict) and {"field", "to", "reason"} <= set(ov)): + r.err(where, "override must be null or {field, to, reason}") + + # ---- decisions-log.md ---- + dl_path = p("decisions-log.md") + if os.path.exists(dl_path): + for d in load_yaml_list(dl_path): + if not isinstance(d, dict): + continue + where = f"decisions-log.md[{d.get('item')}]" + for k in ("date", "item", "event", "verdict"): + if k not in d: + r.err(where, f"missing required key '{k}'") + if d.get("verdict") and d["verdict"] not in VERDICTS: + r.err(where, f"verdict '{d['verdict']}' not in vocabulary") + if d.get("event") and d["event"] not in EVENTS: + r.err(where, f"event '{d['event']}' not in {sorted(EVENTS)}") + cb = d.get("confidence_band") + conf = d.get("confidence") + if isinstance(conf, (int, float)) and cb and band(conf) != cb: + r.err(where, f"confidence_band '{cb}' inconsistent with confidence {conf}") + for key in ("evidence", "contested"): + for ref in (d.get(key) or []): + if ref not in ev_ids: + r.err(where, f"{key} references unknown evidence id '{ref}'") + outcome = d.get("outcome") + if isinstance(outcome, dict): + pwr = outcome.get("problem_was_real") + if pwr is not None and pwr not in ("confirmed", "refuted", "partial"): + r.err(where, f"outcome.problem_was_real '{pwr}' not in confirmed/refuted/partial") + so = outcome.get("solution_outcome") + if so is not None and so not in ("succeeded", "failed", "mixed"): + r.err(where, f"outcome.solution_outcome '{so}' not in succeeded/failed/mixed") + + return r + + +def main(): + if len(sys.argv) != 2: + print(__doc__) + sys.exit(2) + scope_dir = sys.argv[1] + if not os.path.isdir(scope_dir): + print(f"ERROR: not a directory: {scope_dir}", file=sys.stderr) + sys.exit(2) + r = validate(scope_dir) + if r.ok(): + print(f"OK: {scope_dir} conforms to DATA-CONTRACT.md") + sys.exit(0) + print(f"FAIL: {len(r.errors)} violation(s) in {scope_dir}\n", file=sys.stderr) + for where, msg in r.errors: + print(f" {where}: {msg}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/product-eval/skills/calibrate/SKILL.md b/product-eval/skills/calibrate/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..501c6c2125f993b1fa08f29665e5219f8889c1cb --- /dev/null +++ b/product-eval/skills/calibrate/SKILL.md @@ -0,0 +1,33 @@ +--- +name: calibrate +description: Tune and validate the scoring constants (evidence-weight base, diversity factors, gate threshold, crispness bars) against past decisions with known outcomes. Use when the user says "calibrate", "tune the thresholds", "validate the scoring", "the confidence numbers feel off", or has a set of past decisions to check the model against. Reports mismatches and recommended constant changes; never silently changes them. +--- + +# Calibrate + +Check the scoring system against reality. Take past decisions whose outcomes you know, recompute their weight / Confidence / Value / verdict under the current constants, compare to what actually happened, and recommend constant adjustments. The constants ship as heuristics, this is how they earn their numbers. + +## Scope, calibration is local, never global + +The plugin ships **universal default constants** that every install gets. Calibration does **not** edit those: it writes a **local override profile** (`.product-eval/calibration.yml`) owned by this team/product and read on top of the defaults. So tuning is team-scoped: it travels in your repo, survives plugin updates, and never imposes one team's idiosyncrasies on everyone else's install. The shipped defaults themselves change only between plugin versions, maintained centrally. + +## Inputs + +A set of **calibration cases**: ideally pulled from your own `decisions-log.md` once its entries have known outcomes (the log accumulates them as you use the tool; you append how each call turned out), or provided directly (see the template in `references/calibration.md`). Each case has the evidence available at decision time (source types, strengths, recency), the decision made, and the outcome, recorded as whether the **problem turned out real** (`problem_was_real`, the calibration target) and, as context only, whether the shipped solution worked (`solution_outcome`). You don't need data upfront, run on the shipped defaults until ~10 decisions have closed. + +## Workflow + +1. For each case, reconstruct the evidence and compute weight (evidence-quality rubric), Confidence (the scoring-framework formula), and Value; derive the verdict from the matrix. +2. Label each case against whether the **problem turned out real** (`outcome.problem_was_real`), not whether the shipped solution worked: + - **True positive**: model said Decide now, the problem was confirmed real. + - **False positive**: model said Decide now, the problem was refuted (over-confident, tighten). + - **False negative**: model said Run a research sprint / Deprioritize, the problem was confirmed real (under-confident, loosen). + - **True negative**: model held back, the problem was indeed not real or not worth it. + Treat `outcome.solution_outcome` as context, not a label input: a real problem can ship a failed solution, and tuning the scoring for that would punish it for the wrong thing. +3. Tally the confusion matrix and identify the dominant error direction. +4. Recommend constant changes, **one lever at a time**: with the expected effect (see the tuning table in the reference). +5. Propose an updated **calibration profile** and, on the user's approval, write it to `.product-eval/calibration.yml` (the local override: see the reference) plus a short calibration report. **Never edit the shipped rubric files**: they stay universal for every install; only the local profile changes. + +## Output + +The confusion matrix, the dominant error, and a ranked list of recommended constant changes with rationale. Note honestly that small samples are directional, treat as guidance until there are ~10+ cases, and change one lever per round so you can attribute the effect. End with `Next move:` and recommend accepting one local calibration change, collecting more closed decisions, or re-running a high-impact scope with the proposed profile. diff --git a/product-eval/skills/calibrate/references/calibration.md b/product-eval/skills/calibrate/references/calibration.md new file mode 100644 index 0000000000000000000000000000000000000000..7244d443b9711e351a65fe247e888d2b88c00497 --- /dev/null +++ b/product-eval/skills/calibrate/references/calibration.md @@ -0,0 +1,61 @@ +# Calibration reference + +## Case template (from your `decisions-log.md` once outcomes are known, or `calibration/cases.md`) +```yaml +- id: case-01 + problem: "the one-liner that was decided on" + evidence: # what was actually available at decision time + - {source_type: product_analytics, strength: 5, recency_days: 20} + - {source_type: support_ticket, strength: 4, recency_days: 30} + decision_made: "Decide now" # what was actually decided + outcome: + problem_was_real: confirmed # confirmed | refuted | partial -- THE calibration target + solution_outcome: succeeded # succeeded | failed | mixed -- context only, does NOT tune constants + notes: "problem confirmed real (activation +12pp once addressed); shipped solution also worked" +``` + +`problem_was_real` is what `calibrate` tunes against, because Confidence predicts whether the problem is real and worth acting on. `solution_outcome` is recorded for context only: a real problem can still ship a solution that flops for build or go-to-market reasons, and letting that move the constants would punish the scoring for the wrong thing. + +## Method + +For each case: recompute `weight` (evidence-quality rubric), `Confidence` (scoring-framework formula), and `Value` (scoring-framework), then read the verdict from the recommendation matrix. Label each TP / FP / FN / TN against **`outcome.problem_was_real`** (confirmed counts as a real problem, refuted as not, partial as a judgment call), never against `solution_outcome`. The dominant error direction over `problem_was_real` tells you which way to move the constants. + +## The calibration profile (config, not code) + +Tuned values live in a **local override profile**, never in the shipped rubric. The plugin reads its universal defaults, then applies this file if present: + +```yaml +# .product-eval/calibration.yml, overrides the shipped scoring defaults for THIS team/product. +# Written by `calibrate` from your closed decisions. Travels in your repo; survives plugin updates. +version: 1 +updated: 2026-06-28 +based_on_cases: 14 +confidence: + k: 16 # default 14, raised: defaults ran hot (FP-heavy) + diversity: {one: 0.65, two: 0.9, three_plus: 1.0} # default 0.7 / 0.9 / 1.0 +gate: + weight_threshold: 14 # default 12 +crispness: + evaluate_pass: 60 # default 60 + explore_pass: 40 # default 40 +notes: "Tuned on 14 closed decisions, Q1-Q2 2026." +``` + +Anything omitted falls back to the shipped default. Scope it to the workspace/product, one team, one product's evidence patterns; investigations into very different domains can keep separate profiles. + +## Tuning levers (change one at a time; write the new value to the profile) + +| Symptom | Lever | Profile key | +|---|---|---| +| Confidence too high everywhere | raise k (14 → 18) so it saturates slower | `confidence.k` | +| Good bets stuck at Run a research sprint | lower k, or lower the gate weight threshold | `confidence.k` / `gate.weight_threshold` | +| Single-source bets passing that shouldn't | lower the 1-type diversity factor (0.7 → 0.6) | `confidence.diversity.one` | +| Many weak items inflating weight | lower the low-strength base weights (advanced) | propose in report | +| Crispness blocking real problems | lower the stage bar (60 → 55) | `crispness.evaluate_pass` | + +## Guardrails + +- Change **one** lever, then re-run the cases, so you can attribute the effect. +- Small samples are directional; don't overfit to fewer than ~10 cases. +- The shipped rubric holds the **universal defaults**: never edit it. Write tuned values to `.product-eval/calibration.yml`; the skill proposes, the human approves. +- Watch both error types: tightening to kill false positives will create false negatives. Aim for the balance your context wants (a founder may tolerate more false positives than an enterprise PM). diff --git a/product-eval/skills/critique/SKILL.md b/product-eval/skills/critique/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..852db00e84cfc1d756ca3a4ceb2f16b8335aacee --- /dev/null +++ b/product-eval/skills/critique/SKILL.md @@ -0,0 +1,48 @@ +--- +name: critique +description: "Use when someone wants existing thinking attacked, graded, or handed to fresh eyes, not written or summarized. The point is to break the artifact, not produce it. It owns three intents: a skeptical outside read of conclusions, findings, themes, a synthesis, a brief, or a FAQ, from someone who was not in the room; running those findings past hostile lenses (a wary customer, CFO, engineer, competitor) to see where they collapse; and grading a problem statement or one-liner (right altitude, names a real persona, or vague mush) with a score and what is missing. Reach for it on 'give this a fresh adversarial read', 'put this in front of a skeptical X', 'score how crisp this problem statement is', 'poke holes', 'red-team this'. Do not use it to create or restate the artifact. Attacking a whole plan, roadmap, or PRD routes through pressure-test; this skill owns findings, syntheses, problem statements, and panels." +--- + +# Critique + +Run an independent adversarial review of an artifact, evidence, a theme, a problem one-liner, a score, a brief, or a FAQ. The entire value is **independence**: critique never grades work it produced, and never argues to agreement. Run it as a fresh pass (a subagent with its own context; a different model where available). + +## Independence (mandatory, read first) + +Run as a separate pass with its own context, ideally a different model, so the agent that produced a finding never grades it. A critic sharing the author's context tends to rubber-stamp, which defeats the purpose. Treat the author's reasoning as something to attack, not a position to defend. State in the output that it ran independently. + +**Mechanism:** spawn each critic/lens pass as a Task-tool subagent (a general-purpose agent) whose context contains only the artifact under review plus the relevant rubric, not the author's reasoning chain. For high-stakes reviews, prefer a different model. The bounded panel rounds are driven by the parent: collect each subagent's findings, let the author revise, then re-spawn fresh subagents for the next round. + +## Three modes, pick by what's under review + +### 1. Transition critic (default, lightweight) + +Run at any stage handoff (evidence → themes → problem → score → plan). Apply the relevant checks in `../pressure-test/references/critic-checklist.md` plus the always-on cross-stage consistency checks. Return blocking issues (with fixes), suggestions, strengths, and overall readiness (ready / needs fixes / not ready). Every flag must be actionable. + +### 2. Crispness grader (for a problem one-liner) + +Score the problem statement on the dimensions in `references/crispness-rubric.md` (0-10 each), total it, and **block on any dimension scoring 0**. Return the lowest-scoring dimensions and a one-line rewrite that lifts them. Scale the bar to the stage, an exploratory founder one-liner needs altitude and a named persona, not yet a measured consequence. Flag, don't tyrannize. + +### 3. Multi-lens panel (heavy; for a brief, a FAQ, or a high-stakes finding) + +Convene the lenses in `references/lenses.md`, skeptical customer, competitor, economics/CFO, feasibility/engineer, contrarian, security/legal (only where relevant). Each lens attacks the answers or findings it owns. Run **bounded rounds (max 3)**; stop early when a round raises no new substantive issue. Terminate every point into one of three states, **never "consensus"**: + +- **Resolved**: answered and backed by evidence that clears the gate. +- **Open**: needs data the panel lacks → emit a specific gap question (with where to look) routed to `gather-evidence`. Do not argue it closed. +- **Contested**: a genuine unresolved disagreement → log it as a recorded risk for the human to weigh. + +## Calibration + +The critic flags; it does not block everything. Match severity to stage: early discovery tolerates thin evidence (flag as a gap); a commit decision does not. An over-eager critic that demands strength-5 proof from a day-one idea is miscalibrated. + +## Relationship to pressure-test + +`pressure-test` is the entry point for attacking a whole existing artifact (a plan/roadmap/PRD/bet), it runs this engine. `critique` is the general form, usable at any transition, for crispness, or as the panel. One adversarial engine; `pressure-test` is its plan-level door. + +## Record + +Append findings, the crispness score, and any open/contested items to the investigation's `decisions-log.md`. Critique informs; it does not silently edit the artifact. + +## Output + +Lead with the verdict. Modes 1-2: blocking / suggestions / strengths / readiness (plus the crispness score and rewrite). Mode 3: each point tagged resolved / open / contested, lead with the count in each state, open items carrying their gap question, contested items carrying the dissent. End with `Next move:` and recommend revising the artifact, gathering the named missing evidence, or pressure-testing it if it is a plan/roadmap/PRD. diff --git a/product-eval/skills/critique/references/crispness-rubric.md b/product-eval/skills/critique/references/crispness-rubric.md new file mode 100644 index 0000000000000000000000000000000000000000..1f4f11a429081e3c235e97a958c3896974ce0448 --- /dev/null +++ b/product-eval/skills/critique/references/crispness-rubric.md @@ -0,0 +1,45 @@ +# Problem One-Liner Crispness Rubric + +Score a problem statement on 8 dimensions, 0-10 each (max 80). Any dimension at **0 is blocking**: rewrite required. The pass threshold scales with stage (below). Always return the total, the lowest-scoring dimensions, and a one-line rewrite that lifts them. + +## Dimensions + +**1. Persona specificity**: a named, specific segment, not "users". +- 0: "users" / "customers" / unspecified · 5: a bare role ("admins") · 10: qualified segment ("admins on 5+ seat teams during trial"). + +**2. Task / job concreteness**: a specific job-to-be-done, not "a better experience". +- 0: "engage more", "have a better experience" · 5: vague action ("set things up") · 10: concrete task ("configure role-based permissions"). + +**3. Context / trigger**: when/where it happens. +- 0: none · 5: implied · 10: explicit trigger ("during onboarding, before the first invite"). + +**4. Root cause**: why it happens (or an honest "unknown"). +- 0: a solution disguised as a cause ("because there's no wizard") · 5: a restated symptom · 10: a named mechanism ("permissions require manual API calls") or an explicit "root cause unknown, to investigate". + +**5. Consequence (measurable)**: the business impact. +- 0: none · 5: qualitative ("frustration") · 10: measurable ("34% of trials abandon at this step"). + +**6. Altitude**: buildable in 2-6 weeks; not a category, not a ticket. +- 0: a category ("onboarding is bad") or a ticket ("move the button") · 5: slightly too broad/narrow · 10: a team could address it in 2-6 weeks. + +**7. Distinctness**: one problem, not a bundle. +- 0: a bundle ("enterprise struggles") · 10: one clean problem, distinct from the others on the list. + +**8. Evidence link**: grounded, not asserted. +- 0: asserted, no evidence · 5: one weak source · 10: linked to strength-3+ evidence from 2+ source types. + +## Blocking & thresholds + +- **Any dimension at 0 → blocked**, rewrite required. +- Pass threshold by stage: + - **Explore (founder / new space):** ≥ 40/80, and no 0 on Persona, Task, or Altitude. Consequence, Root cause, and Evidence may be low, mark them as gaps to close, not blockers. You are still discovering. + - **Evaluate / Validate (committing resources):** ≥ 60/80, no 0 anywhere, and Consequence + Evidence link ≥ 6. The bar is higher because you are about to spend. + +## Worked examples + +- *"Onboarding is bad."* → persona 0, task 0, context 0, root cause 0, consequence 0, altitude 0, distinct 5, evidence 0 → **5/80, blocked** (it's a category). Rewrite required. +- *"Teams with 5+ members can't configure role-based permissions during onboarding without contacting support, causing trial abandonment."* → persona 9, task 9, context 8, root cause 6, consequence 8, altitude 9, distinct 9, evidence 7 → **~65/80, passes** at the evaluate stage. + +## Always return + +The total, the two or three weakest dimensions, and the rewrite that would raise them. The grader proposes the sharper one-liner; it does not silently replace the author's. diff --git a/product-eval/skills/critique/references/lenses.md b/product-eval/skills/critique/references/lenses.md new file mode 100644 index 0000000000000000000000000000000000000000..0bcce45db652faf617787868b5c817d43d6e3c51 --- /dev/null +++ b/product-eval/skills/critique/references/lenses.md @@ -0,0 +1,34 @@ +# Adversarial Lenses & Termination Protocol + +The multi-lens panel attacks a brief, a FAQ, or a high-stakes finding from independent angles, then terminates each point honestly. Used by `critique` mode 3. + +## The lenses + +Assign each lens to the answers/findings it owns; run only those relevant to the artifact. + +- **Skeptical customer**: "Would I actually switch for this, or is my current workaround fine? Is this a painkiller or a vitamin?" +- **Competitor**: "We'd close this gap in a sprint." Cross-check the competitor's changelog/release notes; gaps that already closed get retired. +- **Economics / CFO**: "Do the unit economics survive the downside case? What's break-even? Which single assumption is doing all the work?" +- **Feasibility / engineer**: "The hard part is the part the brief waves past. What's the riskiest technical assumption, and what if it's wrong?" +- **Contrarian PM**: actively hunts disconfirming evidence, single-source grounding, vanity metrics, and problem-statement drift. +- **Security / legal**: privacy, compliance, and data handling (only where the artifact touches them). + +## Independence (mandatory) + +Each lens runs as a separate pass with its own context; prefer a different model. A panel of one model agreeing with itself is theater. The author's reasoning is input to attack, not a position to defend. + +## Bounded rounds + +Max 3 rounds. After each, the author revises or flags; stop early when a round raises no new substantive issue. Reserve the full panel for high-stakes findings, it's expensive. + +## Termination, three states, never "consensus" + +Agreement among agents is not truth, and more argument cannot resolve a real disagreement, only evidence can. Terminate every point into exactly one state: + +1. **Resolved**: answered and backed by evidence that clears the gate. +2. **Open**: needs data the panel lacks → emit a specific gap question (with where to look), routed to `gather-evidence` or a research sprint. Do not talk it closed. +3. **Contested**: a genuine, unresolved disagreement → log it as a recorded risk in the decisions log, naming who would need to decide. + +## Output + +Each attacked point tagged resolved / open / contested. Lead with the count in each state, "12 resolved, 2 open, 1 contested" is an honest map of where the artifact stands. Open items carry their gap question; contested items carry the dissent. diff --git a/product-eval/skills/decision-readiness/SKILL.md b/product-eval/skills/decision-readiness/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..687bb423419068412156430083d02e9625a724b2 --- /dev/null +++ b/product-eval/skills/decision-readiness/SKILL.md @@ -0,0 +1,50 @@ +--- +name: decision-readiness +description: "Judge whether there is enough evidence to make a build decision on a specific problem or bet, and return one of three outcomes: Decide now, Run a research sprint, or Do not commit yet. Use when the user asks \"do we have enough data to decide?\", \"is this worth building?\", \"are we ready to commit to X?\", \"what's the evidence for this?\", or \"decision readiness\". This is the core gate, Confidence band drives the decision; signal strength is assessed per evidence item." +--- + +# Decision Readiness + +Answer the question every other tool skips: *does the user have the right to make this call yet?* Assess the evidence behind a specific bet, run the sufficiency gate, compute Confidence, and return a clear outcome with the exact gaps to close. This is the main product moment, lead with the verdict, then show the evidence math below it. + +## Workflow + +### 1. Identify the bet and gather its evidence + +Pin down the specific problem/bet being decided (lightly frame it first if it is vague). Collect its evidence from `.product-eval//evidence/`, from connected sources, and from anything the user pastes or uploads (CSV, notes, transcripts). If sources are connected, pull relevant items; if not, accept what the user provides and note the limitation. + +**Preflight (data contract).** Before trusting scoped files, run the conformance preflight in `DATA-CONTRACT.md` (Validation) on the `evidence/`, `themes.md`, and `scores.md` you read. A failed check (for example `scores.md` confidence not matching `themes.md`, or a band inconsistent with its number) stops the readiness call with the exact violation, so the gate never runs on inconsistent data. + +### 2. Rate and weight each evidence item + +Using `references/evidence-quality.md`, rate every item 1-5 and tag it (source type, persona, funnel stage, impact type, recency). Then **dedup by resolved identity** (one user appearing across channels is not several voices), and compute each item's **weight** (base-by-strength × recency). Merge exact duplicates; keep independent cross-source corroboration (it adds weight). Keep contradictory evidence and surface it; it is valuable for an honest call. + +### 3. Triangulate (optional but valuable) + +If the bet rests on internal evidence (e.g. a ticket cluster), search public sources to see whether the same pain appears externally. A match raises confidence and may indicate an industry-wide opportunity; no match suggests it is specific to this product/segment. Record the result as additional evidence and as added source diversity. + +### 4. Run the sufficiency gate (binary) + +Using `references/decision-gate.md`, compute the **total evidence weight** (strength-weighted and deduplicated, quality and independence, not raw count) and check the floors: weight below threshold; nothing rated strength 3+; single source type; or a vague problem statement. Report exactly which floor failed and the fix. + +### 5. Compute Confidence and the verdict + +**Source of truth:** if this item is already scored in `.product-eval//scores.md`, use that Value and Confidence and skip recomputation (recompute only if absent, then write the result back). Otherwise compute Confidence per `references/decision-gate.md` (saturating base from evidence weight × diversity × contradiction penalty), apply any **bounded ±10 adjustment** for context the inputs miss (state the reason, log it), and report it as a **band**: Low / Moderate / High, not a bare point. Combine the band with the item's Value band and map to the matrix to produce the outcome: + +- **Decide now** (passes gate, Confidence 70+), enough to commit. +- **Run a research sprint** (passes gate, Confidence 40-69), promising but get specific proof first. +- **Do not commit yet** (fails gate, or Confidence below 40), not enough to decide. + +Confidence drives the verdict. Respect the confidence ceiling in `.product-eval//sources.md`, if the connected sources cannot reach 70, say so explicitly. + +### 6. Emit gaps with where to look + +Whenever the verdict is not "Decide now", list specific gap questions, each naming **where to look** and **which source or connector would close it** (e.g. "Do product analytics confirm the drop-off at the permissions step? Connect Amplitude/Datadog" or "Have non-sales sources named this, or is it only sales-requested?"). Avoid generic "needs more research". On a **Run a research sprint** verdict, hand these gaps to `design-test` to turn them into the single cheapest experiment that would move Confidence past the gate. + +### 7. Record + +Append the verdict, Confidence with reasoning, evidence item IDs, and the gaps to `.product-eval//decisions-log.md`. Offer the next step: close a gap, run a research sprint, or proceed to pressure-test / the output layer. + +## Output + +A direct outcome (Decide now / Run a research sprint / Do not commit yet), the Confidence band with concise reasoning, what the gate found, and a short list of gaps-with-where-to-look. Put exact math below the verdict or in the scope file. Do not soften "Do not commit yet" into a maybe. End with `Next move:`: Decide now → pressure-test if high-stakes, then write the memo/build brief; Run a research sprint → design the cheapest test; Do not commit yet → gather the missing source, reframe, or deprioritize. diff --git a/product-eval/skills/decision-readiness/references/decision-gate.md b/product-eval/skills/decision-readiness/references/decision-gate.md new file mode 100644 index 0000000000000000000000000000000000000000..7361ab3b1e858de0844fc06ffee55dda55c9e94e --- /dev/null +++ b/product-eval/skills/decision-readiness/references/decision-gate.md @@ -0,0 +1,45 @@ +# Decision Gate Reference + +The two-stage gate that decides whether a bet is ready. Stage 1 is binary and blocking; Stage 2 is the graded verdict. + +## Stage 1, sufficiency gate (binary, blocking) + +The bet is **not ready** if ANY of these fail. Report which floor failed and the specific fix. + +- **Total Evidence Weight ≥ 12** (see the evidence-weight rubric), this replaces a raw item count, so quality counts, not volume. Roughly: one strong first-party finding plus corroboration, or several solid moderate items. +- At least **one item rated strength 3+** (a quality floor, weight cannot be bought with many weak items). +- Evidence comes from **more than one independent source type** (diversity floor, dedup same-user/account first, so one voice across channels is not mistaken for several). +- A **non-vague problem statement** exists (passes the altitude test). + +These are intentionally hard. Weight is computed on *deduplicated, independent* evidence. A bet that fails any floor cannot be "Decide now" no matter how high Value looks. + +## Stage 2, confidence verdict (graded) + +If Stage 1 passes, compute Confidence with the formula in `../../prioritize-backlog/references/scoring-framework.md` (show the computation in audit detail), apply any **bounded ±10 adjustment** (logged, with a reason) for context the inputs miss, and report it as a **band**: Low (<40) / Moderate (40-69) / High (≥70), not a bare point. Combine the band with Value and read the outcome: + +| Condition | Outcome | Meaning | +|---|---|---| +| Gate passed + Confidence 70+ | **Decide now** | Enough credible, diverse evidence to commit | +| Gate passed + Confidence 40-69 | **Run a research sprint** | Promising; get the specific missing proof first | +| Gate failed, or Confidence below 40 | **Do not commit yet** | Not enough to decide; do not commit | + +**Confidence drives the verdict.** Value sizes the prize; Confidence earns the right to act on it. A high-Value, low-Confidence bet should become a research sprint, not a Go. + +## Confidence ceiling + +Read `.product-eval//sources.md`. If the connected source types cannot, even at best, produce strength-3+ first-party diversity, the reachable Confidence is capped (Moderate). Say so explicitly: "Your current sources cap this at Moderate Confidence. To reach Decide now, add [source]." Never report a Confidence the sources can't support. + +## Gap questions, format + +Every non-"Decide now" outcome must include specific gaps, each with where to look and which source closes it. Good: + +- "Do product analytics confirm a drop-off at the permissions step?, check Amplitude/GA4 funnels, or connect them." +- "Have enterprise customers raised this, or only SMB?, segment the support tickets." +- "Is this still true since the Q2 UI change?, check ticket recency." +- "Is the competitor's solution genuinely better or just different?, read their docs and recent G2 reviews." + +Avoid generic "needs more research", always name the where and the which-source. + +## What this gate does NOT do + +It rates the **credibility, diversity, and recency of evidence as described**: it does not verify that a cited number is true. "Enough data to decide" means "enough credible, diverse, recent evidence per the rubric," not "objectively correct." Keep that honesty visible to the user. diff --git a/product-eval/skills/decision-readiness/references/evidence-quality.md b/product-eval/skills/decision-readiness/references/evidence-quality.md new file mode 100644 index 0000000000000000000000000000000000000000..6ed9ef8ea5349f02ff99ee0a89bc381394226083 --- /dev/null +++ b/product-eval/skills/decision-readiness/references/evidence-quality.md @@ -0,0 +1,101 @@ +# Evidence Quality Reference + +What constitutes high-quality evidence for a product problem, and how to rate it. Used when assessing and structuring evidence items. + +## Evidence strength (1-5) + +**Strength 5: direct, quantitative, first-party** +- Product analytics showing measurable drop-off, churn correlation, or failure rate +- A/B test results demonstrating impact +- Revenue or retention data tied to the issue +- Large-scale survey (n > 100) with clear signal +- e.g. "Funnel analytics show 34% drop-off at the permissions step, up from 22% last quarter." + +**Strength 4: direct qualitative first-party, or strong third-party quantitative** +- Customer interview quotes describing the exact pain unprompted +- Support ticket clusters (10+ tickets on the same issue in a window) +- Sales calls where prospects cite this as a reason not to buy +- Churn-survey responses explicitly naming the issue +- Detailed, specific G2/Capterra complaints +- e.g. "In 3 separate interviews, admins called onboarding 'confusing' and needed support to finish setup." + +**Strength 3: indirect first-party, or direct third-party qualitative** +- Feature requests that imply the problem +- Competitor-comparison reviews ("I switched to X because of Y") +- Community posts (Reddit, HN) with significant engagement +- Related (not exact) support tickets +- Internal observations from CS, Sales, Engineering +- e.g. "Reddit thread, 89 upvotes, asking if anyone else finds permissions impossible." + +**Strength 2: indirect or anecdotal** +- Single complaint without corroboration +- Analyst reports on the category (not your product) +- Competitor feature releases implying demand +- Social mentions without engagement +- e.g. "One tweet wishing the product had better onboarding." + +**Strength 1: speculative or very indirect** +- General industry trends that might apply +- Analogies from adjacent products +- Theoretical arguments without data +- Outdated evidence (> 12 months in fast-moving markets) + +## Evidence weight (not just count) + +A raw count is crude, three strength-5 first-party findings far outweigh fifty vague anecdotes. Convert strength into a weight so quality dominates, then sum over deduplicated items. + +**Base weight by strength (non-linear so quality dominates; tunable):** + +| Strength | Base weight | +|---|---| +| 5 | 12 | +| 4 | 7 | +| 3 | 4 | +| 2 | 2 | +| 1 | 1 | + +**Modifiers (multiplicative on base):** + +- **Recency:** ×1.0 if < 3 months, ×0.7 if 3-6, ×0.4 if 6-12, ×0.2 if older (fast-moving markets). +- **Independence / dedup:** collapse items that are the same user/account across channels (see `identity-resolution.md`) or exact duplicates, count the strongest once. Independent corroboration from a *different* source type adds a modest bonus (e.g. +25% of the corroborating item's weight), not a full second count. +- Do **not** add a separate first-party multiplier, first-party-ness is already baked into the strength rating. + +**Total Evidence Weight** = sum of (base × recency) over deduplicated items + corroboration bonus. + +These numbers are a starting heuristic, keep them explicit and tunable, and always show the math (e.g. "Weight 19: two strength-5 first-party (recent) + one strength-3, across 2 source types"). Treat weight as a transparent composite, not a precise truth. + +## Source type taxonomy + +**First-party:** product_analytics, support_ticket, sales_call, churn_survey, customer_interview, internal_data, internal_observation, feature_request + +**Third-party:** app_review, community_post, social_media, competitor_analysis, market_research, blog_or_article + +## Claim extraction rules + +1. **State the pain, not the solution.** "We need Slack integration" → "Users lack visibility into events in their communication tools." +2. **Quantify when possible**, and flag when quantification is missing. +3. **Preserve the persona** (admin vs end-user vs buyer). +4. **Don't editorialize**: reflect what the evidence says. +5. **Flag temporal relevance**: evidence 6+ months old in a fast market tempers confidence. + +## Tags + +- **Persona:** admin, end_user, buyer, developer, manager (tag all that apply, primary first) +- **Funnel stage:** awareness, activation, engagement, retention, expansion, advocacy +- **Impact type:** conversion, activation, retention, expansion, cost, competitive + +## Duplicate & corroboration handling + +- Exact duplicate (same source, same claim): merge. +- Corroborating (different source, same claim): keep both, independent corroboration raises confidence. +- Related but distinct: keep both, suggest a shared theme. +- Contradictory: keep and flag, valuable for an honest assessment. + +## Gap detection patterns + +- No quantitative data → suggest analytics or a survey +- Single-source bias → suggest diversifying source types +- No first-party data → suggest tickets, calls, analytics +- Persona blind spot → suggest investigating other personas +- No recency → suggest checking whether the problem still exists +- No counter-evidence → suggest looking for reasons it might NOT be a problem diff --git a/product-eval/skills/design-test/SKILL.md b/product-eval/skills/design-test/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..a2d98343051f07e0984caf3f08e86b16f7fd94bb --- /dev/null +++ b/product-eval/skills/design-test/SKILL.md @@ -0,0 +1,33 @@ +--- +name: design-test +description: Turn a "Run a research sprint" verdict (promising but under-evidenced) into the cheapest experiment that would close the gap, a concrete validating test with a hypothesis, method, sample, effort, and pass/fail threshold. Use when decision-readiness returns Run a research sprint, or when the user asks "how do we validate this", "what's the cheapest test", "how do we de-risk this bet", or has a promising idea with thin first-party evidence. Designs the test; its results feed back into gather-evidence. Do NOT use to pull existing data (that is gather-evidence) or to judge sufficiency (that is decision-readiness). +--- + +# Design Test + +When a bet is promising but under-evidenced (a **Run a research sprint** verdict), the answer isn't to build or to drop it, it's to run the cheapest experiment that would move Confidence past the gate. This skill designs that experiment. It is the bridge that *generates new first-party signal*, where `gather-evidence` only pulls existing data. + +## When to run + +After `decision-readiness` returns **Run a research sprint** (or **Do not commit yet** with a clear hypothesis), or when the user asks how to validate / de-risk a bet. Read the open gaps from `.product-eval//decisions-log.md`, those are exactly what the test must close. + +## Workflow + +### 1. Name the riskiest assumption +From the gaps, find the single assumption that, if wrong, kills the bet (e.g. "clients miss the email because of spam" vs "they just ignore it"). Test that, not everything. + +### 2. Pick the cheapest method that could disconfirm it +Match the assumption to the lightest test that could prove it *wrong* (see `references/experiment-patterns.md`): fake-door / smoke test, landing page + ad, concierge / Wizard-of-Oz, 5-10 interviews, a survey, an A/B test, or an instrumentation change. Prefer lowest cost and fastest signal. + +### 3. Set a falsifiable threshold *before* running +State what result counts as pass vs fail, a specific number and window (e.g. "≥15% of ~200 landing visitors leave an email in 2 weeks; below 8% kills it"). No post-hoc goalposts. + +### 4. Spec the test +Output: hypothesis · method · audience and sample size · what to instrument · effort/cost · duration · pass/fail threshold. Keep it the smallest test that would change the decision. + +### 5. Close the loop +The test produces new first-party evidence (strength 4-5). When results are in, feed them through `gather-evidence`, then re-run `decision-readiness`, Confidence should move, and the bet becomes Decide-now or gets killed honestly. + +## Output + +A one-page test spec (riskiest assumption · method · sample · instrumentation · effort · duration · pass/fail threshold) plus the explicit "what result would change the decision." Log it in `.product-eval//decisions-log.md`. End with `Next move:` and tell the user to run the test, then feed the results back as evidence for a new readiness call. diff --git a/product-eval/skills/design-test/references/experiment-patterns.md b/product-eval/skills/design-test/references/experiment-patterns.md new file mode 100644 index 0000000000000000000000000000000000000000..82f3e9a8cf34d67a8affa81c28be020a49141e80 --- /dev/null +++ b/product-eval/skills/design-test/references/experiment-patterns.md @@ -0,0 +1,20 @@ +# Experiment patterns, cheapest test by assumption type + +Pick the lightest method that could *disconfirm* the riskiest assumption. Bias to cheap and fast. + +| You need to learn | Cheapest test | Signal | Rough effort | +|---|---|---|---| +| Will anyone want it? (demand) | Fake-door / smoke test; landing page + small ad spend | click / sign-up rate vs threshold | hours-days | +| Will they pay or switch? | Pre-order, paid pilot, "fake" pricing page | conversions / letters of intent | days | +| Do they have the pain we assume? | 5-10 problem interviews (no pitching) | unprompted mentions, workarounds | days | +| Which framing lands? | A/B test or two landing variants | conversion delta | days | +| Is the workflow usable? | Concierge / Wizard-of-Oz (do it manually) | completion, time, drop-off | days-1 wk | +| Is the quantitative claim real? | Instrument the funnel / event; pull after a week | the actual rate | 1 wk | +| Would they recommend it? | Survey + NPS to a real segment (n > 30) | top-box %, themes | days | + +## Rules +- **Test to disconfirm, not confirm.** Design the result that would kill the bet, then see if reality refuses to produce it. +- **One assumption per test**: the riskiest one. Don't bundle. +- **Threshold before data**: a number + window, written before running. No moving goalposts. +- **Cheapest that could change the decision**: if the result wouldn't change what you do, don't run it. +- Results are first-party evidence → feed back via `gather-evidence`, then re-run `decision-readiness`. diff --git a/product-eval/skills/discover-sources/SKILL.md b/product-eval/skills/discover-sources/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..2404c981010fcfb15cb4d1df029d60d8bfc337f5 --- /dev/null +++ b/product-eval/skills/discover-sources/SKILL.md @@ -0,0 +1,61 @@ +--- +name: discover-sources +description: "Map what product-data sources a user can draw on, connectors, tools, files, offline systems, and the decision-confidence ceiling those sources allow. Use when the intent is discovery and setup of the evidence base itself: \"what data do I have / could connect\", \"scan/inventory my sources\", \"is X hooked up\", \"where could product insights come from\", \"how confident could any decision be given my data\", or starting a new project before any framing or prioritizing. Do NOT use when the user wants you to actually work the data they already have, pulling, summarizing, grading, or reporting on tickets, reviews, surveys, metrics, scorecards, or trends. Those are analysis and reporting tasks, not source discovery. The trigger is \"what evidence exists and how good can it get,\" not \"analyze the evidence.\" Produces a source map plus a confidence ceiling." +--- + +# Discover Sources + +Inventory where product-relevant data lives for this user, map each source to the kind of evidence it can produce and the maximum strength that evidence can reach, and record the resulting decision-confidence ceiling. This is the foundation every other skill reads. + +## When to run + +Run this first in a new project, or whenever the user asks what data they have or could connect. If `.product-eval//sources.md` already exists, read it and offer to refresh rather than starting over. + +## Workflow + +### 1. Scan three surfaces + +No single discovery surface is reliable, so check all three: + +1. **Live tools**: Look at which MCP tools / connectors are actually callable in this session right now, including any that are present but require authentication. This is the most authoritative signal for "what can be pulled today." List what you find. +2. **Registry**: If a connector registry is available (e.g. `search_mcp_registry`), search it for product-data categories the user does not yet have connected, so they can be suggested with a one-click connect. +3. **Catalog-prompted**: Walk the categories in `references/source-catalog.md` and ask the user about offline or non-connectable sources. For anything that cannot be connected, offer CSV / file upload as the on-ramp. + +Do not assume a source is unavailable just because one surface is silent. (It is common for a registry to report nothing while live connectors are in fact present.) + +**Probe to confirm, don't just detect.** A tool being present is not proof it returns data. For each detected connector, make one cheap probe call (pull a single recent item) and tag the source **reachable / needs-auth / empty / blocked**; when it works, note the access pattern that worked (tool · operation · params) so `gather-evidence` reuses it. See `../gather-evidence/references/fetch-strategies.md`. + +### 2. Map every found source to an evidence role + +For each source the user has or uploads, record, using `references/source-catalog.md`: + +- the **evidence role** it plays (e.g. first-party quantitative, support-ticket cluster, third-party qualitative), +- the **maximum evidence strength** it can yield (1-5), +- which **metric-tree branch** it illuminates (acquisition, activation, engagement, retention, expansion, advocacy), +- the **join keys** it exposes (email, user_id, domain, username) for identity resolution, see `references/identity-resolution.md`, so the same person or account can be linked across sources later. Note whether a CDP or identity graph is present. + +### 3. Compute the confidence ceiling + +The set of connected sources caps how confident any decision can become. Determine the ceiling honestly: + +- Only third-party / indirect sources (reviews, community, market tools, feature requests) → ceiling is **Moderate**; decisions will tend to land at *Run a research sprint* or *Do not commit yet*. +- At least one strong first-party quantitative source (product analytics, logs) plus corroboration from another source type → ceiling can reach **Decide now**. + +State the ceiling plainly and name the single highest-leverage source the user could add to raise it. + +### 4. Write the source map + +Write `.product-eval//sources.md` (create `.product-eval//` if it does not exist) with: the sources found per surface, the role/strength/branch mapping, the join keys per source (and whether a CDP or identity graph is present), each source's probe status (reachable / needs-auth / empty / blocked) and working access recipe, coverage gaps, the confidence ceiling, and the top 1-2 suggested connectors to add. Every other skill reads this file and degrades gracefully against it. + +### 5. Recommend the next step + +Tell the user what they can and cannot do given their sources, then recommend the next action: no first-party data → gather evidence outside-in, then `synthesize`; rich connectors → gather evidence then `prioritize-backlog`; an existing roadmap → `pressure-test`. + +## Output + +A short, scannable summary in chat (sources found, confidence ceiling, top gap to close) plus the written `.product-eval//sources.md`. Keep the chat summary free of internal file paths unless the user asks. End with `Next move:` and recommend the one action that moves the user toward delivery: upload/pull evidence, gather outside-in signal, rank opportunities, or pressure-test an existing plan. + +## Notes + +- CSV / file upload is the universal on-ramp: any source that can export becomes usable evidence even with zero connectors. Strength comes from the declared source type and content, not from how the data arrived. +- The web is always available as a corroboration layer for later steps, even when the user has no internal data. diff --git a/product-eval/skills/discover-sources/references/identity-resolution.md b/product-eval/skills/discover-sources/references/identity-resolution.md new file mode 100644 index 0000000000000000000000000000000000000000..fb81f371b9e72c554006acb0101ed579b0aa3611 --- /dev/null +++ b/product-eval/skills/discover-sources/references/identity-resolution.md @@ -0,0 +1,54 @@ +# Identity Resolution Reference + +Recognize when records from different sources refer to the same person or account, so multi-touchpoint signals are visible and breadth counts are not inflated. `discover-sources` detects which join keys each source exposes; `gather-evidence` performs the stitching. + +## Two grains + +- **Person**: one individual across tools. +- **Account / org**: the company they belong to. In B2B this is often the more useful unit. + +## Join keys, by reliability + +- **Strong (deterministic):** shared `email`; shared `user_id` / `external_id` when the company uses a consistent ID across tools. +- **Account-level (deterministic):** `email domain` → company. Groups individuals into an account even when individuals can't be matched. Exclude consumer domains (gmail, outlook), never merge all gmail users into one "account". +- **Weak (probabilistic):** `name` + `company`, phone, social handle. Fuzzy, thresholded, flagged for review. + +## Keys by source (typical) + +| Source | Join key | Grain | +|---|---|---| +| Intercom, Zendesk, Help Scout | email, company | person + account | +| Salesforce, HubSpot | email, account (+ ARR, segment, renewal) | person + account | +| Amplitude, PostHog | user_id / external_id; email if identified | person (often pseudonymous) | +| Google Analytics (GA4) | client_id (pseudonymous) | rarely joinable to a person | +| GitHub | username (email often private) | best-effort person | +| CDP (Segment, RudderStack) | pre-resolved identity graph | person + account | + +## Matching ladder (precision-first) + +1. Exact `email` or shared `user_id` → merge (high confidence). +2. Else `email domain` → account-level group (medium-high; exclude consumer domains). +3. Else fuzzy `name + company` with a score → propose, do not auto-merge below threshold; surface for review. + +Record method + confidence per merge. Keep merges reversible and inspectable. Never silently merge on weak keys, a false merge corrupts the evidence. + +## Why it matters, two opposite uses (the key nuance) + +- **Severity / journey (collapse to one entity):** the same user appearing in Intercom + Zendesk + a sales call escalated across channels, a severity signal, and the touchpoints reconstruct a journey (PostHog error → chat → ticket → call). Enrich from CRM (segment, ARR, renewal) to size impact: "this hits $X ARR, concentrated in mid-market." +- **Breadth / reach (dedup by entity):** to count how many *distinct* users or accounts have a problem, dedup by identity so one loud customer across three channels counts as one voice, not three. This corrects Confidence (independent diversity) and Value (frequency / reach). + +Identity resolution therefore feeds both severity and accurate breadth, and prevents the classic trap of mistaking one vocal account for a widespread problem. + +## CDP shortcut + +If a CDP (Segment, RudderStack) or a reverse-ETL identity graph is connected, use its resolved identity instead of re-deriving, it already stitches IDs. Detect this in `discover-sources`. + +## Data handling + +Identity resolution works *because* it uses real identifiers: a shared email or email domain is what stitches the same person or account across Zendesk, the CRM, and analytics into one entity (and what lets you enrich that entity with CRM segment and ARR). So keep those identifiers readable in `identities.md` and in the evidence tags. Hashing or pseudonymizing them would break the cross-system match and the holistic picture it produces, which is the whole point of this step. + +`.product-eval/` lives in the user's own repository, so retention, redaction, and what to commit versus `.gitignore` are the user's data-governance call, not something this plugin imposes. The one default worth keeping: do not send raw customer identifiers into the external web-triangulation step, since that would hand them to third-party search. Surface where identifiers are stored so the user can apply their own policy. + +## Output + +`.product-eval//identities.md` mapping each resolved person/account to its source records with match method + confidence. Evidence items carry `person_id` and `account_id` so synthesis can collapse (for severity) or dedup (for breadth) correctly. diff --git a/product-eval/skills/discover-sources/references/source-catalog.md b/product-eval/skills/discover-sources/references/source-catalog.md new file mode 100644 index 0000000000000000000000000000000000000000..f7ac4cfa406301d945f36fa14b5cd79fd6bfbedd --- /dev/null +++ b/product-eval/skills/discover-sources/references/source-catalog.md @@ -0,0 +1,52 @@ +# Source Catalog, the universe of product-data sources + +The catalog of where product-relevant evidence can come from. `discover-sources` walks this list to ask about, detect, and map a user's sources. Each row maps a category to how it is ingested, the evidence role it plays, the maximum evidence strength it can reach (per the evidence-quality rubric, 1-5), and the metric-tree branch it illuminates. + +| Category | Example sources | Ingest via | Evidence role | Max strength | Lights up | +|---|---|---|---|---|---| +| Product / behavioral analytics | Amplitude, Pendo, Mixpanel, PostHog, GA4; raw in Snowflake/BigQuery/Databricks | MCP / CSV | first-party quantitative | 5 | activation, engagement, retention | +| Reliability / logs / errors | Datadog, Sentry, New Relic, Grafana, CloudWatch | MCP / CSV | first-party quantitative | 5 | failure modes, performance | +| Support / tickets | Zendesk, Intercom, Freshdesk, Help Scout | MCP / CSV | support-ticket cluster | 4 | activation & retention friction | +| Voice of customer / calls | Fireflies, Gong, Chorus; churn & NPS surveys | MCP / CSV | transcript / survey | 4-5 | unprompted pain, churn reasons | +| CRM / sales | Salesforce (PFRs, lost-deal reasons), HubSpot, Pipedrive | MCP / CSV | sales_call / feature_request | 3-4 | conversion, expansion | +| Feedback / idea management | Productboard, Canny, Aha!, UserVoice | MCP / CSV | feature_request | 3 | demand intent | +| Reviews / app stores | G2, Capterra, TrustRadius, App Store, Play Store | Web / CSV | third-party qualitative | 2-4 | comparative gaps | +| Community | Reddit, Hacker News, Discord, Slack communities | Web | third-party qualitative | 2-4 | emerging pain, workarounds | +| Issue trackers / dev signal | GitHub issues, GitLab issues, public roadmaps | MCP / Web | bug report / feature_request | 3-4 | bugs, integration & developer friction | +| Changelogs / release notes | competitor + own /changelog, /releases, GitHub releases, RSS | Web / MCP | freshness validation (not pain evidence) |, | retire stale gap evidence | +| Market / SEO / demand | Semrush, Ahrefs, SimilarWeb, Google Trends | MCP / Web | market_research | 2-3 | category demand, competitor gaps | +| Backlog / delivery | Linear, Jira, Asana, ClickUp, Monday, GitHub PRs | MCP | context (not evidence) |, | what is already planned | +| Knowledge / docs | Notion, Confluence, Guru, Google Drive, Box | MCP / CSV | prior art / decisions | varies | past research, decisions | +| Manual | CSV / XLSX exports, pasted notes, interview transcripts | Upload | depends on content | by content | universal fallback | + +## Two cross-cutting on-ramps + +- **CSV / file upload is universal.** Any source that can export becomes gradable evidence even with zero connectors, a Zendesk export, a Salesforce report, survey results, a spreadsheet of feature requests. Assign strength from the declared source type and content, not the delivery method. +- **The web is the always-available corroboration layer.** Even a fully instrumented PM gains from triangulation: take an internal cluster (e.g. a recurring ticket theme) and check whether the same pain appears externally (G2 dislikes, Reddit, competitor reviews). A match raises confidence and may signal an industry-wide opportunity; no match suggests the issue is specific to this product or segment. Either way it adds source diversity, which the sufficiency gate rewards. + +## How sources set the confidence ceiling + +- Only third-party / indirect sources present → confidence ceiling is **Moderate**. Decisions will tend to land at *Run a research sprint* or *Do not commit yet*. +- At least one strong first-party quantitative source (analytics or logs) **plus** corroboration from a second source type → ceiling can reach **Decide now**. +- Single-source coverage, however strong, cannot pass the sufficiency gate (which requires more than one source type). Treat single-source as a gap to close. + +## Linking the same user across sources (identity resolution) + +Many sources can be joined on a shared key so the same person or account is recognized across tools, e.g. someone who opened an Intercom chat, filed a Zendesk ticket, and exists in the CRM. `discover-sources` records the **join keys** each source exposes; `gather-evidence` uses them to stitch identities. Full method in `identity-resolution.md`. Keys by source: + +| Source | Typical join key | Grain | +|---|---|---| +| Intercom, Zendesk, Help Scout | email, company | person + account | +| Salesforce, HubSpot | email, account (+ ARR, segment, renewal) | person + account | +| Amplitude, PostHog | user_id / external_id (email if identified) | person (often pseudonymous) | +| Google Analytics (GA4) | client_id (pseudonymous) | rarely joinable to a person | +| GitHub | username (email often private) | best-effort person | +| CDP (Segment, RudderStack) | pre-resolved identity graph | person + account | + +Email is the strongest cross-tool key; email domain enables account-level grouping (exclude consumer domains). GA4 and pseudonymous analytics usually cannot be tied to a named person unless a `user_id` is set, treat them as account or aggregate signal. + +## Mapping notes + +- Tag each source with the persona(s) it speaks for and the funnel stage(s) it covers, so gaps (a missing persona, an uncovered stage) are visible. +- A backlog tool (Linear, Jira) is *context*, not evidence, it tells you what is planned, not whether it is worth doing. +- Prefer recent sources; in fast-moving categories, evidence older than ~6-12 months should temper confidence. diff --git a/product-eval/skills/drift-check/SKILL.md b/product-eval/skills/drift-check/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..30016a82261f2b298157c7d48e7723b9bd7b2d4f --- /dev/null +++ b/product-eval/skills/drift-check/SKILL.md @@ -0,0 +1,34 @@ +--- +name: drift-check +description: "Compare an investigation's CURRENT state against an EARLIER SNAPSHOT of itself and report what moved between those two points in time: problem-statement drift, evidence added or retired, Value/Confidence score movement, theme or narrative reframing, and whether confidence is genuinely accumulating or just churning. Use when the user says \"what changed since last month\", \"has this drifted\", \"compare this to the prior version\", \"is the confidence trajectory improving\", or wants to audit a scope before re-deciding. Reads scoped snapshots and git history and runs the cross-stage consistency checks. Do NOT use for finding patterns or trends WITHIN the current evidence set, that is synthesize; nor for reviewing a product-metrics dashboard, that is a metrics review. This is specifically a then-vs-now diff of the investigation. No daemon; runs on demand or on a schedule." +--- + +# Drift Check + +Compare the current state of an investigation scope against a prior snapshot and report what moved. There is no running process: this reads the versioned files (`.product-eval//snapshots/` and git history) on demand. + +## Workflow + +### 1. Pick scope and baseline +Choose the investigation scope and the baseline to compare against, a prior snapshot in `.product-eval//snapshots/`, or a git ref. + +### 2. Diff the artifacts +- **Problem statement**: has it drifted from the evidence that supports it? +- **Evidence**: items added or removed; total weight change; anything newly retired as stale (e.g. by a changelog check). +- **Themes**: new, faded, or merged. +- **Scores**: Value and Confidence movement, with drivers. +- **Brief / narrative**: has the framing been reworded or reframed? + +### 3. Run consistency checks +Apply the cross-stage consistency checks from `../pressure-test/references/critic-checklist.md`: problem-statement drift, persona drift, vanity-metric creep, scope vs evidence proportionality, and circular reversals in the decisions log. + +### 4. Report the confidence trajectory +The key signal: is evidence accumulating (Confidence rising as real data arrives) or are you spinning (churn without new evidence)? Say which. + +## Output + +A short "what moved since " summary: scores up/down with drivers, evidence added/retired, problem or narrative drift flags, consistency violations, and the confidence trajectory. Append it to the decisions log. End with `Next move:` and recommend re-running readiness, refreshing evidence, or updating the brief/build brief if the decision changed. + +## Scheduled use + +Can run as a weekly scheduled task, "what moved this week across my investigations", as a digest, not a service. Offer to set one up when the user tracks an investigation over time. diff --git a/product-eval/skills/gather-evidence/SKILL.md b/product-eval/skills/gather-evidence/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..cc708427e599eabb468db942f31f036d5dd52e31 --- /dev/null +++ b/product-eval/skills/gather-evidence/SKILL.md @@ -0,0 +1,56 @@ +--- +name: gather-evidence +description: Pull product evidence from connected sources, CSV uploads, and the web; then normalize, identity-resolve, strength-rate, and weight it into scoped evidence items. Use when the user says "gather evidence", "pull the tickets/reviews/analytics", "find signal for this problem", "collect data on X", or as part of Rank opportunities / Decide on one bet. Writes evidence items and a resolved identity map that synthesize and decision-readiness consume. +--- + +# Gather Evidence + +Retrieve product-relevant data for a problem or area and turn it into normalized, identity-resolved, strength-rated, weighted evidence items. This is the Problem Scout, retrieval and grading, not theming (that is `synthesize`). + +## When to run + +After source setup. Read `.product-eval//sources.md` for what's available, the join keys per source, and the confidence ceiling. If it is missing, run source discovery for this scope first. + +## Workflow + +### 1. Set retrieval hypotheses + +Don't dump everything. Decide where to look: walk the metric tree (acquisition→advocacy), the user journey, the personas, and competitive gaps, and form "where would pain show up?" hypotheses. Retrieve against those. + +### 2. Retrieve from each available source + +**Connectors vary: probe, don't assume.** Before bulk-pulling, make one cheap call to learn a source's actual shape (list its operations; pull one recent item; inspect the fields), then build the real query around what you see. If a connected source won't return data, climb the **adaptive fetch ladder** in `references/fetch-strategies.md`, relax the query, try an alternate operation, re-read the tool schema and re-parameterize, or fall back to a broad pull + local filter, diagnosing each failure (auth / empty / schema / permission / rate-limit) and retrying at most ~3 times per failure class before handing back to the user. Record the working access pattern so later pulls are one step. + +- **Connectors**: query each source's search/list tools with targeted filters (time window, status, tags, keywords from the hypotheses): support tickets, CRM PFRs and lost-deal reasons, GitHub issues, analytics funnels, error groups. Paginate or sample large sources, but capture totals even when sampling. +- **CSV / uploads**: parse exports directly. +- **Web**: search reviews, community, and competitor sources for external signal. + +**Stopping rule:** sample rather than exhaust: pull a representative recent set per source (default ~30-50 items, or until new items stop surfacing new themes, theme saturation), but always capture totals so volume/frequency signals survive. **When a fetch is blocked or a source is unavailable:** do not work around it, note the gap explicitly, treat that source as missing (which lowers reachable Confidence), and ask the user to paste or upload the data (CSV / export / text). Never substitute an alternative fetch method for a blocked one. + +### 3. Normalize into evidence items + +For each item record: claim (state the pain, not the solution), `source_type`, `persona`, `funnel_stage`, `impact_type`, `timestamp`, `raw_metric`, and `raw_link`. Follow the claim-extraction rules in `references/signal-analysis.md`, and write each item to the evidence-card schema in the data contract (`DATA-CONTRACT.md`, plugin root). + +### 4. Resolve identities + +Apply the identity ladder in `../discover-sources/references/identity-resolution.md`, using the join keys recorded in `.product-eval//sources.md`: stitch the same person/account across sources, write `.product-eval//identities.md` (method + confidence per merge), and tag each evidence item with `person_id` / `account_id`. Dedup so one user across channels is one voice, not several. Keep identifiers readable so the same entity stitches across systems into a holistic picture; data handling (retention, redaction, `.gitignore`) is the user's own governance call, per that reference. + +### 5. Rate strength and compute weight + +Rate each item 1-5 (`references/signal-analysis.md` plus the evidence-quality rubric) and compute its weight (base-by-strength × recency). These feed the sufficiency gate and Confidence downstream. + +### 6. Filter signal from noise + +Apply the signal hierarchy: keep tier-1 (migration stories, workarounds, specific complaints) over vague mentions; exclude competitor marketing, paid/incentivized reviews, bots, and stale items. When signals are thin, widen the search (proxies, adjacent areas, competitor gaps) and mark items as emerging/lower-confidence. + +### 7. Validate freshness against changelogs + +Before finalizing, freshness-check the competitive and gap evidence. Fetch the relevant changelogs / release notes, competitor `/changelog`, `/releases`, "What's New", GitHub releases, release-notes RSS, and the company's own changelog. For each piece of evidence, compare its date against shipped changes touching the same capability: if a release post-dates the evidence and addresses it, mark the evidence **stale / superseded**, decay its weight, and raise a contradiction flag for `synthesize`. Treat a changelog as a factual freshness check (not pain evidence, and not the same as competitor marketing), and remember a shipped feature isn't necessarily a good one, so a hit downgrades a table-stakes gap rather than auto-killing a differentiation play. + +### 8. Write and hand off + +Write `.product-eval//evidence/*` and `.product-eval//identities.md`. Summarize what was found (counts by source, distinct accounts after dedup, notable clusters, anything retired as stale) and hand the evidence to the ranking or decision flow for problem-framed synthesis. + +## Output + +A short summary, items found, distinct users/accounts after dedup, source mix, anything striking, plus the written evidence. Keep internal paths out of chat unless asked. Note honestly that it rates evidence as described, not ground truth. End with `Next move:` and route toward problem-framed synthesis/ranking for broad decisions, or readiness for one named bet. diff --git a/product-eval/skills/gather-evidence/references/fetch-strategies.md b/product-eval/skills/gather-evidence/references/fetch-strategies.md new file mode 100644 index 0000000000000000000000000000000000000000..4abed8cfde2f082374ca47b2c1dadb235723a556 --- /dev/null +++ b/product-eval/skills/gather-evidence/references/fetch-strategies.md @@ -0,0 +1,33 @@ +# Fetching evidence from a source, adaptive ladder + +Connectors vary across users, different MCP wrappers, configs, custom fields, permissions, and data volumes. Never assume a source's shape: **probe it, then climb a ladder of strategies, diagnosing each failure.** The goal, if a source *can* yield data, find the way; if it can't, say so precisely. + +## 1. Probe first (one cheap call) +Before bulk-pulling, learn the source's actual shape: list the connector's available operations, pull one recent item, and inspect the returned fields. Build the real query around what you observe, MCP wrappers often rename parameters and reshape output relative to the underlying API. + +## 2. The fetch ladder (try in order; stop when useful data returns) +1. **Targeted query**: the search/list operation with filters from your hypotheses (time window, status, tags, keywords). +2. **Relax the query**: if empty: widen the window, drop the narrowest filter, try synonyms or alternate field names. +3. **Alternate operation**: most tools expose several (e.g. Zendesk: search vs list-tickets vs views vs incremental export). Try another. +4. **Re-read the schema, re-parameterize**: inspect the tool's parameter schema; the wrapper may use different names (`q` vs `query`, `created_after` vs `start_time`). Retry with corrected params. +5. **Broad pull + local filter**: fetch a larger recent set and filter in memory when server-side filters misbehave. +6. **Hand back to the user**: CSV export / paste, reconnect, or grant scope (see the failure table). + +## 3. Diagnose the failure (don't retry blindly) +| Symptom | Likely cause | Next move | +|---|---|---| +| Auth / credential error | not authenticated, token expired | prompt reconnect, retry once | +| Empty result | query too narrow, wrong field, or genuinely no data | relax (step 2) → alternate op (step 3) | +| Schema / validation error | wrapper renamed params | re-read schema, re-parameterize (step 4) | +| Permission / 403 | restricted scope | narrow scope; ask the user to grant access | +| Rate limit / timeout | too much at once | back off, paginate, sample | +| Operation not found | connector lacks that op | alternate op or CSV fallback | + +## 4. Bounded loop +At most ~3 attempts per source per failure class. Stop when useful data returns, or when the blocker needs the user (auth / permission / missing data). Never loop indefinitely, report what you tried and what's needed (same discipline as `investigate`: no endless retrying). + +## 5. Remember the working recipe +Once a source returns data, record the working access pattern (tool · operation · params) in `.product-eval//sources.md`, so later pulls, and other skills, reuse it in one step instead of re-climbing the ladder. Treat it like a per-source note that fires next time you touch that source. + +## Honest degradation +If the ladder is exhausted, mark the source **blocked/empty** in the source map (which lowers the confidence ceiling) and tell the user exactly what failed and the one action that would unblock it (reconnect, grant scope, or export a CSV). A source you can't read is a known gap, not a silent zero. diff --git a/product-eval/skills/gather-evidence/references/signal-analysis.md b/product-eval/skills/gather-evidence/references/signal-analysis.md new file mode 100644 index 0000000000000000000000000000000000000000..cd8a983bfe1be907a6c82e5f8ce55519976f7cca --- /dev/null +++ b/product-eval/skills/gather-evidence/references/signal-analysis.md @@ -0,0 +1,49 @@ +# Signal Analysis Reference + +How to separate signal from noise when gathering evidence, and what to extract. + +## Signal hierarchy + +### Tier 1, direct pain (highest reliability) +- **Migration stories** ("we switched from X to Y because..."), strongest; a costly action taken (revealed preference). Extract the trigger, not just the destination. +- **Workaround descriptions** ("I built a spreadsheet because the product can't..."), the workaround reveals the problem's shape. Extract what it does. +- **Detailed complaints with specifics** ("took 3 hours to configure permissions, still got it wrong"), specificity indicates real experience. Extract task, context, failure mode. + +### Tier 2, indirect pain (moderate) +- **Feature requests**: extract the implied unmet need, not the requested feature. +- **Comparative dissatisfaction** ("competitor does this better"), extract the missing capability. +- **Support-seeking on things that should be obvious**: a discoverability problem. + +### Tier 3, weak (corroboration only) +- **Vague complaints** ("this is frustrating"), only to increase count on an already-identified theme. +- **Competitor praise without product criticism**: context only. +- **Analyst/influencer opinions**: flag to investigate, never use as primary evidence. + +### NOT signals (never use as evidence) +Competitor marketing (landing pages, announcements); paid/sponsored reviews; signals older than ~2 years in fast-moving categories; bot/spam content. + +## Changelogs as a freshness layer (not pain evidence) + +Competitor marketing is never a pain signal, but a competitor's (or your own) **changelog / release notes** is a factual record of what shipped, and it's the right tool for checking whether evidence is still current. Use it to **validate freshness**, not as evidence of pain: if a complaint that "Competitor X lacks Y" predates X shipping Y, retire that gap; and check your own changelog so you don't "discover" a problem you already fixed. A shipped feature isn't necessarily a good one, so a changelog hit downgrades a table-stakes gap rather than killing a quality-differentiation play. + +## Source evaluation (quick guide) + +- **Reddit**: weigh upvotes, comment count and quality; 100+ upvotes with substantive pain = strong; watch sample bias. +- **G2 / Capterra**: the "what do you dislike?" section is the best signal; 5+ reviews naming the same dislike = strong; watch incentivized reviews. +- **App stores**: 1-3 star reviews with specifics; skews to mobile UX and reliability. +- **Hacker News**: "Ask HN: alternatives to X" and Show HN feedback; developer-skewed. +- **GitHub issues**: detailed bug reports with repros and 👍 reactions; recurring/duplicated issues = strong; distinguish bugs from feature requests. +- **Community (Discord/Slack)**: recurring threads on the same issue; watch power-user bias. + +## Claim extraction rules + +State the pain, not the solution; quantify when possible (flag when missing); preserve the persona; don't editorialize; flag temporal relevance (6+ months old in a fast market tempers confidence). + +## When signals are thin + +If research yields fewer than ~5 qualifying problems: +1. Lower the threshold, include 1-2 strong tier-1 signals (migration story, detailed workaround), marked lower confidence. +2. Search adjacent areas, onboarding, integrations, pricing/packaging, performance, migration/switching. +3. Check competitor gaps, problems rivals solve well that this product doesn't (people may have already churned). +4. Explain what was searched and found, and suggest next steps. +5. Include sub-threshold signals as "emerging". diff --git a/product-eval/skills/pressure-test/SKILL.md b/product-eval/skills/pressure-test/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..78e446350ad32615295cb1a2180b82c718ed1a6a --- /dev/null +++ b/product-eval/skills/pressure-test/SKILL.md @@ -0,0 +1,19 @@ +--- +name: pressure-test +description: "Use when the user wants an existing plan attacked rather than built, they have a roadmap, plan, PRD, bet, or strategy and want you to find what's wrong with it before they commit. Triggers on any adversarial-critique intent: tear it apart, find the holes, poke holes, play devil's advocate, stress-test, pressure-test, challenge the assumptions, what are we missing / not seeing, where will this break, why might this fail, talk me out of this, red-team it. The defining signal is skeptical scrutiny of a decision the user is about to make. Surfaces thin evidence, hidden or unexamined assumptions, wrong scope, and drift, then returns blocking issues, suggestions, strengths, and a ready/needs-fixes/not-ready verdict. Do NOT use for generating new ideas, brainstorming directions, writing or updating a plan/PRD/roadmap, or reviewing metrics and status updates, this only critiques something that already exists." +--- + +# Pressure Test + +Adversarial review of an **existing** plan, roadmap, PRD, bet, or strategy, find what's wrong before the user commits. This is `critique` pointed at a whole artifact: it runs the same independent adversarial engine, so there is one critic, not two. + +## Workflow + +1. Take in the artifact and its supporting evidence/scores from `.product-eval//`. +2. Run **`critique`** on it: the multi-lens panel (skeptical customer, competitor, CFO, engineer, contrarian, security) plus the `critic-checklist.md` checks (evidence→solution, solution→ship, cross-stage consistency) and a **scope-mode** recommendation (Expand / Hold / Reduce). Run it independently (a fresh-context subagent; a different model if available) so it does not rubber-stamp. +3. Hunt counter-evidence and force hidden assumptions into the open; check that metrics are not vanity and that success criteria are falsifiable. +4. Terminate each point as resolved / open (→ a gap routed to `gather-evidence`) / contested (→ logged dissent), never argue to consensus. + +## Output + +Blocking issues (with fixes), suggestions, strengths, a scope-mode call, and an overall **ready / needs fixes / not ready** verdict. Append the findings to `.product-eval//decisions-log.md`. Lead with the verdict and the blocking issues. End with `Next move:` and recommend fixing the blockers, writing the decision memo, making the build brief, or using critique if the artifact is only a finding/problem statement rather than a full plan. diff --git a/product-eval/skills/pressure-test/references/critic-checklist.md b/product-eval/skills/pressure-test/references/critic-checklist.md new file mode 100644 index 0000000000000000000000000000000000000000..86fa3def126e348e31d684c9bd19fdce4a44723a --- /dev/null +++ b/product-eval/skills/pressure-test/references/critic-checklist.md @@ -0,0 +1,45 @@ +# Critic Checklist Reference + +What to check when pressure-testing. Every flag must be actionable. Distinguish blocking issues from suggestions, and acknowledge strengths. + +## Evidence → solution (is the problem ready to solution?) + +**Blocking:** total evidence weight below threshold (strength-weighted and deduplicated, not a raw count); no evidence rated strength 3+; problem statement missing or vague; all evidence from a single source type. + +**Suggestions:** no first-party data; no quantitative data; no counter-evidence considered; evidence older than 6 months without recent validation; Confidence below 40; persona gaps in evidence coverage; missing funnel context. + +## Solution → ship (is the solution ready to build?) + +**Blocking:** a Must-have requirement has no linked evidence; no acceptance criteria on Must-have requirements; no success metrics defined; the solution doesn't address the top evidence theme; goals are not measurable (no baseline or target). + +**Suggestions:** requirements linked to only one evidence item (weak grounding); no non-goals defined (scope-creep risk); no risks identified; scope too big or too small for an MVP; metrics without an instrumentation plan; dependencies not identified; solution treats symptoms not root cause; decisions log shows requirements removed and re-added (indecision signal). + +## Doc / pre-export (is the document sound?) + +**Blocking:** the document omits the problem statement; the evidence summary doesn't match the actual top evidence; requirements in the doc don't match the agreed solution; required sections missing. + +**Suggestions:** document too long (over-detailed) or too thin; no edge-cases section; tasks without acceptance criteria; rollout plan missing or generic; open-questions section suspiciously empty. + +## Cross-stage consistency (always check, this is how drift shows up) + +- Problem statement has not drifted from the evidence. +- Persona in the solution matches the persona in the evidence. +- Metrics relate to the actual problem (not vanity metrics). +- Scope is proportional to evidence strength and Value. +- Decisions log is consistent (no circular reversals). +- The narrative/framing has not quietly changed from a prior version without justification. + +## Output shape + +Return four things: + +- **blocking_issues**: with specific fix instructions +- **suggestions**: with what to do +- **strengths**: what's working +- **overall_readiness**: ready / needs fixes / not ready + +## Scope modes (recommend exactly one) + +- **Expand**: the real opportunity is bigger than proposed; name the missing capability. +- **Hold**: scope is right; defend why. +- **Reduce**: scope is too big or unproven; identify the narrowest valuable wedge to ship and learn from first. diff --git a/product-eval/skills/prioritize-backlog/SKILL.md b/product-eval/skills/prioritize-backlog/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..354665775468bc08e0bdc508ae113dff903d05e3 --- /dev/null +++ b/product-eval/skills/prioritize-backlog/SKILL.md @@ -0,0 +1,47 @@ +--- +name: prioritize-backlog +description: Rank opportunities or backlog items by evidence-backed Value and Confidence bands, storing exact 0-100 scores for audit. Use when the user says "prioritize my backlog", "rank these", "what should we build next", "score these opportunities", or hands over a list of problems/features to sequence. This is the Rank opportunities outcome, distinct from updating a roadmap's Now/Next/Later. Falls back to proxies when data is thin and flags lower confidence. +--- + +# Prioritize Backlog + +Score and rank a set of problems or opportunities so the user knows what to build next and why. Produce a defensible ranking with explicit reasoning, not a gut order. In chat, show bands and recommendations first; store exact numbers in scope files. + +## Workflow + +### 1. Gather the items and their context + +Take the list to rank (from the user, from problem-framed themes, or from `.product-eval//`). Read `.product-eval//context.md` for the north-star metric and personas, and `.product-eval//sources.md` for the confidence ceiling. If business context is missing, ask only the few questions needed to score (north star, primary personas) and proceed. + +**Preflight (data contract).** When ranking themes already written to `.product-eval//`, validate `themes.md` against the conformance preflight in `DATA-CONTRACT.md` (Validation) first (members resolve to real evidence, `total_weight` matches the members, `confidence_band` is consistent with `confidence`). On a failure, stop and report the file, field, and violation rather than scoring malformed input. + +### 2. Score Value per item + +Using `references/scoring-framework.md`, score four dimensions (each 0-25, summed). Store the exact 0-100 `value` and `value_band`; show the band in chat. + +- **Persona weight**: buyer vs admin vs end-user vs edge case. +- **Funnel location**: retention/activation rank highest; advocacy lowest. +- **Frequency**: how often the affected users hit this. +- **Competitive pressure**: from "customers leaving" down to "no pressure". + +When first-party data is missing, use the proxies in the reference (complaints as a drop-off proxy, sales objections as a conversion proxy, competitor investment as a gap proxy) and mark those scores as lower confidence. + +### 3. Score Confidence per item, report as a band + +Confidence reflects how well-supported the item is by evidence: count of independent evidence items, diversity of source types, their strength (1-5), recency, and how much is first-party. Store the exact 0-100 `confidence` for audit and calibration, but present `confidence_band` in chat (Low / Moderate / High). Respect the ceiling from `.product-eval//sources.md`. Signal strength is an input to Confidence, not a separate verdict. + +### 4. Apply the recommendation matrix + +Map each item's (Value, Confidence) to a recommendation using the matrix in the reference: *Decide now / Run a research sprint / Do not commit yet / Deprioritize.* High value + low confidence means "research more", not "drop", keep these visible. + +### 5. Rank and present + +Order by recommendation tier first, then Value band within tier. Present a scannable ranked table: item, Value band, Confidence band, recommendation, one-line rationale. Call out the few items worth acting on now and the few to deprioritize. Put exact point scores only in an audit note or the scope file. + +### 6. Record and offer next step + +Write `.product-eval//scores.md` with exact scores, bands, and reasoning, and log the ranking in `.product-eval//decisions-log.md`, using the score and decisions-log schemas in the data contract (`DATA-CONTRACT.md`). Offer to decide on the top item or pressure-test the resulting shortlist. Honor user score overrides, but require a one-sentence reason and log it. + +## Output + +A ranked table with bands, recommendations, and a short "act on these / hold these" summary. Keep exact math and internal paths out of the chat unless asked. End with `Next move:` and recommend deciding on the top item, pressure-testing the shortlist, rendering a scorecard, or writing the report the user asked for. diff --git a/product-eval/skills/prioritize-backlog/references/scoring-framework.md b/product-eval/skills/prioritize-backlog/references/scoring-framework.md new file mode 100644 index 0000000000000000000000000000000000000000..238e04dd658e28e4c27233d2939a5280474472ad --- /dev/null +++ b/product-eval/skills/prioritize-backlog/references/scoring-framework.md @@ -0,0 +1,97 @@ +# Scoring Framework Reference + +How Value and Confidence are calculated for problems, and how they map to a recommendation. + +## Two scores, two questions + +- **Value (0-100):** "If this problem is real, how big is it?", about potential impact, not certainty. +- **Confidence (0-100):** "How sure are we that this problem is real and worth acting on?", about evidence quality and coverage. + +These are deliberately separate. Store exact numbers in `.product-eval//scores.md` for audit and calibration, but lead user-facing output with bands and recommendations. High value + low confidence = research more. Low value + high confidence = real but not worth building for. + +## Value inputs (each 0-25, summed to 100) + +**Persona weight (0-25):** Buyer = 25, Admin activation = 20, End-user core = 15, End-user secondary = 10, Edge case = 5. + +**Funnel location (0-25):** Retention = 25, Activation = 22, Conversion = 18, Expansion = 15, Engagement = 10, Advocacy = 5. + +**Frequency (0-25):** Every user every time = 25, Most users at key moments = 20, Significant segment regularly = 15, Some users sometimes = 10, Rare = 5. + +**Competitive pressure (0-25):** Customers leaving over it = 25, Competitors solved it = 20, Competitors building it = 15, Expectation forming = 10, No pressure = 5. + +## Confidence (0-100) + +Confidence is **computed**, not guessed, so it is reproducible. It builds on total evidence weight (W, deduplicated), source-type diversity, and unresolved contradictions. Recency and first-party-ness are already inside W via the weight rubric, do not double-count them. + +``` +base = 100 × (1 − e^(−W / 14)) # saturating; W = total evidence weight (deduped) +diversity = 0.7 if 1 source type, 0.9 if 2, 1.0 if 3+ +penalty = min(0.3, 0.1 × number_of_unresolved_material_contradictions) +Confidence = clamp( base × diversity × (1 − penalty), 0, 100 ) +``` + +Reference points for `base` (no contradictions): W=8 → 44; W=12 → 58; W=19 → 74; W=26 → 84; W=40 → 94. Interpolate if you can't evaluate the exponential exactly. + +Always show the computation, e.g. *"Confidence 67 = base 74 (W=19) × 0.9 (2 source types) × 1.0 (no contradictions)."* A single-source case (diversity 0.7) is capped well below a multi-source one at the same weight, by design, so the gate rewards diversity, not just volume. + +The constant 14, the diversity steps, and the penalty are **shipped defaults**, universal across installs. A team can override them for their product via a local `.product-eval/calibration.yml` profile (written by `calibrate`): read the defaults, then apply that profile if present. The defaults themselves change only between plugin versions, never edit them per-install. + +## Report bands, not points + +The computed number places a band, report the band, not false precision. Map Value and Confidence to labels: + +- **Low** (< 40) +- **Moderate** (40-69) +- **High** (≥ 70) + +Display e.g. "Value: High; Confidence: Moderate (~60-70)", not "Value: 72; Confidence: 67" in normal chat. The point estimate is computed for placement and calibration; the band is what you present and decide on, so a 2-point wobble never flips a verdict. + +## Bounded, logged adjustment + +The structured inputs can't see everything, a known-flaky dashboard, a biased sample, a contradiction that resolves on a careful read. After computing the base Confidence you MAY apply **one adjustment of up to ±10 points** for such context. It is not a free hand: + +- State the reason and record it in the decisions log (e.g. "−8: the 41% drop comes from a dashboard with a known double-counting bug"). +- Cap it at ±10; it may shift the band, which is the point. +- Show the math: "base 74 → 66 after −8 (flaky source)". + +This keeps judgment dynamic where it genuinely adds value, while the number stays reproducible and auditable everywhere else. + +Always explain the score. Example: "Confidence 72: 12 independent sources across 3 channel types (strong diversity), majority from the last 4 months (recent), but no first-party quantitative data (gap)." + +Signal strength (1-5) is an **input** to Confidence, assessed per evidence item and per data source. It does not by itself decide anything. + +## Score-to-recommendation matrix + +One vocabulary, shared with `decision-readiness`: + +| Value | Confidence | Recommendation | +|---|---|---| +| 40+ | High (70+) | **Decide now**: worth building and well-supported | +| 70+ | Moderate (40-69) | **Run a research sprint**: high value, needs more proof | +| 40-69 | Moderate (40-69) | **Run a research sprint**: promising; firm up both | +| 40+ | Low (<40) | **Do not commit yet**: too unproven to act on | +| below 40 | any | **Deprioritize**: prize too small to pursue | + +## Proxies when data is thin + +When first-party data is missing, score from proxies and mark the result lower confidence: + +- Most complaints → drop-off proxy +- Sales objections → conversion proxy +- Churn reasons → retention proxy +- Competitor investment areas → gap proxy + +## Ranking criteria (when scores tie or for a quick first pass) + +By weight: north-star proximity > persona importance > journey stage > competitive urgency > estimated breadth. + +## Overrides + +Users may override a score. When they do: require a one-sentence reason, log it in the decisions log, and respect it, but note any discrepancy with the computed score. + +## Anti-patterns + +- Don't generate items from thin air, trace each to business context or evidence. +- Don't list features as problems. +- Don't be exhaustive, a focused 5-8 ranked items beats 30. +- Don't assume the user's priorities, present the reasoning; they decide. diff --git a/product-eval/skills/scorecard/SKILL.md b/product-eval/skills/scorecard/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..7258b85f9ee529291f6613ae1a4cb3b05a7f94b7 --- /dev/null +++ b/product-eval/skills/scorecard/SKILL.md @@ -0,0 +1,32 @@ +--- +name: scorecard +description: "Render the results of a product-eval investigation as a self-contained interactive HTML page: ranked opportunities with their Value, Confidence, verdict, evidence weight, customer quotes, and open gaps, laid out as KPI cards, a Value by Confidence quadrant, and per-opportunity cards. Use when the user wants to visualize, render, share, or make a dashboard or scorecard from an existing product-eval scope, that is, the output of prioritize-backlog or decision-readiness, or a scope's scores.md, themes.md, and decisions-log.md. Triggers include 'decision dashboard', 'product dashboard', 'scorecard from the ranked opportunities', 'turn the prioritization into something visual to share'. Do NOT use for general metrics or KPI dashboards, north-star or quarterly metric trends, grading raw signal strength, checking connected data sources, or drafting briefs; this only renders an already-scored opportunity ranking, it does not gather data or compute scores." +--- + +# Scorecard + +Turn an investigation's scores and verdicts into a single self-contained, interactive HTML page a PM can open, sort, share, and act on. This is the **rich output**: it makes `prioritize-backlog` and `decision-readiness` results tangible instead of a wall of text. + +## When to run + +After `prioritize-backlog` (ideally with `decision-readiness` run on the top items). Reads `.product-eval//scores.md`, `.product-eval//themes.md`, and `.product-eval//decisions-log.md`. + +**Preflight (data contract).** Before rendering, run the conformance preflight in `DATA-CONTRACT.md` (Validation) on `scores.md`, `themes.md`, and `decisions-log.md`. If a check fails (a verdict outside the four-verdict vocabulary, a band that disagrees with its number, a `problem` with no matching theme), stop and report it rather than drawing a misleading dashboard. `examples/activation-q3/` is a conformant fixture and `examples/activation-q3/scorecard.html` is its rendered output. + +## What it produces + +One standalone `.product-eval//scorecard.html` (no server; only optional Chart.js from a CDN), containing: + +- **KPI cards**: total opportunities, counts by recommendation (Decide now / Run a research sprint / Do not commit yet / Deprioritize), and the confidence ceiling. +- **Value × Confidence quadrant**: each opportunity plotted; a vertical line at the Confidence gate (70) and a horizontal at Value 70, so the eye lands on the top-right "Decide now" quadrant. +- **Opportunity cards**: one per opportunity, with clearly-labeled bands (Value, Confidence), optional 0-100 audit bars, evidence weight, accounts affected, the recommendation, **verbatim customer quotes** (the qualitative voice), the **top complaint** and the **explicit ask**, and a **"why it ranks here"** line tying the score back to that customer pain, plus the open gaps. The quotes and the complaint/ask are what make the prioritization legible; lead with them, not the number. +- **A "how to read" glossary**: plain-language definitions of Value, Confidence, evidence weight, and accounts, and the recommendation tiers. +- **Honest footer**: the confidence ceiling and the single cheapest move to raise the top item (e.g. "add one independent source type"). + +## How to build + +Generate one HTML file with inline CSS + JS. Read the data from the scope files (or accept inline data when rendering an example). For each opportunity, pull 1-2 **verbatim** customer quotes plus its top complaint and explicit ask from the evidence, these carry the qualitative voice and make the ranking legible. Show clearly-labeled Value and Confidence bands first, with exact 0-100 bars only as audit detail, and include the glossary. Color-code recommendations consistently: Decide now = green, Run a research sprint = amber, Do not commit yet = grey, Deprioritize = red. If a bounded ±10 adjustment was applied, note it. Keep it faithful to the formula, never invent a number the evidence doesn't support. Write to `.product-eval//scorecard.html` and offer to open it. + +## Output + +The path to the HTML scorecard plus a one-line summary (e.g. "5 opportunities, 1 Decide now, 2 Run a research sprint, 1 Do not commit yet, 1 Deprioritize; ceiling: Decide-now reachable"). End with `Next move:` and recommend deciding on the top item, designing the research sprint for under-evidenced items, or writing the stakeholder report. diff --git a/product-eval/skills/start/SKILL.md b/product-eval/skills/start/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..dd7e1316d9386cf98557af12f41f47de3b25bb32 --- /dev/null +++ b/product-eval/skills/start/SKILL.md @@ -0,0 +1,53 @@ +--- +name: start +description: "Use as the front door when the user is getting oriented rather than requesting a finished deliverable. Trigger on bare openers ('start', 'where do I begin', 'help me get going', 'just installed this'), questions about what this plugin or tool does, requests to survey or kick off an investigation into a space or area, or any broad product goal stated without a single concrete action ('find our next bet', 'what should we work on'). The intent is 'point me in the right direction', not 'do this specific task'. Route them toward one of four outcomes: rank opportunities, decide on one bet, pressure-test a plan, or write an output. Do NOT use when the user names a concrete deliverable and the inputs for it: synthesize provided evidence, rank a given list, judge one named bet, pressure-test a pasted plan, or draft a specific brief, memo, or spec. When in doubt, if there is a clear single action to perform, stay out." +--- + +# Start + +Route the user into the right workflow by the **outcome** they want, set up an investigation scope, and keep internal skill choreography out of the chat unless the user asks. Never ask "are you a founder or a PM"; ask what product decision they are trying to make, what product/context it applies to, and what evidence they want decisions to rely on. + +## First-run setup gate + +Do **not** run straight into analysis on a first contact or a vague prompt. If there is no usable `.product-eval//context.md` and `.product-eval//sources.md`, or the user has not supplied enough context, pause and ask a compact setup question before ranking or deciding. + +Minimum context to proceed: + +- Product and target users/personas. +- Decision goal: rank opportunities, decide one bet, pressure-test a plan, or write a report/brief/spec/scorecard. +- Scope and time horizon, including what the named product area means. +- Available evidence/data sources: files, CSVs, transcripts, tickets, analytics, CRM, reviews, connectors, or "outside-in only". +- Success metric or business constraint that should shape Value. + +Ask for these in one short message, not a long form. If the user explicitly says "use outside-in evidence only" or "make a low-confidence first pass", proceed but state the confidence ceiling. If the user asks for a report, first ask who the audience is and what source material or scope it should draw from unless that is already clear. + +## Run to the outcome after setup + +When the setup context is available and the user states an outcome, "what should we build and write the brief", "is X worth it", "give me the scorecard", don't make them invoke each step. Run the chain in one pass as needed: source setup → evidence gathering → problem-framed synthesis → ranking/readiness → the requested output. Pause only for (a) the **decision-readiness gate** (the genuine Decide-now / Run-a-research-sprint / Do-not-commit-yet fork), (b) real **taste calls** the user must own, and (c) **blockers** (a source won't connect, evidence too thin → a research sprint design). Narrate briefly; surface decisions, not mechanics. The point is the user reaches the document, not that they orchestrate the skills. + +## Workflow + +### 1. Identify the outcome + +Infer from what the user said, or ask once (one question, not a form). The outcomes (see `references/jobs.md`): + +- **Rank opportunities**: turn an area, backlog, or evidence pile into a ranked problem shortlist. +- **Decide on one bet**: judge whether one idea is ready to commit, needs a research sprint, or should not proceed yet. +- **Pressure-test a plan**: find the weak spots in an existing roadmap, PRD, strategy, or bet. +- **Write the output**: produce a brief, decision memo, build brief, or scorecard. + +### 2. Select or create the investigation scope + +Each investigation gets its own namespace, `.product-eval//`, so a broad-space scan and a specific-product analysis don't bleed into each other. Ask for a short scope name only if it cannot be inferred, and create or reuse the directory. List existing scopes if the user might be resuming one. + +### 3. Run setup + +Set up the evidence context for this scope and capture missing business context before proceeding. State plainly what the tool can and cannot decide given the available data, including the honest confidence ceiling. + +### 4. Run the outcome flow + +Run the internal steps for the selected outcome (see `references/jobs.md`) and present the output in outcome language. End each step with `Next move:` and propose the single best action toward delivery, not a menu of skill names. + +## Output + +A one-line confirmation of the outcome and scope, a concise data/ceiling summary, and the first useful result or setup question. Always end with `Next move:`. Keep skill names out of the user-facing text unless asked; describe the action in user terms such as "upload the support CSV", "rank these opportunities", "run the readiness call", "pressure-test the plan", or "write the decision memo". diff --git a/product-eval/skills/start/references/jobs.md b/product-eval/skills/start/references/jobs.md new file mode 100644 index 0000000000000000000000000000000000000000..e683c43a0e8131b29017fde4f725727aac7ed6a1 --- /dev/null +++ b/product-eval/skills/start/references/jobs.md @@ -0,0 +1,52 @@ +# Outcome Modes + +Entry is by desired outcome, not persona or internal skill. Two axes configure a session: the **outcome** (sets breadth + output) and **data availability** (sets direction + confidence ceiling). "Founder" and "PM" are common contexts, not entry points. + +## The outcomes + +| Outcome | Trigger phrases | Breadth | Internal flow | First output | +|---|---|---|---|---| +| Rank opportunities | "what should we build next", "prioritize this backlog", "rank these opportunities" | broad or specific | gather evidence as needed, synthesize into problem-framed themes, score Value/Confidence, render ranking | ranked problem shortlist | +| Decide on one bet | "is this worth building", "do we have enough data for X", "can we commit" | single | frame the bet, gather targeted evidence if needed, run readiness, design the cheapest test if proof is missing | Decide now / Run a research sprint / Do not commit yet | +| Pressure-test a plan | "poke holes in this", "stress-test our roadmap", "red-team this plan" | existing artifact | pressure-test the artifact against evidence and assumptions | blocking issues + verdict | +| Write the output | "write the brief", "write the decision memo", "turn this into a spec", "make a scorecard" | decided or scored artifact | produce the requested decision artifact from the scope | brief, memo, build brief, or scorecard | + +Keep these labels user-facing. The internal flow can use specific skills, but the chat should normally say "ranking", "decision", "research sprint", "pressure-test", or "brief/spec" rather than naming every step. + +## Setup before outcomes + +On a new scope or a vague first prompt, setup comes before analysis. Ask for the product, target users, decision goal, scope/time horizon, available evidence sources, and the metric or business constraint that should shape Value. Do not rank "onboarding" or judge "SSO" until the product context and evidence plan are clear, unless the user explicitly asks for an outside-in first pass and accepts a lower confidence ceiling. + +If the user wants a report or brief, treat that as a Write outcome but still ask for audience, source material, and the decision the report should support when those are missing. + +## Investigation scopes + +- State lives at `.product-eval//`, one scope per distinct investigation. +- A broad-space exploration and a specific-product evaluation are **different scopes**; do not share evidence between them unless the user explicitly links them. +- Resuming: if a scope already exists, load its `context.md`, `sources.md`, and prior `decisions-log.md` rather than starting over. +- The same user can keep several scopes alive in parallel; `drift-check` tracks each independently over time. + +## Direction & ceiling (the data axis) + +- No first-party data → outside-in; confidence ceiling **Moderate**; lean on the evidence-generation loop (prescribe the cheapest test). +- First-party connectors present → inside-out; ceiling up to **Decide now**; lean on connector aggregation. +- Mixed is common (e.g., own analytics + competitor web signal). The ceiling follows the data, independent of the job. + +## Delivery nudges + +Every user-facing result should end with `Next move:` and one recommended action. Prefer delivery language over skill names: + +| Current result | Best next move | +|---|---| +| Setup is incomplete | Ask for the missing product context, evidence source, or report audience. | +| Source map exists | Pull or upload the evidence that can answer the decision. | +| Evidence is gathered | Turn it into problem-framed candidates for ranking or one-bet readiness. | +| Problem candidates are framed | Rank them, or run a readiness call on the leading bet. | +| Ranking is done | Decide on the top item, pressure-test the shortlist, or render a scorecard/report. | +| Decide now | Pressure-test the plan if commitment is high-stakes, then write the memo or build brief. | +| Run a research sprint | Design the cheapest test that would close the proof gap. | +| Do not commit yet | Reframe, gather a missing source, or deprioritize the bet. | +| Pressure-test is done | Fix blocking issues, then write the decision memo or build brief. | +| Critique is done | Revise the artifact, or pressure-test it if it is a plan/roadmap/PRD. | +| Brief/memo is done | Run an adversarial FAQ pass if needed, then create the build brief. | +| Build brief is done | Hand it to the implementation or design agent. | diff --git a/product-eval/skills/synthesize/SKILL.md b/product-eval/skills/synthesize/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..f302435fe33ffe03d8b8a0c7749365b606299fd8 --- /dev/null +++ b/product-eval/skills/synthesize/SKILL.md @@ -0,0 +1,42 @@ +--- +name: synthesize +description: "Use when turning gathered evidence (research, tickets, reviews, interview notes, analytics, or weighted evidence items in a folder) into a small set of problem-framed themes or clusters. Also use when someone has a vague observation, complaint, or signal (e.g. 'users keep dropping off in signup') and wants it shaped into a real, crisp problem statement at the right altitude. Triggers on: cluster or group evidence into problems, find the themes or recurring patterns, see what is trending over time, frame this up, is this the right problem, what should we actually build, or feeding candidates into prioritization. Reads scoped evidence, clusters by underlying problem (not topic), detects trends and corroboration, reasons about confidence, names gaps, and writes buildable problem statements that prioritize-backlog can score. Not for gathering or scoring evidence, only for making sense of evidence already collected, or framing a loose pain into a structured problem." +--- + +# Synthesize + +Turn a pile of evidence items into a small set of problem-framed themes, with trend and corroboration analysis, explained confidence, and specific gaps. This is the place where raw evidence becomes buildable problem candidates; do not make users wait for a separate "themes, then problems" step. + +## When to run + +After evidence gathering. Read `.product-eval//evidence/*` (already identity-deduped and weighted). + +**Preflight (data contract).** Before clustering, run the conformance preflight in `DATA-CONTRACT.md` (Validation) on `evidence/*`. If an item is malformed (missing `strength` or `weight`, an unknown `source_type`, a dangling `corroborated_by`), stop and report the exact file and field rather than theming around it. + +## Workflow + +### 1. Cluster into problem-themes + +Group evidence by *problem*, not topic: same pain + same persona + same context, ideally from 2+ source types. Method in `references/synthesis.md`: for modest corpora use LLM map-reduce (extract a normalized claim per item, cluster the claims, label each as a problem, not a solution); for large corpora pre-aggregate or embed-and-cluster, then label. Typical result is 2-5 problem candidates; more than 7 means splitting too finely; only 1 means evidence too narrow. + +Then **frame each at the right altitude**: the problem-statement formula *[persona] cannot [job] [context] because [root cause], resulting in [business consequence]*, splitting too-broad themes (by persona / journey step / severity / frequency) and lifting too-narrow ones to the underlying problem. In early exploration, return the best-current framing with gaps; for evaluate/validate work, run the **crispness grader** (`critique`) as a gate and rewrite any dimension scoring 0. See `references/problem-framing.md`. + +### 2. Detect trends and patterns + +Per theme: volume over time (WoW/MoM) and the signal-count bands (3-5 emerging, 6-12 established, 13+ well-known, 50+ fundamental or recent regression). A sudden spike usually signals a recent regression. Record cross-source corroboration, including internal↔external triangulation, and surface contradictions (e.g. analytics show 78% completion while a vocal cluster complains) rather than smoothing them. + +### 3. Reason about confidence + +For each theme, compute and explain confidence from total evidence weight, source diversity, recency, and contradictions, never just a number ("Confidence 72: 12 items across 3 source types, recent, but no first-party quantitative data"). + +### 4. Write gaps + +Frame specific gap questions, each with where to look and which source would close it. + +### 5. Write and hand off + +Write `.product-eval//themes.md`: per theme, name (problem-framed), member evidence IDs, affected personas, distinct accounts, average strength, total weight, trend, corroboration, contradictions, exact confidence plus confidence band, and gaps, following the theme schema in the data contract (`DATA-CONTRACT.md`). Hand the framed problem candidates directly to `prioritize-backlog` for scoring. + +## Output + +A scannable problem-candidate list (problem statement, trend, confidence band, top gap) plus the written themes. Lead with the strongest candidate. Do not make build recommendations here, that is the scoring step's job. End with `Next move:` and recommend ranking the candidates, or running readiness on the strongest single bet if the user is already trying to commit. diff --git a/product-eval/skills/synthesize/references/problem-framing.md b/product-eval/skills/synthesize/references/problem-framing.md new file mode 100644 index 0000000000000000000000000000000000000000..a72b58dc19c9d9242e52e89baacd5924db9f4018 --- /dev/null +++ b/product-eval/skills/synthesize/references/problem-framing.md @@ -0,0 +1,59 @@ +# Problem Framing Reference + +How to frame product problems at the right altitude: specific enough to investigate and build for, broad enough to justify investment. + +## The altitude test + +**Too broad (split/narrow):** "Our onboarding is bad" · "Users don't engage enough" · "We're losing to competitors" · "The product is slow." These are categories, not problems, split them by persona, journey step, severity, or frequency. + +**Too narrow (lift):** "The tooltip on step 3 is confusing" · "The CSV button is in the wrong place" · "Users want dark mode." These are bugs, UX tweaks, or feature requests, lift them to the underlying user problem before scoring. + +**Right altitude (accept):** +- "Teams with 5+ members cannot configure role-based permissions during onboarding without contacting support, leading to trial abandonment." +- "Managers cannot see cross-team project status without checking 3+ screens, so they build shadow tracking in spreadsheets." +- "Free-tier users who hit the usage limit have no visibility into what upgrading unlocks, so they churn instead of converting." + +Specific enough to build for, broad enough to justify investment, tied to a business outcome. + +## Problem-statement formula + +**[Persona] cannot [job/task] [context/constraint] because [root cause], resulting in [business consequence].** + +Every problem must contain: +1. WHO is affected (a specific persona, not "users") +2. WHAT they can't do or struggle with (the job/task) +3. WHEN/WHERE it happens (the triggering context) +4. WHY it happens (root cause if known; "unknown" is acceptable) +5. SO WHAT (the business impact: churn, lost revenue, support cost, competitive loss) + +For exploration, unknown root cause or consequence can move forward as an explicit gap. For evaluation or validation, the statement should be concrete enough that a PM knows what evidence would prove or disprove it. + +## Decomposing broad themes + +- **By persona:** admin setup vs end-user first experience vs buyer evaluation are different problems. +- **By journey step:** where exactly in the flow does friction occur? +- **By severity:** blocker vs annoyance, focus on blockers first. +- **By frequency:** every user, or a segment? Which segment matters most? + +Example, broad "onboarding is weak" decomposes into: +1. "Admins cannot complete SSO configuration without engineering help" (activation, admin, blocker, enterprise) +2. "New end-users don't discover core reporting in week one" (activation, end-user, missed value, all) +3. "Trial teams with 10+ invitees hit slow imports" (activation, admin, performance, growth) + +## Quality checklist + +- [ ] Persona specific (not "users" or "customers") +- [ ] Task concrete (not "have a better experience") +- [ ] Business consequence named, measured when evidence exists +- [ ] Addressable by a team in roughly 2-6 weeks +- [ ] Distinct from the other problems on the list +- [ ] A PM would immediately know what to investigate + +Use the checklist as a progress tool. In exploration, return "best current framing + gaps"; in commit-stage decisions, tighten or rewrite before scoring readiness. + +## Common mistakes + +- **Disguised solutions:** "Need Slack integration" → "Users lose context on updates because notifications only exist in-product." +- **Symptom vs root cause:** "Too many support tickets" → "Users cannot resolve config errors because error messages don't explain what went wrong." +- **Bundled problems:** "Enterprise struggles" → split into specific pains per point. +- **No consequence:** "Settings page is cluttered" → "Admins take 4x longer to find notification preferences, causing misconfigured alerts." diff --git a/product-eval/skills/synthesize/references/synthesis.md b/product-eval/skills/synthesize/references/synthesis.md new file mode 100644 index 0000000000000000000000000000000000000000..6f980567382f4d4dd7e30a7851ecbd9f6b9a2865 --- /dev/null +++ b/product-eval/skills/synthesize/references/synthesis.md @@ -0,0 +1,42 @@ +# Synthesis Reference + +How to turn evidence items into problem-framed themes, trends, confidence, and gaps. + +## Theme identification + +A theme is a recurring pattern across 2+ evidence items. Themes must already be usable as problem candidates: +- Named as **problems**, not solutions ("auth breaks during setup", not "need an OAuth wizard"). +- Specific enough to act on. +- Distinct from each other (merge heavy overlap). + +Report per theme: problem-framed name (5-10 words), optional one-line problem statement, member evidence IDs, affected personas, distinct accounts, average strength, total weight, impact type, confidence band, and exact confidence for audit. Typical: 2-5 themes. More than 7 = too fine. Only 1 = evidence too narrow. + +## Clustering method + +- **Modest corpora (hundreds of items):** LLM map-reduce, extract a normalized claim + tags per item, cluster the claims into themes, label each as a problem. No infrastructure; explainable. +- **Large corpora (thousands+):** pre-aggregate (counts, tag/keyword rollups, time buckets) and/or embed-and-cluster (e.g. HDBSCAN, which isolates outliers as noise), then label and frame clusters with the model. This is where a vector store earns its place. +- Evidence arrives already identity-deduped from `gather-evidence`, so one user across channels does not inflate a cluster. + +## Trend and pattern detection + +- Volume over time per theme; WoW/MoM deltas. +- Signal-count bands: 3-5 emerging (worth flagging), 6-12 established (likely real), 13+ well-known, 50+ fundamental issue or recent regression. +- A sudden spike in a theme = likely recent regression, flag it. +- Cross-source corroboration (2+ source types) and internal↔external triangulation raise confidence and may flag an industry-wide opportunity. +- Surface contradictions; do not smooth them. + +## Merge rules + +MERGE into a theme when 3+ independent signals describe the same core pain, same persona, same context, from 2+ source types. DO NOT merge when pains are similar but distinct (setup vs ongoing use), personas differ, or different root causes share a surface symptom. + +## Summary rules (3-5 sentences) + +Lead with the strongest problem candidate; state the number and diversity of sources; note contradictions or surprises; do NOT make build recommendations (that is the scoring step's job). + +## Confidence reasoning + +Compute Confidence with the formula in `../../prioritize-backlog/references/scoring-framework.md` and report it as a **band** (Low / Moderate / High), not a bare point, then explain it: "Confidence Moderate (~70): 12 items across 3 source types (strong diversity), recent, but no first-party quantitative data (gap); consistent on the pain, varying on severity." + +## Gap questions + +Specific, with where to look: "Do product analytics confirm the drop-off at the permissions step?" / "Is this enterprise or only SMB?" / "Still true since the Q2 UI change?" Avoid generic "needs more research". diff --git a/product-eval/skills/to-build-brief/SKILL.md b/product-eval/skills/to-build-brief/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..7ed9063305d4c48b689bf8aa2fa75bec0c330b4d --- /dev/null +++ b/product-eval/skills/to-build-brief/SKILL.md @@ -0,0 +1,31 @@ +--- +name: to-build-brief +description: Transform an approved decision into an agent-ready build spec, --code for Claude Code/Codex, --design for Figma/wireframe/Gemini. Use when the user says "turn this into a spec", "make it buildable", "build brief", "hand this to Claude Code / Figma", or after a brief is approved. Strips narrative voice; outputs structured, testable markdown. +--- + +# To Build Brief + +Turn the approved Opportunity Brief / decision into a structured spec an implementation or design agent can execute. This artifact is for builders: literal requirements, screens, states, data, acceptance criteria, and metrics. Reads `.product-eval//`; writes to `.product-eval//build/`. Two modes; strip persuasion and prose, builders want literal, testable detail (see `references/build-brief.md`). + +## Preconditions + +Only run on an approved decision. **Verify a passing decision-readiness verdict exists for this item in `.product-eval//decisions-log.md`**; if it has not passed, refuse, do not generate a build spec for an unvalidated bet (say what is missing and point to `decision-readiness` / `design-test`). Run the conformance preflight in `DATA-CONTRACT.md` (Validation) on the scope files first, so a build spec is never generated from a malformed `decisions-log.md` or `scores.md`. + +## `--code` (Claude Code / Codex) + +Emit: scope in/out (explicit non-goals); primary user flows; P0/P1 functional requirements each with **Given/When/Then acceptance criteria**; data model / key entities; states and edge/error cases; constraints, dependencies, interfaces; success metrics + instrumentation; and a "build this MVP slice first" callout. Markdown, hierarchical, no narrative voice. + +## `--design` (Figma / wireframe / Gemini) + +Emit: a screen-by-screen breakdown, each view, its elements, and its states (empty / loading / error / success); flows as numbered steps; the primary action/CTA per screen; key copy; brand/platform/responsive constraints. Optionally push to Figma via its MCP path. + +## Workflow + +1. Load the approved brief, decision, and scores from the scope. +2. Render the requested mode from `references/build-brief.md`. +3. Carry the success metrics over, so the builder produces something measurable. +4. Write to `.product-eval//build/` and note it's ready to hand to the target tool. + +## Output + +The build brief in the requested mode, in plain markdown so Claude Code, Codex, Gemini, and Figma's tooling all parse it. Lead with the scope and the MVP slice. End with `Next move:` and name the handoff target: Codex/Claude Code for implementation, or the design tool/agent for wireframes and interaction work. diff --git a/product-eval/skills/to-build-brief/references/build-brief.md b/product-eval/skills/to-build-brief/references/build-brief.md new file mode 100644 index 0000000000000000000000000000000000000000..c55bffbfcb00f8323deb7434b544d3b2faab5a06 --- /dev/null +++ b/product-eval/skills/to-build-brief/references/build-brief.md @@ -0,0 +1,31 @@ +# Build Brief, structures + +Agent-facing specs. Plain markdown, hierarchical, literal. No narrative voice, it distracts code and design agents. + +## `--code` structure + +- **Goal** (1-2 sentences) + target user + top job-to-be-done. +- **Scope**: In / Out (non-goals explicit). +- **User flows**: numbered steps per flow. +- **Functional requirements**: table: `ID | Requirement | Priority (P0/P1) | Acceptance criteria (Given/When/Then) | Owner`. +- **Data model**: entities, key fields, relationships. +- **States & edge cases**: empty / loading / error / success; boundary conditions; failure handling. +- **Constraints & dependencies**: tech constraints, interfaces/contracts, third-party deps. +- **Success metrics + instrumentation**: what to measure and where. +- **MVP slice**: the narrowest shippable wedge to build first. + +Acceptance criteria are the highest-leverage element for a code agent, every P0 must have testable Given/When/Then. + +## `--design` structure + +- **Goal + primary user + key flow** (1-2 sentences). +- **Screens**: one block per view: purpose, elements (header, inputs, primary CTA, …), and states (empty / loading / error / success). +- **Flows**: numbered step sequences across screens; the primary action per screen. +- **Copy**: key microcopy (CTAs, empty states, error messages). +- **Constraints**: brand, platform (web/mobile), responsive behavior, accessibility notes. + +Screen + state enumeration is the highest-leverage element for a design agent. Prose is useless here. + +## Both modes + +Carry the success metrics from the decision so the build is measurable, and link back to the approved brief for context. diff --git a/product-eval/skills/write-brief/SKILL.md b/product-eval/skills/write-brief/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..3f542e124b83676b6f494361ceb6916ff2fa8e21 --- /dev/null +++ b/product-eval/skills/write-brief/SKILL.md @@ -0,0 +1,40 @@ +--- +name: write-brief +description: "Use when the user wants the human-facing decision document for a problem or bet that has been investigated, to align people on it or to force a choice. Covers three shapes: a narrative or report (problem, why now, impact, and the ask), a hard-questions FAQ, or a decision or options memo that weighs several options (including do-nothing), recommends one, and states the ask. Trigger on phrasing like 'write the brief', 'draft the brief', 'opportunity brief', 'the narrative', 'the FAQ', 'decision memo', 'options memo', 'recommendation memo', 'weigh options and recommend one', or after a decision passes the readiness gate. This is the leadership decision artifact, rendered from existing evidence and scores, NOT an engineering PRD, spec, acceptance criteria, status update, competitive battlecard, or metrics review. Refuses to invent metrics." +--- + +# Write Brief + +Produce the human decision artifact for a decided problem or bet, in the requested style. Narrative and FAQ styles persuade and align humans around the problem, why now, impact, and ask; memo style forces a yes/no or option choice with a recommendation and explicit ask. It is not an implementation spec. Each style renders from the same `.product-eval//` content (problem, evidence, themes, scores, decision). Never invent metrics, owners, or dates, write "Not provided in input" instead. + +## Preconditions + +The brief is gated on `decision-readiness`. **Check the item's latest verdict in `.product-eval//decisions-log.md`**: if there is no passing (Decide-now) verdict, or the latest outcome is "Run a research sprint", do not produce a confident brief. Say so, and offer a "decision-readiness brief" that states the gaps and points to `design-test` for the cheapest validating test instead. + +Also run the conformance preflight in `DATA-CONTRACT.md` (Validation) on the scope files you render from; if `scores.md`, `themes.md`, or `decisions-log.md` fails a check, report the violation rather than rendering a brief from inconsistent data. + +## Styles, `--style narrative | faq | memo | both` + +### narrative / report +A flowing readout: problem → why now → proposed solution → customer & business impact → success metrics → the ask. Prose, not bullets; one quantified claim early; explicit before/after; no unsupported roadmap commitments. Optional press-release voice (write it as if already shipped). Structure and starters in `references/opportunity-brief.md`. + +### faq +The hard-questions version, the completeness gate. Answer the question bank in `references/opportunity-brief.md` (problem, customer, timing, alternatives, limitations, out-of-scope, failure modes, success metrics, one-way doors, …). This version is also the scaffold the `critique` lens-panel attacks before the doc is "done". + +### memo +A decision-oriented memo that forces a yes/no or option choice by a deadline, for *driving a decision*, not persuading. Headings in order: purpose & decision requested (one sentence + deadline) · executive summary (customer + business impact) · current state & evidence (each claim with source + strength) · root cause · options considered (≥3 incl. do-nothing; table `Option | Summary | Benefits | Tradeoffs | Key risk | Why not selected`) · recommended strategy (falsifiable success/failure criteria) · metrics scorecard (`Metric | Baseline | Target | Owner | Date`) · economics if relevant · risks/dependencies/mitigations (owner + close date) · the ask (concrete, owner- and time-bound). Run `critique` on the recommendation's falsifiability and whether do-nothing is honestly weighed. + +### both +Narrative as the readout, FAQ as the backing appendix. + +## Workflow + +1. Load the decided problem, its evidence, scores, and verdict from the scope. +2. Render the requested style(s) from `references/opportunity-brief.md`. +3. Put evidence in appendices, not in marketing claims; cite source + strength. +4. Run `critique` (panel mode) on the FAQ and tag each answer resolved / open / contested before calling it done. +5. Export to docx/pdf if asked (read the docx/pdf skills). Write to `.product-eval//docs/`. + +## Output + +The brief in the requested style plus the critique summary (resolved / open / contested counts). Lead with the one-paragraph essence. End with `Next move:` and recommend resolving open FAQ items, pressure-testing the plan if commitment risk remains, or turning the approved decision into a build brief. diff --git a/product-eval/skills/write-brief/references/opportunity-brief.md b/product-eval/skills/write-brief/references/opportunity-brief.md new file mode 100644 index 0000000000000000000000000000000000000000..a57f6811589cefcf7040e6083129f2283d1fd6ec --- /dev/null +++ b/product-eval/skills/write-brief/references/opportunity-brief.md @@ -0,0 +1,63 @@ +# Opportunity Brief, structure & question bank + +Two renderings of the same content. Neutral naming; "narrative style" is the press-release-from-the-future approach without the label. + +## Narrative / report version + +Order: +1. Headline + one-line subhead (what changes, for whom). +2. Opening paragraph, what's proposed/launching + quantified value + the previous tradeoff it removes. +3. How it works, in one realistic workflow. +4. Before → after contrast. +5. Customer voice (before → now → outcome). +6. Business impact (revenue / cost / strategic), with the top assumption named. +7. Success metrics (north star + guardrail): baseline → target → window. +8. The ask, decision requested, owner, date. + +Rules: prose, no bullets in the final output; one quantified claim in the first two paragraphs; explicit before/after; no unsupported roadmap commitments; active voice with named actors; future tense only for approved commitments. + +Starters: +- "Today {persona} must {workaround}, costing {quantified impact}." +- "{Product} now lets {persona} {job} without {tradeoff}, improving {metric} from {baseline} to {target}." +- "Before X we {workaround}; now we {new workflow}, which {outcome}.", {Name, Title, Company} + +## FAQ version (the completeness gate) + +Answer every relevant question; "Not provided in input" when a value is missing. External questions then internal, continuous numbering. + +Core questions: +1. What is this and who is it for? +2. What customer problem does it solve, and what evidence supports it? +3. Why now? (the trigger) +4. How does it work at a high level? +5. How is it different from the alternatives, including the status quo / workaround? +6. Which use cases should NOT use it? +7. What's explicitly out of scope, and why? +8. What are the key limitations / known gaps? +9. What are the primary failure modes and mitigations? +10. How will we measure success (north star + guardrails), with baseline and target? +11. What are the top risks, owners, and mitigations? +12. Which decisions are one-way doors? +13. What does it cost / what's the rough economics? +14. What's the migration path from today's workflow? + +Add context-pack questions (platform, security/privacy, GTM, economics) by relevance. Answer rules: customer-facing answers concise (2-4 sentences); don't invent quantitative values; every claim traceable to evidence in the appendix. + +## Appendix + +Evidence summary (source · strength · recency), economics assumptions, cohort/journey notes. Appendices carry evidence, not marketing. + + +## Decision-memo version (`--style memo`) + +For driving a decision by a deadline (not persuasion). Headings in order: +1. Purpose & decision requested, one explicit sentence + a deadline. +2. Executive summary, customer + business impact. +3. Current state & evidence, each claim with source + strength. +4. Root cause, the cause, not a restated symptom. +5. Options considered, ≥3 including do-nothing: `Option | Summary | Benefits | Tradeoffs | Key risk | Why not selected`. +6. Recommended strategy, falsifiable success/failure criteria. +7. Metrics scorecard, `Metric | Baseline | Target | Owner | Date`. +8. Economics (if relevant), gross → net → margin; sensitivities. +9. Risks, dependencies, mitigations, owner + close date. +10. The ask, concrete, owner- and time-bound. diff --git a/proxy.ts b/proxy.ts new file mode 100644 index 0000000000000000000000000000000000000000..6200ef7e354165e09e98caaa8b942272d33dd1f1 --- /dev/null +++ b/proxy.ts @@ -0,0 +1,81 @@ +import { NextRequest, NextResponse } from "next/server" +import { isAuthenticatedProxy } from "@/lib/auth/proxy" + +// ── Sliding-window rate limiter ─────────────────────────────────────────────── +// Guards /api/fetch-url from being hammered as a public CORS proxy. +// On serverless (Vercel) this is best-effort per-instance; on persistent +// hosts (Render) it is reliable across requests. + +const store = new Map() + +function isRateLimited(ip: string, path: string): boolean { + const now = Date.now() + const key = `${ip}:${path}` + const hits = (store.get(key) ?? []).filter(t => now - t < 60_000) + if (hits.length >= 30) return true // 30 URL fetches/min per IP + hits.push(now) + store.set(key, hits) + return false +} + +export function proxy(req: NextRequest) { + const { pathname } = req.nextUrl + + const publicPaths = new Set(["/login", "/api/auth/login", "/api/auth/logout", "/api/auth/status", "/health", "/api-docs"]) + if (publicPaths.has(pathname)) return NextResponse.next() + if (pathname.startsWith("/api/")) { + // API endpoints handle their own deep authentication via requireAuth. + // The proxy only does a light pass to ensure unauthenticated requests + // don't waste downstream resources, but relies on API route handlers + // to correctly reject unauthorized access. + if (!isAuthenticatedProxy(req)) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401, headers: { "Cache-Control": "no-store" } }) + } + } else if (!isAuthenticatedProxy(req)) { + return NextResponse.redirect(new URL("/login", req.url)) + } + + if (!pathname.startsWith("/api/fetch-url")) { + return NextResponse.next({ headers: { "Cache-Control": "private, no-store" } }) + } + + // Origin check — block requests from other origins. + // Use strict URL parsing rather than substring matching to prevent + // bypasses like https://evil.com?nodepad.space passing the check. + const origin = req.headers.get("origin") ?? "" + const referer = req.headers.get("referer") ?? "" + const host = (req.headers.get("host") ?? "").split(":")[0] // strip port + + function strictHostMatch(headerValue: string): boolean { + try { return new URL(headerValue).hostname === host } catch { return false } + } + + const isLocalhost = strictHostMatch(origin || referer) + ? ["localhost", "127.0.0.1"].includes(new URL(origin || referer).hostname) + : (origin + referer).includes("localhost") + const isSameOrigin = strictHostMatch(origin) || strictHostMatch(referer) + + if (origin && !isLocalhost && !isSameOrigin) { + return new NextResponse(JSON.stringify({ error: "Forbidden" }), { + status: 403, + headers: { "Content-Type": "application/json" }, + }) + } + + const ip = req.headers.get("x-forwarded-for")?.split(",")[0].trim() + ?? req.headers.get("x-real-ip") + ?? "unknown" + + if (isRateLimited(ip, pathname)) { + return new NextResponse(JSON.stringify({ error: "Rate limit exceeded." }), { + status: 429, + headers: { "Content-Type": "application/json", "Retry-After": "60" }, + }) + } + + return NextResponse.next({ headers: { "Cache-Control": "private, no-store" } }) +} + +export const config = { + matcher: ["/((?!_next/static|_next/image|icon.svg|favicon.ico|apple-icon.png).*)"], +} diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000000000000000000000000000000000000..d201be7cd232e4ddeda530cb954f902bf8ea78df --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/public/nodepad.jpg b/public/nodepad.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ed10cc92c2e43edab9eefb517249748cd7c89ca2 Binary files /dev/null and b/public/nodepad.jpg differ diff --git a/scripts/predeploy-docker.ts b/scripts/predeploy-docker.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ff86ad497ff995651f634c89c68120d19fe50dc --- /dev/null +++ b/scripts/predeploy-docker.ts @@ -0,0 +1,90 @@ +import { spawn } from "node:child_process" +import { createServer } from "node:http" +import { fileURLToPath } from "node:url" +import path from "node:path" +import { FIXTURES, freePort, isolatedEnv } from "./predeploy-smoke" + +const dockerEnv = () => { + const env = isolatedEnv() + for (const key of ["DOCKER_HOST", "DOCKER_CONTEXT", "DOCKER_TLS", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"]) delete env[key] + return env +} +async function command(args: string[], allowFailure = false) { + const child = spawn("docker", ["--host=unix:///var/run/docker.sock", ...args], { env: dockerEnv(), stdio: ["ignore", "pipe", "pipe"] }) + let output = "" + child.stdout.on("data", data => { output += data }); child.stderr.on("data", data => { output += data }) + const code = await new Promise((resolve, reject) => { child.once("error", reject); child.once("exit", code => resolve(code ?? 1)) }) + if (code && !allowFailure) { + const safe = Object.values(FIXTURES).reduce((text, secret) => text.split(secret).join("[FIXTURE_REDACTED]"), output) + throw new Error(`Docker ${args[0]} failed:\n${safe.slice(-6000)}`) + } + return { code, output } +} +export async function dockerGate() { + try { + if ((await command(["info"], true)).code !== 0) { console.log("SKIPPED_ENV_DOCKER: managed local daemon unavailable"); return { result: "SKIPPED_ENV_DOCKER", passed: 0, failed: 0, skipped: 3 } } + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error + console.log("SKIPPED_ENV_DOCKER: docker executable absent"); return { result: "SKIPPED_ENV_DOCKER", passed: 0, failed: 0, skipped: 3 } + } + const args = ["build", "-t", "nodepad-predeploy"] + // Optional CA bundle is only a BuildKit secret; never copied into the image. + if (process.env.CODEX_PROXY_CERT) args.push("--secret", "id=proxy_ca,src=/etc/ssl/certs/ca-certificates.crt") + args.push(".") + await command(args) + const server = createServer(async (req, res) => { + if (req.headers.authorization !== `Bearer ${FIXTURES.OPENAI_API}`) { res.writeHead(401).end(); return } + res.setHeader("Content-Type", "application/json") + if (req.url === "/v1/models") res.end(JSON.stringify({ data: [{ id: "qwythos-9b" }] })) + else if (req.url === "/v1/chat/completions") res.end(JSON.stringify({ choices: [{ message: { content: "NODEPAD_LLM_OK" } }] })) + else res.writeHead(404).end() + }) + const providerPort = await new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "0.0.0.0", () => resolve((server.address() as { port: number }).port)) }) + const port = await freePort() + const name = `nodepad-predeploy-${process.pid}` + let passed = 1 + const check = (condition: unknown, message: string) => { if (!condition) throw new Error(message); passed++ } + const env = { ...FIXTURES, NODEPAD_GITHUB_SYNC: "false", NODEPAD_MAX_RESEARCH_TASKS_PER_HEARTBEAT: "0", NODEPAD_RESEARCH_ENABLED: "false", NODEPAD_DATA_DIR: "/app/data", OPENAI_URL: `http://host.docker.internal:${providerPort}/v1`, OPENAI_MODEL: "qwythos-9b" } + try { + // Loopback-only publication; no host networking. Docker's configured proxies stay intact. + await command(["run", "-d", "--name", name, "--add-host", "host.docker.internal:host-gateway", "-p", `127.0.0.1:${port}:7860`, ...Object.entries(env).flatMap(([key, value]) => ["-e", `${key}=${value}`]), "nodepad-predeploy"]) + const base = `http://127.0.0.1:${port}` + async function request(url: string, options: RequestInit = {}) { + return fetch(base + url, { ...options, redirect: "manual", signal: AbortSignal.timeout(15_000) }) + } + let ready = false + for (let n = 0; n < 100; n++) { + try { if ((await request("/health")).ok) { ready = true; break } } catch { /* startup poll */ } + await new Promise(resolve => setTimeout(resolve, 100)) + } + check(ready, "Docker readiness failed") + check((await request("/login")).ok, "Docker login page") + check((await request("/api/v1/workspaces")).status === 401, "Docker anonymous protection") + const uid = (await command(["exec", name, "id", "-u"])).output.trim() + check(uid === "1001", "Docker must run as non-root nextjs UID 1001") + const login = await request("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ accessKey: FIXTURES.NODEPAD_LOGIN_KEY }) }) + check(login.ok, "Docker writable session store") + const cookie = (login.headers.get("set-cookie") || "").split(";")[0] + const headers = { Authorization: `Bearer ${FIXTURES.NODEPAD_API_KEY}`, "Content-Type": "application/json" } + check((await request("/api/v1/workspaces/docker/evidence", { method: "POST", headers, body: JSON.stringify({ claim: "Docker persistent fixture" }) })).status === 201, "Docker non-root data write") + check((await request("/api/v1/llm/models", { headers: { Cookie: cookie } })).ok, "Docker local mock discovery") + const completion = await request("/api/v1/llm/test", { method: "POST", headers, body: "{}" }) + check(completion.ok && (await completion.json()).content === "NODEPAD_LLM_OK", "Docker local mock inference") + await command(["restart", name]) + for (let n = 0; n < 100; n++) { try { if ((await request("/health")).ok) break } catch {} await new Promise(resolve => setTimeout(resolve, 100)) } + const evidence = await request("/api/v1/workspaces/docker/evidence", { headers }) + check(evidence.ok && (await evidence.json()).length === 1, "Docker data survives restart") + const sessionStatus = await request("/api/auth/status", { headers: { Cookie: cookie } }) + check(sessionStatus.ok && (await sessionStatus.json()).authenticated === true, "Docker session survives restart") + const logs = (await command(["logs", name])).output + check(Object.values(FIXTURES).every(secret => !logs.includes(secret)), "Docker logs must not contain fixture credentials") + console.log(`PASS Docker build/runtime/non-root/persistence: ${passed} assertions`) + return { result: "PASS", passed, failed: 0, skipped: 0 } + } finally { + await command(["rm", "-f", name], true) + server.closeAllConnections(); await new Promise(resolve => server.close(() => resolve())) + } +} +if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) dockerGate().catch(error => { + console.error(error instanceof Error ? error.message : "Docker validation failed"); process.exitCode = 1 +}) diff --git a/scripts/predeploy-env.ts b/scripts/predeploy-env.ts new file mode 100644 index 0000000000000000000000000000000000000000..20a76cfae94da03bf18794df4872355cb3794245 --- /dev/null +++ b/scripts/predeploy-env.ts @@ -0,0 +1,13 @@ +// Unit tests must not inherit deployment credentials or activate external workers. +for (const name of Object.keys(process.env)) { + if (/^(NODEPAD_|OPENAI_|NEXT_PUBLIC_.*(?:KEY|TOKEN|SECRET)|GH_TOKEN$|GITHUB_TOKEN$|HF_TOKEN$)/.test(name)) delete process.env[name] +} +process.env.NODEPAD_GITHUB_SYNC = "false" +process.env.NODEPAD_MAX_RESEARCH_TASKS_PER_HEARTBEAT = "0" +const originalFetch = globalThis.fetch +// Tests may replace fetch with explicit mocks. Unmocked network calls are loopback-only. +globalThis.fetch = ((input: RequestInfo | URL, init?: RequestInit) => { + const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url) + if (!["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)) throw new Error("PREDEPLOY_EXTERNAL_NETWORK_FORBIDDEN") + return originalFetch(input, init) +}) as typeof fetch diff --git a/scripts/predeploy-smoke.ts b/scripts/predeploy-smoke.ts new file mode 100644 index 0000000000000000000000000000000000000000..388701331200849d772c3086beb37911a27ff561 --- /dev/null +++ b/scripts/predeploy-smoke.ts @@ -0,0 +1,195 @@ +import assert from "node:assert/strict" +import { spawn, type ChildProcess } from "node:child_process" +import { createServer, type Server } from "node:http" +import { cp, mkdir, mkdtemp, readFile, readdir, rm } from "node:fs/promises" +import path from "node:path" +import { tmpdir } from "node:os" +import { fileURLToPath } from "node:url" + +export const FIXTURES = { NODEPAD_LOGIN_KEY: "SUPER_SECRET_LOGIN_928374", NODEPAD_API_KEY: "SUPER_SECRET_NODEPAD_API_928374", OPENAI_API: "SUPER_SECRET_OPENAI_928374" } +export function isolatedEnv(): NodeJS.ProcessEnv { + const env = { ...process.env } + for (const name of Object.keys(env)) if (/^(NODEPAD_|OPENAI_|GH_TOKEN$|GITHUB_TOKEN$|HF_TOKEN$|NEXT_PUBLIC_.*(?:KEY|TOKEN|SECRET))/.test(name)) delete env[name] + return { ...env, ...FIXTURES, NODE_ENV: "production", NODEPAD_GITHUB_SYNC: "false", NODEPAD_MAX_RESEARCH_TASKS_PER_HEARTBEAT: "0", NODEPAD_RESEARCH_ENABLED: "false", NODEPAD_SYNTHESIS_MODE: "deterministic", NEXT_TELEMETRY_DISABLED: "1" } +} +const secretSafe = (value: string) => Object.values(FIXTURES).every(secret => !value.includes(secret)) +const listen = (server: Server, host = "127.0.0.1") => new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, host, () => resolve((server.address() as { port: number }).port)) }) +const close = (server: Server) => new Promise(resolve => { server.closeAllConnections(); server.close(() => resolve()) }) +export async function freePort() { const server = createServer(); const port = await listen(server); await close(server); return port } + +export async function smoke(docker = false) { + const root = process.cwd() + const temporary = await mkdtemp(path.join(tmpdir(), "nodepad-predeploy-")) + let child: ChildProcess | undefined + let logs = "" + let modelVersion = 1 + let discoveryFailure = false + let completionFailure = false + const observed: { model: string; authorized: boolean }[] = [] + const groups: Record = {} + let group = "" + const check = (condition: unknown, message: string) => { assert.ok(condition, message); groups[group].passed++ } + async function section(name: string, action: () => Promise) { + group = name; groups[name] = { passed: 0, failed: 0, result: "PASS" } + try { await action(); console.log(`PASS ${name}: ${groups[name].passed} assertions`) } + catch (error) { groups[name].failed++; groups[name].result = "FAIL"; throw error } + } + const provider = createServer(async (req, res) => { + const authorized = req.headers.authorization === `Bearer ${FIXTURES.OPENAI_API}` + if (!authorized) { res.writeHead(401).end(); return } + if (req.url === "/v1/models") { + res.setHeader("Content-Type", "application/json") + if (discoveryFailure) { res.writeHead(404).end("unsupported"); return } + res.end(JSON.stringify({ data: ["model-a", "Spark-X2.5", "Qwen/Qwen3.8-27B-FP8", "strange/custom:model@v2", ...(modelVersion > 1 ? ["model-c"] : [])].map(id => ({ id })) })) + return + } + if (req.url === "/v1/chat/completions") { + let text = ""; for await (const chunk of req) text += chunk + const body = JSON.parse(text); observed.push({ model: body.model, authorized }) + if (completionFailure) { res.writeHead(503).end("fixture provider unavailable"); return } + res.setHeader("Content-Type", "application/json") + res.end(JSON.stringify({ model: body.model, choices: [{ message: { content: "NODEPAD_LLM_OK" } }] })) + return + } + res.writeHead(404).end() + }) + let base = "" + let cookie = "" + const captures: string[] = [] + async function request(url: string, method = "GET", body?: unknown, auth: "none" | "session" | "machine" | "bad-session" | "bad-machine" | "human-bearer" = "none", extra: Record = {}) { + const headers: Record = { ...extra } + if (auth === "session") headers.Cookie = cookie + if (auth === "machine") headers.Authorization = `Bearer ${FIXTURES.NODEPAD_API_KEY}` + if (auth === "bad-machine") headers.Authorization = "Bearer invalid-fixture" + if (auth === "human-bearer") headers.Authorization = `Bearer ${FIXTURES.NODEPAD_LOGIN_KEY}` + if (auth === "bad-session") headers.Cookie = "nodepad_session=invalid-fixture" + if (body !== undefined) headers["Content-Type"] = "application/json" + const response = await fetch(base + url, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), redirect: "manual", signal: AbortSignal.timeout(20_000) }) + const text = await response.text(); captures.push(text) + let data: any; try { data = JSON.parse(text) } catch { data = text } + return { response, data, text } + } + async function stop() { + if (!child || child.exitCode !== null || child.signalCode !== null) return + const exited = new Promise(resolve => child!.once("exit", () => resolve())) + child.kill("SIGTERM") + const timer = setTimeout(() => child?.kill("SIGKILL"), 5000) + await exited; clearTimeout(timer) + } + try { + const providerPort = await listen(provider, docker ? "0.0.0.0" : "127.0.0.1") + const port = await freePort() + base = `http://127.0.0.1:${port}` + const env = { ...isolatedEnv(), NODEPAD_AUTH_DIR: path.join(temporary, "sessions"), NODEPAD_DATA_DIR: path.join(temporary, "data"), OPENAI_URL: `http://127.0.0.1:${providerPort}/v1`, OPENAI_MODEL: "qwythos-9b", PORT: String(port), HOSTNAME: "127.0.0.1" } + await cp(path.join(root, ".next/static"), path.join(root, ".next/standalone/.next/static"), { recursive: true }) + await cp(path.join(root, "public"), path.join(root, ".next/standalone/public"), { recursive: true }) + const start = () => { + child = spawn(process.execPath, [path.join(root, ".next/standalone/server.js")], { cwd: root, env, stdio: ["ignore", "pipe", "pipe"] }) + child.stdout?.on("data", data => { logs += data.toString() }); child.stderr?.on("data", data => { logs += data.toString() }) + } + const ready = async () => { + for (let attempt = 0; attempt < 100; attempt++) { + if (child?.exitCode !== null) throw new Error("Production server exited before readiness") + try { if ((await fetch(base + "/health", { signal: AbortSignal.timeout(500) })).ok) return } catch { /* poll readiness */ } + await new Promise(resolve => setTimeout(resolve, 100)) + } + throw new Error("Production server did not become ready") + } + start(); await ready() + await section("Authentication", async () => { + check((await request("/health")).response.status === 200, "health public") + check((await request("/login")).response.status === 200, "login public") + check((await request("/api-docs")).response.status === 200, "docs public") + const anonymous = await request("/") + check(anonymous.response.status === 307 && new URL(anonymous.response.headers.get("location")!, base).pathname === "/login", "anonymous redirect") + check((await request("/api/auth/status")).data.authenticated === false, "anonymous status") + for (const body of [{}, { accessKey: "" }, { accessKey: "wrong" }, { accessKey: FIXTURES.NODEPAD_API_KEY }]) check((await request("/api/auth/login", "POST", body)).response.status === 401, "bad human login") + const login = await request("/api/auth/login", "POST", { accessKey: FIXTURES.NODEPAD_LOGIN_KEY }) + check(login.response.status === 200 && login.data.success, "human login works") + const header = login.response.headers.get("set-cookie") || "" + for (const attribute of ["HttpOnly", "Secure", "Path=/", "SameSite=lax", "Max-Age="]) check(header.includes(attribute), "cookie security attribute") + cookie = header.split(";")[0] + check((await request("/", "GET", undefined, "session")).response.status === 200, "authenticated page no redirect loop") + check((await request("/api/auth/status", "GET", undefined, "session")).data.authenticated === true, "session status") + }) + await section("Route protection", async () => { + const routes: [string, string, unknown?][] = [ + ["/api/v1/workspaces", "GET"], ["/api/v1/portfolio", "GET"], ["/api/v1/llm/status", "GET"], ["/api/v1/llm/models", "GET"], ["/api/v1/embeddings/status", "GET"], ["/api/v1/workspaces/matrix/adjudications", "GET"], + ...["context", "graph", "evidence", "hypotheses", "brain", "events"].map(name => [`/api/v1/workspaces/matrix/${name}`, "GET"] as [string, string]), + ["/api/v1/workspaces/matrix/evidence", "POST", { claim: "Matrix evidence" }], ["/api/v1/workspaces/matrix/heartbeat", "POST", { runId: "matrix" }], ["/api/v1/workspaces/matrix/brain/export", "POST", {}], + ] + for (const [url, method, body] of routes) for (const auth of ["none", "bad-session", "bad-machine", "human-bearer", "session", "machine"] as const) { + const result = await request(url, method, body, auth) + check(auth === "session" || auth === "machine" ? result.response.ok : result.response.status === 401, `protected matrix ${method} ${url} ${auth}`) + } + }) + await section("Custom models", async () => { + const models = await request("/api/v1/llm/models", "GET", undefined, "session") + check(models.data.defaultModel === "qwythos-9b", "environment default") + for (const id of ["qwythos-9b", "Spark-X2.5", "Qwen/Qwen3.8-27B-FP8", "strange/custom:model@v2"]) check(models.data.models.some((m: any) => m.id === id), "opaque discovered model") + modelVersion++ + check((await request("/api/v1/llm/models", "GET", undefined, "session")).data.models.some((m: any) => m.id === "model-c"), "refresh exposes new model") + discoveryFailure = true + check((await request("/api/v1/llm/models", "GET", undefined, "session")).data.models.some((m: any) => m.id === "qwythos-9b"), "failed discovery retains default") + discoveryFailure = false + check((await request("/api/v1/llm/test", "POST", {}, "session")).data.content === "NODEPAD_LLM_OK", "mock inference success") + check(observed.at(-1)?.model === "qwythos-9b" && observed.at(-1)?.authorized, "default inference payload") + check((await request("/api/v1/llm/chat/completions", "POST", { model: "my-private-model-v37", messages: [{ role: "user", content: "test" }] }, "session")).response.ok, "manual model accepted") + check(observed.at(-1)?.model === "my-private-model-v37", "manual inference payload") + }) + await section("Nodepad core", async () => { + const evidence = await request("/api/v1/workspaces/workspace-a/evidence", "POST", { claim: "Independent demand", strength: 5, sourceType: "interview" }, "machine", { "Idempotency-Key": "e1", "x-nodepad-actor-type": "agent", "x-nodepad-actor-id": "fixture-agent" }) + check(evidence.response.status === 201 && evidence.data.workspaceId === "workspace-a" && evidence.data.provenance.actorId === "fixture-agent", "evidence persisted actor metadata") + const duplicate = await request("/api/v1/workspaces/workspace-a/evidence", "POST", { claim: "Independent demand" }, "machine", { "Idempotency-Key": "e1" }) + check(duplicate.data.id === evidence.data.id, "idempotent evidence") + const hypothesis = await request("/api/v1/workspaces/workspace-a/hypotheses", "POST", { statement: "Demand exists", supportingEvidence: [evidence.data.id], evidenceScore: 100, confidence: 100, qualification: "strongly_qualified" }, "machine") + check(hypothesis.response.status === 201 && hypothesis.data.confidence < 100, "scoring overrides supplied LLM authority") + for (const name of ["context", "graph", "evidence", "hypotheses", "brain"]) check((await request(`/api/v1/workspaces/workspace-a/${name}`, "GET", undefined, "machine")).response.ok, "workspace retrieval") + check((await request("/api/v1/workspaces/workspace-b/evidence", "GET", undefined, "machine")).data.length === 0, "workspace isolation") + const context = (await request("/api/v1/workspaces/workspace-a/context", "GET", undefined, "machine")).data + check((await request("/api/v1/workspaces/workspace-a/research/tasks", "POST", { question: "q", objective: "support" }, "machine", { "If-Match": String(context.revision) })).response.ok, "revision write") + const conflict = await request("/api/v1/workspaces/workspace-a/research/tasks", "POST", { question: "stale", objective: "support" }, "machine", { "If-Match": String(context.revision) }) + check(conflict.response.status === 409 && conflict.data.error.startsWith("revision_conflict"), "stale revision conflict") + completionFailure = true + check((await request("/api/v1/workspaces/workspace-a/syntheses/run", "POST", { synthesisType: "S011", runId: "fallback" }, "machine")).response.ok, "deterministic synthesis without LLM") + const before = observed.length + for (let i = 0; i < 3; i++) check((await request("/api/v1/workspaces/workspace-a/heartbeat", "POST", { runId: `repeat-${i}` }, "machine")).response.ok, "deterministic heartbeat") + check(observed.length === before, "heartbeat makes no inference calls") + const graph = (await request("/api/v1/workspaces/workspace-a/graph", "GET", undefined, "machine")).data + check(new Set(graph.nodes.map((n: any) => n.id)).size === graph.nodes.length && graph.nodes.length < 200, "bounded deduplicated graph") + const oversized = await request("/api/v1/workspaces/workspace-a/evidence", "POST", { claim: "x".repeat(1_000_001) }, "machine") + check(!oversized.response.ok && oversized.data.error === "request_body_too_large", "oversized mutation rejected") + completionFailure = false + }) + await section("Secret isolation", async () => { + for (const response of captures) check(secretSafe(response), "response contains no fixture credentials") + check(secretSafe(logs), "production logs secret safe") + async function scan(directory: string) { + for (const entry of await readdir(directory, { withFileTypes: true })) { + const file = path.join(directory, entry.name) + if (entry.isDirectory()) await scan(file) + else check(secretSafe(await readFile(file, "utf8")), "client/persistent file secret safe") + } + } + await scan(path.join(root, ".next/static")) + await scan(temporary) + }) + await section("Persistence and logout", async () => { + await stop(); start(); await ready() + check((await request("/api/auth/status", "GET", undefined, "session")).data.authenticated === true, "session survives process restart") + check((await request("/api/v1/workspaces/workspace-a/evidence", "GET", undefined, "machine")).data.length === 1, "evidence survives process restart") + check((await request("/api/auth/logout", "POST", undefined, "session")).response.ok, "logout success") + check((await request("/api/v1/workspaces", "GET", undefined, "session")).response.status === 401, "old cookie revoked") + check((await request("/api/v1/workspaces", "GET", undefined, "machine")).response.ok, "machine independent of logout") + }) + return groups + } catch (error) { + const message = Object.values(FIXTURES).reduce((text, secret) => text.split(secret).join("[FIXTURE_REDACTED]"), error instanceof Error ? error.message : "smoke failure") + throw Object.assign(new Error(`${group || "Startup"}: ${message}`), { groups }) + } finally { + await stop(); await close(provider); await rm(temporary, { recursive: true, force: true }) + } +} +if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) { + smoke().catch(error => { console.error(`FAIL ${error instanceof Error ? error.message : "local production smoke"}`); process.exitCode = 1 }) +} diff --git a/scripts/predeploy.ts b/scripts/predeploy.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f110696bbfe88c7ec02562538119a0cce410e0f --- /dev/null +++ b/scripts/predeploy.ts @@ -0,0 +1,49 @@ +import { spawn } from "node:child_process" +import { mkdir, mkdtemp, readFile, stat, writeFile } from "node:fs/promises" +import { tmpdir } from "node:os" +import path from "node:path" +import { FIXTURES, isolatedEnv, smoke } from "./predeploy-smoke" +import { dockerGate } from "./predeploy-docker" + +async function main() { + const logs = await mkdtemp(path.join(tmpdir(), "nodepad-predeploy-logs-")) + const results: Record = {} + const env = isolatedEnv() + let failed = false + const safe = (text: string) => Object.values(FIXTURES).reduce((value, secret) => value.split(secret).join("[FIXTURE_REDACTED]"), text) + async function run(group: string, command: string, args: string[]) { + const child = spawn(command, args, { env, stdio: ["ignore", "pipe", "pipe"] }) + let output = "" + child.stdout.on("data", value => { output += value }); child.stderr.on("data", value => { output += value }) + const code = await new Promise((resolve, reject) => { child.once("error", reject); child.once("exit", code => resolve(code ?? 1)) }) + const file = path.join(logs, `${group}.log`) + await writeFile(file, safe(output)) + const count = (name: string) => Number(output.match(new RegExp(`(?:ℹ |# )${name} (\\d+)`))?.[1] || 0) + results[group] = { result: code ? "FAIL" : "PASS", passed: count("pass") || (code ? 0 : 1), failed: count("fail") || (code ? 1 : 0), skipped: count("skipped"), log: file } + console.log(`${code ? "FAIL" : "PASS"} ${group}${count("tests") ? `: ${count("tests")} tests` : ""}`) + if (code) { failed = true; console.error(safe(output).slice(-6000)) } + return code === 0 + } + try { + await Promise.all([run("Lint", "npm", ["run", "lint"]), run("Types", "node", ["node_modules/typescript/bin/tsc", "--noEmit", "--incremental", "false"])]) + await Promise.all([run("Unit", "npm", ["run", "test:predeploy:unit"]), run("Registry", "npm", ["run", "test:registry"]), run("Fixture", "npm", ["run", "validate:fixture"])]) + if (failed) throw new Error("Code quality or unit gate failed") + if (!await run("Build", "npm", ["run", "build"])) throw new Error("Production build failed") + for (const file of [".next/standalone/server.js", ".next/static", "public"]) await stat(file) + results.Artifacts = { result: "PASS", passed: 3, failed: 0, skipped: 0 } + Object.assign(results, await smoke()) + results.Docker = await dockerGate() + } catch (error) { + failed = true + console.error(safe(error instanceof Error ? error.message : "Unknown predeploy failure")) + if (error && typeof error === "object" && "groups" in error) Object.assign(results, error.groups) + results.GateFailure = { result: "FAIL", failed: 1 } + } + const verdict = failed ? "RED — DO NOT REDEPLOY" : (results.Docker as { result: string })?.result === "SKIPPED_ENV_DOCKER" ? "YELLOW — INTERNAL TESTS PASS EXCEPT EXPLICIT ENVIRONMENT-ONLY CHECKS" : "GREEN — READY TO REDEPLOY" + const report = { generatedAt: new Date().toISOString(), verdict, results } + await writeFile("/tmp/nodepad-predeploy-report.json", JSON.stringify(report, null, 2)) + console.log(verdict) + console.log("Report: /tmp/nodepad-predeploy-report.json") + if (failed) process.exitCode = 1 +} +main().catch(() => { console.error("RED — DO NOT REDEPLOY: runner failed"); process.exitCode = 1 }) diff --git a/scripts/validate-fixture.ts b/scripts/validate-fixture.ts new file mode 100644 index 0000000000000000000000000000000000000000..bc2bc2900e29edad0a8477a4579b93a6e7128ca5 --- /dev/null +++ b/scripts/validate-fixture.ts @@ -0,0 +1,10 @@ +import assert from "node:assert/strict" +import { fixture, evidence, hypothesis } from "../tests/helpers" +import { runSynthesis } from "../lib/synthesis/runner" +import { scoreHypothesis } from "../lib/scoring/hypothesis" +import { updateBrain, exportBrain } from "../lib/brain/export" +import { restoreSerialized, serializeWorkspace, snapshotContainsSecrets } from "../lib/github-sync/serialization" +async function main(){const w=fixture();w.evidence=[evidence("support-1","support","interview")];await runSynthesis(w,"S011","fixture-run");let h=scoreHypothesis(hypothesis(),[],Date.parse("2026-10-04"));const initial=h.qualification;h={...h,supportingEvidence:["support-1"]};h=scoreHypothesis(h,w.evidence,Date.parse("2026-10-04"));w.evidence.push(evidence("support-2","support","analytics"));h={...h,supportingEvidence:["support-1","support-2"]};h=scoreHypothesis(h,w.evidence,Date.parse("2026-10-04"));w.hypotheses=[h];updateBrain(w,"promote");const promoted=h.qualification;w.researchResults.push({id:"result-1",taskId:"task-1",providerId:"fixture",title:"Failed behavioral test",excerpt:"No user completed the workflow",retrievedAt:"2026-10-04T00:00:00.000Z"});w.evidence.push(evidence("contra-1","contradict","experiment"),evidence("contra-2","contradict","usage"),evidence("contra-3","contradict","sales"),evidence("contra-4","contradict","support"));h={...h,contradictingEvidence:["contra-1","contra-2","contra-3","contra-4"]};w.hypotheses=[scoreHypothesis(h,w.evidence,Date.parse("2026-10-04"))];updateBrain(w,"demote");const brain=exportBrain(w),snapshot=serializeWorkspace(w),restored=restoreSerialized(snapshot);assert.equal(snapshotContainsSecrets(snapshot),false);assert.deepEqual(serializeWorkspace(restored).files,snapshot.files);assert.ok(brain.archive.some(x=>x.id==="h1"));console.log(JSON.stringify({path:[initial,promoted,w.hypotheses[0].qualification],synthesis:w.syntheses[0].id,brain:brain.manifest.counts,roundTripFiles:Object.keys(snapshot.files).length,secrets:false},null,2)) + +} +void main() diff --git a/spec.md b/spec.md new file mode 100644 index 0000000000000000000000000000000000000000..f2e107347b08d1e4ee3299d31c368b1c0a638008 --- /dev/null +++ b/spec.md @@ -0,0 +1,1275 @@ +Nodepad Continuous Evidence, Synthesis & Hypothesis Brain — Implementation Specification +Repository: `JsonLord/nodepad` +Target branch: `integration-evals` +Inspected branch head: `af41c021035624ed9651b2544e56142355f62cf8` +Merged donor repositories currently present: `product-eval/`, `find-me-saas/`, `founder-skills/`, `venture-analyst/` +Status: implementation specification +Primary objective: evolve Nodepad from a spatial AI note canvas plus vendored evaluation repositories into a continuously developing, evidence-grounded reasoning graph that researches, synthesizes, proposes hypotheses, attacks those hypotheses with further research, updates their qualification state, and exports a durable “brain” that later specialist agents from `agency-agents` can read and extend. +--- +0. Executive summary +Nodepad should become an Evidence-to-Hypothesis Operating System. +The core loop is: +```text +sources / observations / feedback / analytics / research + ↓ + EVIDENCE + ↓ + SYNTHESIS + (130+ typed lenses) + ↓ + PROPOSED HYPOTHESES + ↓ + GAP + CONTRADICTION ANALYSIS + ↓ + TARGETED RESEARCH TASKS + ↓ + NEW EVIDENCE + ↓ + HYPOTHESIS RE-SCORING + ↓ + GRAPH STATE UPDATE + ↓ + PROMOTE / CONTEST / DISQUALIFY + ↓ + DEVELOPING BRAIN + ↺ +``` +Nodepad's existing spatial note experience remains important, but the canonical model becomes a typed graph with explicit provenance. AI is allowed to propose interpretations, hypotheses, opportunities, value propositions and experiments, but it is not allowed to silently convert speculation into knowledge. +The four merged repositories should not remain isolated subprojects forever. Their strongest mechanisms should be extracted behind Nodepad-native services: +Product-Eval → evidence contract, evidence quality, themes, contradictions, confidence, decision readiness, design-test loop, calibration. +FindMeSaaS → mechanical validation harness, score recomputation, schema contracts, B2B/B2C lanes, pre-mortem / devil's-advocate discipline. +Founder Skills → assumption map, impact × uncertainty, pressure testing, pretotype design, explicit kill assumptions, pivot logic. +Venture Analyst → free/zero-key research collectors and source-adapter patterns for HN, Reddit, GitHub, web search and trends. +The implementation must preserve provenance to the donor code until replacement behavior is covered by tests. +--- +1. Vision +1.1 Product identity +Nodepad becomes: +> **A continuously developing evidence graph that turns research into hypotheses, turns hypotheses into tests, and turns test results into an auditable evolving brain.** +It has five engines: +```text +THINK → associations, implications, emergent hypotheses, Ghost/Gardener +KNOW → evidence, provenance, source quality, contradiction, confidence +TEST → research gaps, RATs, experiments, falsification thresholds +DECIDE → qualification state, priorities, value propositions, pivots, kill rules +VERIFY → schemas, arithmetic, graph integrity, source freshness, replayability +``` +1.2 Design principles +Evidence and hypothesis are different node classes. Never blur them. +Every non-user-authored conclusion must have provenance. +Contradictory evidence is retained, not smoothed away. +Qualification is continuous, not a one-time verdict. +Confidence and valence are separate. A hypothesis can be strongly disqualified with high confidence. +Synthesis is plural. The same evidence can legitimately produce many different strategic views. +AI proposes; deterministic code scores and validates. +Research is budgeted and rate-limit aware. Continuous does not mean abusive or wasteful. +Everything important is serializable to diff-friendly GitHub files. +Agency agents later operate through stable APIs, not by editing internal files ad hoc. +Human overrides are allowed but always logged. +The graph must be replayable from Git history and event logs. +Brain promotion is conservative. Qualified knowledge and speculative hypotheses are exported separately. +One shared graph, many views. Do not create parallel isolated stores for research, hypotheses, experiments and decisions. +The system should degrade gracefully offline. Existing local Nodepad import/export must continue to work. +--- +2. Current branch assessment +At `integration-evals`, the four donor repositories are currently merged safely as top-level directories. The root Nodepad package remains the original Next.js application and does not yet declare workspaces or wire the donor packages into runtime services. +This is desirable for the next step. Treat the donor directories as reference/vendor sources during extraction, not as four applications to execute side-by-side. +2.1 Preserve existing Nodepad strengths +Preserve: +Next.js / React / TypeScript application. +spatial tiling view. +type-based Kanban. +force-directed graph. +AI enrichment. +content classification. +inferred relationships. +Ghost / emergent thesis behavior. +project/workspace separation. +`.nodepad` import/export. +local browser mode during migration. +custom OpenAI-compatible base URL support. +2.2 Do not do these things +Do not rewrite the frontend framework. +Do not copy all donor prompts into the Nodepad prompt context. +Do not run 100+ independent LLM agents per heartbeat. +Do not make GitHub the only runtime datastore. +Do not store GitHub tokens or LLM keys in workspace backups. +Do not allow model output to directly mutate evidence scores without validation. +Do not delete donor directories until parity tests cover extracted behavior. +Do not require paid APIs for the core loop. +--- +3. Target repository architecture +Use a gradual extraction architecture. A full npm workspace conversion is optional in this implementation pass; internal modules may live under `lib/` first if that is safer for the current Next.js setup. +Recommended destination: +```text +nodepad/ +├── app/ +│ └── existing UI + new API routes +├── components/ +│ └── existing views + graph status overlays +├── lib/ +│ ├── graph/ +│ ├── evidence/ +│ ├── hypotheses/ +│ ├── synthesis/ +│ ├── research/ +│ ├── scoring/ +│ ├── heartbeat/ +│ ├── brain/ +│ ├── persistence/ +│ ├── github-sync/ +│ ├── agents/ +│ └── validation/ +├── worker/ +│ └── heartbeat.ts +├── data/ # gitignored runtime data by default +├── product-eval/ # donor/reference until extracted +├── find-me-saas/ # donor/reference until extracted +├── founder-skills/ # donor/reference until extracted +├── venture-analyst/ # donor/reference until extracted +├── docs/ +│ ├── DATA_MODEL.md +│ ├── SYNTHESIS_REGISTRY.md +│ ├── HEARTBEAT.md +│ ├── GITHUB_BACKUP.md +│ ├── BRAIN_EXPORT.md +│ └── AGENCY_AGENTS_INTEGRATION.md +└── spec.md +``` +--- +4. Canonical graph data model +4.1 Node kinds +Keep Nodepad's general-purpose content types for UI/editor semantics, but add a domain semantic kind. +```ts +export type KnowledgeNodeKind = + | "evidence" + | "observation" + | "source" + | "entity" + | "person" + | "organization" + | "segment" + | "persona" + | "job_to_be_done" + | "pain" + | "problem" + | "assumption" + | "hypothesis" + | "counter_hypothesis" + | "insight" + | "theme" + | "opportunity" + | "value_proposition" + | "positioning" + | "message" + | "objection" + | "feature" + | "solution_concept" + | "pricing_hypothesis" + | "channel_hypothesis" + | "experiment" + | "research_task" + | "result" + | "metric" + | "decision" + | "pivot" + | "risk" + | "constraint" + | "brain_knowledge"; +``` +4.2 Edge kinds +```ts +export type KnowledgeEdgeType = + | "supports" + | "contradicts" + | "corroborates" + | "derived_from" + | "implies" + | "depends_on" + | "explains" + | "challenges" + | "alternative_to" + | "tests" + | "produced" + | "invalidates" + | "strengthens" + | "weakens" + | "supersedes" + | "mentions" + | "applies_to" + | "belongs_to" + | "causes" + | "precedes" + | "blocks" + | "enables" + | "measures" + | "qualified_by" + | "disqualified_by"; +``` +Every inferred edge stores: +```ts +interface EdgeProvenance { + origin: "human" | "system" | "ai" | "agent" | "import" | "research"; + actorId?: string; + runId?: string; + confidence?: number; + explanation?: string; + createdAt: string; +} +``` +4.3 Evidence record +Preserve Product-Eval's additive data contract and extend it rather than renaming its fields. +```ts +interface EvidenceRecord { + id: string; + claim: string; + sourceType: string; + sourceRef?: string; + sourceUrl?: string; + sourceId?: string; + persona?: string[]; + funnelStage?: string; + impactType?: string; + strength: 1 | 2 | 3 | 4 | 5; + recencyDays?: number; + weight: number; + stale: boolean; + personId?: string | null; + accountId?: string | null; + corroboratedBy: string[]; + contradicts: string[]; + observedAt?: string; + ingestedAt: string; + provenance: Provenance; +} +``` +4.4 Hypothesis record +```ts +interface HypothesisRecord { + id: string; + workspaceId: string; + title: string; + statement: string; + hypothesisType: string; + + derivedFrom: string[]; + supportingEvidence: string[]; + contradictingEvidence: string[]; + dependentAssumptions: string[]; + alternativeHypotheses: string[]; + + impact: number; // 0..100 + uncertainty: number; // 0..100 + novelty: number; // 0..100 + strategicImportance: number; // 0..100 + + evidenceScore: number; // -100..100 + confidence: number; // 0..100 + qualification: HypothesisQualification; + + sourceDiversity: number; + firstPartyWeight: number; + behavioralWeight: number; + freshnessScore: number; + + status: + | "active" + | "stale" + | "superseded" + | "archived"; + + researchGaps: string[]; + nextResearchAt?: string; + lastEvaluatedAt?: string; + createdAt: string; + updatedAt: string; + + generation: { + method: string; + synthesisType?: string; + model?: string; + runId?: string; + generationConfidence?: number; + }; +} +``` +--- +5. Hypothesis qualification lifecycle +Do not use a binary validated/not-validated field. +5.1 Evidence score and qualification bands +Store a signed `evidenceScore` from `-100` to `+100`. Display a semantic band first and the exact score second. +Score Qualification Meaning +-100..-80 strongly_disqualified Multiple strong/independent sources or direct behavioral evidence refute it. +-79..-55 disqualified Evidence materially contradicts the hypothesis. +-54..-30 leaning_disqualified Contradictory evidence outweighs support, but uncertainty remains. +-29..-10 weakly_disfavored Mild negative evidence; do not kill yet. +-9..+9 unresolved Evidence balance insufficient or contradictory. ++10..+29 weak_signal Some support, low decision readiness. ++30..+49 potentially_qualified Meaningful evidence exists, but important gaps remain. ++50..+69 provisionally_qualified Good support from multiple sources; still subject to targeted falsification. ++70..+89 qualified Strong, diverse evidence supports it. ++90..+100 strongly_qualified Repeated independent and preferably behavioral/first-party confirmation. +Special overlays: +`unresearched` — hypothesis exists but has no meaningful evidence yet. +`contested` — material high-quality evidence exists on both sides. +`stale` — evidence freshness below policy. +`superseded` — a newer hypothesis/model replaces it. +`operationalized` — the hypothesis has become a working decision/product principle, but still retains its evidence state. +5.2 Confidence is separate +`confidence` measures how confident the system is in the qualification, not whether the hypothesis is positive. +Examples: +evidenceScore `-82`, confidence `91` → confidently false. +evidenceScore `+41`, confidence `34` → potentially promising but poorly established. +evidenceScore `0`, confidence `88`, contested overlay → genuinely mixed evidence. +5.3 Scoring inputs +Start with a deterministic configurable function built from: +support evidence weight. +contradiction evidence weight. +evidence strength 1..5. +recency/freshness. +independent source diversity. +duplicate-source penalty. +first-party data bonus. +behavioral evidence bonus over stated opinion. +direct measurement bonus. +contradiction severity. +sample/account diversity. +known source reliability. +explicit human override only as a separately logged field. +Do not let an LLM directly choose the final qualification band. +5.4 Research priority +For each hypothesis compute: +```text +research_priority = + strategic_importance + × uncertainty + × impact_if_wrong + × evidence_gap + × graph_dependency_factor + × freshness_need + ÷ expected_research_cost +``` +Normalize to 0..100. +`graph_dependency_factor` increases priority when many other hypotheses/decisions depend on the hypothesis. +--- +6. Continuous heartbeat +6.1 Requirement +The system must have a durable heartbeat worker. Browser timers alone are insufficient because research and re-evaluation must continue when no tab is open. +Implement: +```bash +npm run heartbeat +``` +and optionally: +```bash +npm run dev:all +``` +for local development. +The worker may be run as a sidecar/process in production. Also provide a protected API trigger so an external cron/Paperclip workflow can request a heartbeat safely. +6.2 Heartbeat phases +Every heartbeat run gets a stable `runId` and executes idempotently: +Acquire lease — prevent overlapping workers for the same workspace. +Read dirty state — new evidence, changed notes, completed research tasks, stale hypotheses. +Ingest — normalize newly arrived source material. +Deduplicate — semantic/content/source duplication detection. +Evidence-quality pass — strength, recency, source type, identity resolution, contradictions. +Theme refresh — update affected problem/theme clusters only. +Synthesis dispatch — execute eligible synthesis definitions from the registry. +Hypothesis generation — create proposals from new/changed syntheses. +Hypothesis matching — merge semantic duplicates; create `supersedes` or `alternative_to` instead of duplication. +Re-score affected hypotheses — deterministic evidence scoring. +Gap planner — identify evidence needed to move the most valuable hypotheses. +Research scheduler — enqueue targeted research tasks respecting budgets/rate limits. +Research execution — run only tasks due this heartbeat and only through healthy providers. +Result ingestion — convert research results into evidence nodes. +Second re-score — update hypotheses changed by the newly collected evidence. +Graph maintenance — update typed edges, centrality/dependency information and status overlays. +Brain promotion/demotion — evaluate what may enter/leave qualified brain knowledge. +Drift/staleness checks — schedule revisits to old important evidence. +GitHub backup — if canonical state is dirty and commit debounce permits. +Emit events/summary — heartbeat summary for UI, API, Agency Agents, Paperclip/n8n. +Release lease. +6.3 Heartbeat modes +```ts +type HeartbeatMode = + | "paused" + | "low" + | "normal" + | "research_intense"; +``` +Suggested defaults: +`low`: synthesis/score frequently, external research at most every 12h. +`normal`: synthesis/score every 15–30m while worker runs; external research due tasks every 2–6h. +`research_intense`: active experiments/research every 1–2h where provider limits allow. +All timings are configuration, not hard-coded assumptions. +6.4 Budget and rate-limit policy +Each research provider declares: +```ts +interface ResearchProviderPolicy { + id: string; + costClass: "local" | "free" | "free_quota" | "paid_optional"; + requestsPerWindow?: number; + windowSeconds?: number; + concurrency: number; + minIntervalMs?: number; + enabled: boolean; + health: "healthy" | "degraded" | "cooldown" | "disabled"; +} +``` +Default provider preference: +```text +local/self-hosted +→ free public/no-key +→ free hosted +→ free quota +→ paid optional (never silently) +``` +--- +7. Research provider layer +Extract provider adapters from Venture Analyst instead of invoking its scripts as an opaque application. +Initial adapters: +Hacker News Algolia stories. +Hacker News comments. +Reddit public endpoints where currently functional and policy-compliant. +GitHub issue search. +GitHub repository search. +DDGS/web search. +Google Trends/trendspyg when healthy. +Add adapter slots for later MCP/capabilities: +Dialog Reddit Research MCP. +Jordan Reddit MCP action layer. +LinkedIn MCP. +Idea Reality MCP. +Jina/Scrapling/Playwright crawler chain. +product analytics/CRM/support connectors. +Agency Agent supplied research packets. +Every provider returns a common `ResearchArtifact` shape and must not directly create qualified knowledge. +--- +8. Synthesis engine +8.1 Core idea +A synthesis is a typed transformation over one or more evidence/hypothesis/graph selections. +Do not implement each synthesis as a separate agent process. Implement one registry and one execution engine. +```ts +interface SynthesisDefinition { + id: string; + category: string; + name: string; + description: string; + inputKinds: KnowledgeNodeKind[]; + outputKinds: KnowledgeNodeKind[]; + minEvidence?: number; + requiresIndependentSources?: number; + freshnessPolicy?: string; + promptTemplate?: string; + deterministicPostProcessor?: string; + generatesHypotheses: boolean; + heartbeatEligible: boolean; + priority: number; +} +``` +Every synthesis result must carry: +```ts +interface SynthesisResult { + id: string; + synthesisType: string; + workspaceId: string; + title: string; + statement: string; + rationale: string; + derivedFrom: string[]; + supports?: string[]; + contradicts?: string[]; + unknowns: string[]; + suggestedResearch: string[]; + generatedHypotheses: string[]; + generationConfidence: number; + createdAt: string; + runId: string; +} +``` +8.2 Eligibility +A synthesis runs when: +one or more of its input nodes changed materially; or +its output is stale; or +a user/agent explicitly requests it; or +a heartbeat policy marks it due. +Use content hashes so unchanged inputs do not trigger repeated LLM calls. +8.3 130 initial synthesis definitions +The registry must ship with at least the following 130 definitions. These may initially share generic generation infrastructure; they do not need 130 custom implementations. +A. Evidence quality & epistemics — S001–S010 +S001 Evidence Sufficiency — determine whether the evidence set is sufficient to support any decision at all. +S002 Source Diversity — identify overreliance on one source class/community/account. +S003 Evidence Freshness — identify strategically important stale evidence. +S004 Contradiction Map — surface direct and indirect contradictions across sources. +S005 Corroboration Map — identify independently corroborated claims. +S006 Evidence Blind Spots — identify missing evidence classes needed for confidence. +S007 Anecdote-vs-Behavior Gap — contrast stated complaints/opinions with observed behavior. +S008 First-Party Evidence Gap — identify claims based only on public/secondary evidence. +S009 Source Bias Hypothesis — infer how source selection may distort the apparent conclusion. +S010 Confidence Ceiling — state the highest defensible confidence possible from current evidence. +B. Customer, segment & problem synthesis — S011–S020 +S011 Problem Cluster — cluster evidence into underlying problems rather than topics. +S012 Root Cause Candidate — derive plausible root causes for a repeated pain. +S013 Pain Intensity — infer severity and cost of the problem. +S014 Pain Frequency — infer how often the problem occurs. +S015 Pain Urgency — infer forcing functions and time sensitivity. +S016 Segment Split — identify customer segments experiencing meaningfully different versions of the problem. +S017 Persona Role Map — distinguish user, buyer, champion, blocker and administrator. +S018 High-Pain Segment — identify the segment with highest evidenced severity/frequency. +S019 Problem Context — identify contexts/triggers in which the problem becomes salient. +S020 Problem Consequence Chain — map pain → operational effect → business effect → emotional/social effect. +C. Jobs, behavior & adoption — S021–S030 +S021 JTBD Statement — derive job-to-be-done candidates from evidence. +S022 Current Workflow — reconstruct how users solve the job today. +S023 Workaround Map — classify manual, software, service, spreadsheet, social and non-consumption workarounds. +S024 Non-Consumption Reason — explain why users do nothing despite a stated pain. +S025 Switching Cost — identify procedural, data, social, contractual and cognitive switching costs. +S026 Behavior Change Burden — estimate how much new behavior adoption requires. +S027 Trigger-to-Action Chain — identify what event causes a user to seek a solution. +S028 Habit/Retention Driver — identify repeated triggers/rewards that could sustain use. +S029 Adoption Blocker — identify friction preventing trial or continued use. +S030 Abandonment Hypothesis — explain why users may stop using an existing/imagined solution. +D. Value proposition, positioning & messaging — S031–S040 +S031 Core Value Proposition — derive the most evidence-supported promise. +S032 Avoided-Loss Proposition — frame value as cost/risk/delay avoided. +S033 Gain Proposition — frame value as measurable upside achieved. +S034 Emotional Value Proposition — identify confidence/status/stress/reassurance value. +S035 Persona-Specific Proposition — adapt proposition by user/buyer/champion role. +S036 Positioning Statement — derive category, audience, alternative and differentiated value. +S037 Category Hypothesis — infer which category framing makes the product easiest to understand/buy. +S038 Differentiation Hypothesis — identify differences customers appear to value, not just product differences. +S039 Headline Hypotheses — generate testable landing-page headline variants grounded in customer language. +S040 CTA Hypotheses — derive calls-to-action appropriate to evidence/readiness and customer intent. +E. Product, feature & solution synthesis — S041–S050 +S041 Feature Opportunity — derive features directly from evidenced problems. +S042 Feature Anti-Opportunity — identify requested features not justified by evidence. +S043 Minimum Valuable Experience — derive the smallest experience that could deliver the core outcome. +S044 Concierge Version — identify what can be manually delivered before software exists. +S045 Wizard-of-Oz Version — identify which automated-looking behavior can safely be fulfilled manually for testing. +S046 Fake-Door Concept — derive the smallest credible demand probe. +S047 Workflow Integration Point — identify where in the customer's current workflow the product should enter. +S048 Product Boundary — define what the product should explicitly not attempt to solve. +S049 Feature Dependency Graph — derive feature prerequisites and dependent outcomes. +S050 Solution Alternative Set — generate materially different solution approaches to the same problem. +F. Pricing, monetization & economics — S051–S060 +S051 Willingness-to-Pay Hypothesis — infer testable WTP ranges and confidence. +S052 Pricing Metric Hypothesis — seat, usage, project, outcome, asset, account, device, etc. +S053 Packaging Hypothesis — identify coherent bundles/tier boundaries. +S054 Freemium Suitability — reason whether free usage creates conversion/distribution value or cost leakage. +S055 Trial Design Hypothesis — infer what a trial must expose to demonstrate value. +S056 Cost-to-Serve Risk — infer expensive workflows or human/service burden. +S057 Gross-Margin Hypothesis — estimate major margin drivers needing validation. +S058 CAC Ceiling Hypothesis — derive maximum tolerable acquisition cost from current economics. +S059 LTV Driver Hypothesis — identify behaviors and business variables most likely to drive lifetime value. +S060 Monetization Failure Mode — identify reasons real demand may fail to become revenue. +G. Competition, alternatives & market structure — S061–S070 +S061 Direct Competitor Map — synthesize direct competitors and key evidence. +S062 Indirect Competitor Map — synthesize adjacent products solving the same job differently. +S063 Substitute Map — spreadsheets, services, internal processes, community, doing nothing. +S064 Competitor Complaint Cluster — aggregate repeated negative review/problem themes. +S065 Competitor Strength Cluster — identify incumbent strengths users value and should not be ignored. +S066 Positioning Gap — infer underserved combinations of audience/problem/experience/price/trust. +S067 Incumbent Indifference — identify opportunities too small/awkward for incumbents to prioritize. +S068 Incumbent Response Risk — estimate how easily an incumbent could neutralize the differentiation. +S069 Market Saturation Hypothesis — infer whether competition indicates demand, commoditization or over-supply. +S070 Precedent Failure Lesson — extract lessons from similar products/startups that failed or shut down. +H. Distribution, channel & growth — S071–S080 +S071 Channel Fit — rank channels by evidence quality and reachability. +S072 Evidence-Per-Euro Channel — rank channels specifically by validation signal per cost, not scale. +S073 Community Fit — identify communities in which the problem naturally appears. +S074 Search Intent — infer search queries and intent stages indicating active demand. +S075 Content Angle — derive content topics likely to produce useful validation signals. +S076 Outreach Angle — derive conversation openers based on evidenced pains rather than sales copy. +S077 Referral Loop Hypothesis — identify whether use naturally exposes/invites others. +S078 Virality Constraint — identify what prevents organic sharing or collaboration loops. +S079 Distribution Edge — identify founder/company assets that reduce acquisition difficulty. +S080 Channel Failure Mode — identify why an apparently attractive channel may not produce qualified users. +I. Trust, risk, regulatory & feasibility — S081–S090 +S081 Trust Barrier — derive why customers may not trust the solution/provider. +S082 Proof Requirement — identify proof, certification, demo or social validation needed for adoption. +S083 Privacy Concern — synthesize data/privacy objections. +S084 Security Concern — synthesize security objections and required assurances. +S085 Regulatory Constraint — identify regulation/licensing/compliance hypotheses requiring validation. +S086 Platform Dependency Risk — detect dependency on APIs/platforms/providers that could invalidate the model. +S087 Technical Feasibility Risk — identify evidence gaps around whether the solution can actually work. +S088 Operational Feasibility Risk — identify human/process scaling constraints. +S089 Reputation Risk — identify trust/reputation failure paths from the proposed behavior. +S090 Abuse/Fraud Risk — derive foreseeable misuse/fraud incentives that affect product viability. +J. Research & experiment design — S091–S100 +S091 Riskiest Assumption — select the highest-impact/highest-uncertainty load-bearing assumption. +S092 Cheapest Disconfirming Test — identify the cheapest test capable of proving the hypothesis wrong. +S093 Interview Test — derive Mom-Test-style questions that avoid pitching and leading. +S094 Fake-Door Test — derive audience, message, instrumentation and pass/fail threshold. +S095 Pricing Test — derive behavioral pricing experiment and threshold. +S096 Concierge Test — derive manual test, sample, duration and success/failure threshold. +S097 Instrumentation Gap — identify what product analytics/event tracking is needed to answer a hypothesis. +S098 Source Acquisition Plan — identify which evidence sources can most efficiently close the gap. +S099 Discriminating Experiment — design a test that differentiates between two competing hypotheses. +S100 Replication Test — design an independent repeat/segment/source test for an apparently strong result. +K. Reporting, narrative & communication — S101–S110 +S101 Executive Focus Points — identify what a decision-maker report should emphasize. +S102 Evidence Narrative — turn evidence into a chronological/causal story without overstating certainty. +S103 Customer Voice Summary — summarize recurring language, preserving provenance. +S104 Key Quotes/Examples Selection — choose representative examples without cherry-picking. +S105 Decision Memo Focus — identify the facts that should determine build/test/pivot/kill decisions. +S106 Investor Narrative Focus — derive market/problem/proof/risk points relevant to an investor audience. +S107 Sales Narrative Focus — derive pain/proof/outcome points relevant to buyer conversations. +S108 Research Report Focus — identify strongest findings, caveats and next questions. +S109 Dashboard KPI Focus — derive which metrics should be monitored because they resolve current hypotheses. +S110 Unknowns Disclosure — produce the explicit “what we still do not know” section for any report. +L. Strategy, portfolio & decision synthesis — S111–S120 +S111 Build/Test/Pivot/Kill Recommendation — deterministic/structured decision backed by evidence state. +S112 Pivot Candidate — generate a one- or two-variable pivot preserving strong dimensions. +S113 Kill Criterion — define evidence that should terminate the idea/hypothesis. +S114 Scale Criterion — define evidence required before allocating more resources. +S115 Venture Mode Fit — infer solo/indie/B2B/venture-scale operating assumptions and mismatches. +S116 Resource Fit — map founder/team assets and constraints to the opportunity. +S117 Portfolio Priority — rank multiple hypotheses/ventures by evidence, upside, cost and uncertainty. +S118 Expected Learning Value — rank actions by expected decision information generated per unit cost/time. +S119 Strategic Dependency — identify hypotheses that gate many downstream decisions. +S120 Opportunity Cost — identify what pursuing this option prevents or delays elsewhere. +M. Emergent / second-order / graph-native synthesis — S121–S130 +S121 Cross-Cluster Bridge — find non-obvious connections between evidence clusters (Nodepad Ghost-style). +S122 Second-Order Hypothesis — derive what follows if a first-order hypothesis is true. +S123 Third-Order Business Consequence — derive downstream business implications of a supported second-order effect. +S124 Alternative Explanation — generate plausible explanations for the same observation. +S125 Hidden Common Cause — infer a latent cause that could explain multiple problems at once. +S126 Tension/Paradox — surface evidence pairs that appear true but pull strategy in opposite directions. +S127 Adjacent Opportunity — infer an adjacent product/service opportunity from existing pain/evidence. +S128 New Venture Candidate — identify when multiple evidence clusters imply a distinct venture rather than a feature. +S129 Weak-Signal Emergence — identify low-volume but fast-growing or strategically novel signals. +S130 World-Model Update — synthesize the most important change to the current “brain” caused by new evidence. +8.4 Synthesis status +Each synthesis result receives: +`draft_ai` — generated, not yet validated. +`supported` — adequately traceable to current evidence. +`contested` — significant contradictory evidence. +`stale` — inputs changed/freshness expired. +`superseded` — replaced by newer synthesis. +`promoted` — accepted as a stable graph insight/brain candidate. +--- +9. Hypothesis Gardener +Build a background `HypothesisGardener` that runs after synthesis. +It must ask graph-native questions programmatically: +Which strong evidence nodes have no hypothesis explaining them? +Which important hypotheses have no counter-hypothesis? +Which hypotheses depend on the same weak assumption? +Which hypotheses share evidence but make different predictions? +Which qualified hypotheses have not been retested recently? +Which disqualified hypotheses still have dependent decisions/features? +Which high-centrality assumptions are under-researched? +Which value propositions have insufficient evidence? +Which pain clusters have no proposed solution/value proposition? +Which product ideas are based mainly on weak opinion evidence? +Which contradictions can be resolved by one discriminating experiment? +Which segment-specific findings are being incorrectly generalized? +Which weak signals are growing rapidly? +Which graph areas have become stale? +Which evidence changed enough to require a world-model update? +The Gardener may propose nodes and edges, but it must not directly promote brain knowledge. +--- +10. Research task lifecycle +```ts +interface ResearchTask { + id: string; + workspaceId: string; + hypothesisIds: string[]; + question: string; + objective: "support" | "disconfirm" | "discriminate" | "refresh" | "discover"; + preferredSources: string[]; + requiredSourceDiversity: number; + maxCostClass: "local" | "free" | "free_quota" | "paid_optional"; + status: "queued" | "running" | "blocked" | "complete" | "failed" | "cancelled"; + priority: number; + notBefore?: string; + attempts: number; + maxAttempts: number; + createdBy: string; + runId?: string; +} +``` +Research results become evidence; they never directly set hypothesis status. +For every important hypothesis, schedule falsification-biased research as well as supportive research. +--- +11. Brain model +11.1 Brain layers +Export three explicitly separate layers: +```text +BRAIN +├── knowledge/ strongly/qualified, sufficiently confident propositions +├── hypotheses/ active, unresolved, potentially/provisionally qualified models +└── archive/ disqualified, superseded and historical hypotheses +``` +Do not delete disqualified hypotheses; they are valuable negative knowledge. +11.2 Promotion policy +Default promotion to `brain/knowledge` requires configurable thresholds such as: +qualification `qualified` or `strongly_qualified`. +confidence >= 70. +source diversity >= 2 independent source types. +no unresolved high-strength contradiction, unless marked `contested` and intentionally represented as such. +not stale. +provenance complete. +Demotion occurs when new evidence drops a previously promoted proposition below threshold. Demotion must be append-only in the event log and visible in graph history. +11.3 Brain export +Generate: +```text +brain/ +├── manifest.json +├── summary.md +├── graph.json +├── knowledge/ +│ └── .md +├── hypotheses/ +│ └── .md +├── archive/ +│ └── .md +├── entities/ +│ └── .md +├── decisions/ +│ └── .md +└── changelog.ndjson +``` +`summary.md` must explain what changed since the previous brain snapshot. +This format is designed to be consumed later by BrainAPI, Agency Agents, RAG/indexers, or other agent memory systems. +--- +12. GitHub continuous backup and restore +12.1 Goal +Nodepad workspaces must be continuously serializable into a normal Git repository as human-readable, diffable files. GitHub is a durable mirror/version store, not a secret store. +12.2 Runtime-to-Git layout +Recommended repository layout per workspace: +```text +nodepad-workspaces/ +└── / + ├── manifest.json + ├── context.md + ├── graph/ + │ ├── nodes/ + │ │ └── .md + │ ├── edges.ndjson + │ └── index.json + ├── evidence/ + │ └── .md + ├── syntheses/ + │ └── .md + ├── hypotheses/ + │ └── .md + ├── experiments/ + │ └── .md + ├── research/ + │ ├── tasks/ + │ └── results/ + ├── decisions/ + │ └── .md + ├── brain/ + │ └── ...brain export... + ├── runs/ + │ └── .json + ├── events/ + │ └── events.ndjson + └── schema-version.txt +``` +12.3 File conventions +Markdown files use YAML front matter for machine fields and body text for human-readable explanation. +High-volume append-only streams use NDJSON. +Index/manifest files use JSON. +Never store tokens, API keys, cookies, OAuth refresh tokens or provider secrets. +Store source URLs/references only when policy permits. +Add a `schemaVersion` to every serialized object/file format. +12.4 GitHub sync adapter +Create a storage adapter interface: +```ts +interface BackupRemote { + status(): Promise; + pushWorkspace(snapshot: WorkspaceSnapshot): Promise; + listSnapshots(workspaceId: string): Promise; + restoreWorkspace(workspaceId: string, ref: string): Promise; + diffWorkspace(workspaceId: string, fromRef: string, toRef: string): Promise; +} +``` +Initial implementation: GitHub REST or local git, whichever best fits runtime. +Configuration should support: +```text +NODEPAD_GITHUB_TOKEN +NODEPAD_GITHUB_REPOSITORY=owner/repo +NODEPAD_GITHUB_BRANCH=nodepad-backup +NODEPAD_GITHUB_PATH=nodepad-workspaces +NODEPAD_GITHUB_SYNC=true +``` +Do not expose these values to the client bundle. +12.5 Commit policy +Heartbeat checks whether the workspace is dirty. If so: +debounce commits (suggested minimum 10 minutes; configurable). +batch all workspace changes into one commit. +commit only after local state validates. +commit message: +```text +nodepad(): heartbeat — +12 evidence, 4 hypotheses updated, 1 promoted +``` +record resulting commit SHA in local heartbeat history. +never force push automatically. +on non-fast-forward/conflict: stop sync, mark `sync_conflict`, preserve local data, require merge/reconcile or use a new recovery branch. +12.6 Restore +Expose UI/API operations: +restore latest backup. +restore by commit SHA. +restore by tag/snapshot. +preview diff before destructive restore. +restore into a new workspace by default. +explicit “replace current workspace” requires confirmation and creates a pre-restore local snapshot first. +Restore procedure: +Fetch selected ref. +Validate schema/version. +Run migrations into current schema. +Run full graph/data validation. +Materialize into a staging workspace. +Compare counts/hashes. +Activate only after validation succeeds. +Record restore event with source commit SHA. +12.7 Snapshot tags +Allow meaningful milestones to create tags or named snapshot metadata: +`pre-pivot` +`post-customer-interviews` +`qualified-v1` +`pre-major-experiment` +--- +13. Persistence layer +Current browser `localStorage` remains supported for legacy/offline mode, but continuous heartbeat requires server-side persistence. +Create a persistence abstraction: +```ts +interface NodepadStore { + getWorkspace(id: string): Promise; + saveWorkspace(workspace: Workspace): Promise; + listNodes(workspaceId: string, filter?: NodeFilter): Promise; + upsertNode(node: KnowledgeNode): Promise; + upsertEdge(edge: KnowledgeEdge): Promise; + appendEvent(event: DomainEvent): Promise; + acquireLease(workspaceId: string, owner: string, ttlMs: number): Promise; + releaseLease(workspaceId: string, owner: string): Promise; +} +``` +For this implementation pass, prefer the simplest durable backend that is safe for the existing repository (e.g. filesystem JSON/Markdown plus indexes or SQLite). Keep the interface backend-neutral so PostgreSQL can be added later. +Legacy `.nodepad` import must migrate old blocks into canonical nodes/edges. +--- +14. Graph UX changes +14.1 Visual qualification state +The graph must visually distinguish hypothesis state without relying solely on color. Use a combination of: +badge/icon. +border style. +status label. +optional color. +Examples: +strongly disqualified → strike/stop marker. +disqualified → negative badge. +unresolved → hollow/question badge. +potentially qualified → rising badge. +provisionally qualified → check-outline. +qualified → solid check. +strongly qualified → double check/star. +contested → split/versus badge. +stale → clock badge. +14.2 Graph detail panel +For any hypothesis show: +statement. +qualification band + evidence score. +confidence. +supporting evidence. +contradicting evidence. +source diversity. +assumptions/dependencies. +alternative hypotheses. +evidence gaps. +latest research tasks. +what would disqualify it. +what would qualify it further. +dependent decisions/features/value propositions. +complete history. +14.3 Provenance trace +One click must answer: +> Why does this node exist? +Traverse: +```text +node → synthesis → hypotheses/evidence → original source/result +``` +14.4 Heartbeat panel +Add a compact status surface: +last heartbeat. +next heartbeat. +new evidence count. +syntheses generated. +hypotheses created/changed. +hypotheses promoted/demoted/disqualified. +research tasks queued/running/blocked. +GitHub backup status + last commit SHA. +--- +15. Validation and tests +Extract ideas from both Product-Eval's conformance checks and FindMeSaaS's validation harness. +Required validators: +```text +validateNode +validateEdge +validateEvidence +validateTheme +validateSynthesis +validateHypothesis +validateExperiment +validateDecision +validateBrainPromotion +validateWorkspace +validateGitSnapshot +validateWorkflowContracts +``` +Test invariants: +Every edge endpoint exists. +Every evidence reference resolves. +Every hypothesis score can be recomputed. +Qualification band matches evidence score unless explicit logged override exists. +No `brain_knowledge` node is promoted below configured thresholds. +Contradicting evidence cannot be silently dropped during synthesis. +Research result cannot directly mutate final hypothesis band. +Heartbeat is idempotent for unchanged inputs. +Duplicate heartbeat execution cannot create duplicate hypotheses/evidence. +GitHub serialization → restore → serialization is semantically stable. +Legacy `.nodepad` import retains block text and relationships. +No secret-like configuration is included in Git backup exports. +A stale qualified hypothesis is visibly marked and scheduled for refresh. +A new contradiction can demote previously qualified brain knowledge. +Restore failure leaves current workspace untouched. +Add fixtures covering positive, negative, contested, stale and contradictory hypotheses. +--- +16. Agency Agents readiness +Nodepad should be the shared reasoning/evidence substrate; Agency Agents should remain a separate workforce/role repository. +16.1 Stable API surface +Add versioned server service/API interfaces now, even if some routes initially wrap local services: +```text +GET /api/v1/workspaces +GET /api/v1/workspaces/:id/graph +GET /api/v1/workspaces/:id/context + +POST /api/v1/workspaces/:id/evidence +GET /api/v1/workspaces/:id/evidence + +POST /api/v1/workspaces/:id/hypotheses +GET /api/v1/workspaces/:id/hypotheses +GET /api/v1/workspaces/:id/hypotheses/:hypothesisId + +POST /api/v1/workspaces/:id/syntheses/run +POST /api/v1/workspaces/:id/heartbeat + +GET /api/v1/workspaces/:id/research/tasks +POST /api/v1/workspaces/:id/research/tasks/:taskId/results + +GET /api/v1/workspaces/:id/brain +POST /api/v1/workspaces/:id/brain/export + +GET /api/v1/workspaces/:id/events + +POST /api/v1/workspaces/:id/github/backup +POST /api/v1/workspaces/:id/github/restore +``` +16.2 Agent attribution +All agent writes carry: +```ts +interface ActorContext { + actorType: "human" | "agent" | "system"; + actorId: string; + role?: string; + sessionId?: string; + agencyTaskId?: string; + correlationId?: string; +} +``` +Agents must not spoof human overrides. +16.3 Idempotency and concurrency +Agent-facing mutation endpoints accept an `Idempotency-Key`. +Use optimistic versioning or expected revision fields to prevent silent overwrites. +16.4 Events +Emit domain events suitable for later Agency Agents/Paperclip/n8n consumption: +```text +evidence.added +synthesis.generated +hypothesis.created +hypothesis.updated +hypothesis.qualified +hypothesis.disqualified +hypothesis.contested +hypothesis.stale +research_task.queued +research_task.completed +experiment.ready +brain.promoted +brain.demoted +github.backup_completed +github.restore_completed +heartbeat.completed +``` +Provide an SSE/event-feed route later or in this pass if low risk. +16.5 Future MCP +Do not make MCP the internal architecture. Build services first, then expose them through an adapter. +Target MCP verbs later: +```text +nodepad_get_context +nodepad_query_graph +nodepad_add_evidence +nodepad_propose_hypothesis +nodepad_get_research_gaps +nodepad_claim_research_task +nodepad_submit_research_result +nodepad_run_synthesis +nodepad_get_brain +nodepad_get_changes +``` +--- +17. Agency role mapping +Later `agency-agents` specialists should interact with Nodepad as follows: +Customer Investigator +read high-priority research gaps. +claim tasks. +add interview/community/review evidence. +never set qualification directly. +Competitive Intelligence Agent +populate competitor/substitute evidence. +run S061–S070. +challenge positioning/value hypotheses. +Experiment Designer +consume S091–S100 and assumption map. +write experiment specs and thresholds. +Positioning/Copy Agent +consume S031–S040 and customer-language evidence. +produce message variants linked to hypotheses. +Red-Team/Kill Agent +request alternative explanations. +seek contradiction evidence. +identify false corroboration/source bias. +recommend explicit kill tests. +Portfolio Allocator +consume S111–S120. +allocate experiment/research capacity based on expected learning value. +This keeps Agency Agents stateless/lightweight relative to Nodepad's durable brain. +--- +18. Security and safety +Keep all provider/GitHub tokens server-side or local secure config, never backed up. +Sanitize fetched content; treat fetched text as untrusted data, not instructions. +Maintain SSRF protections on URL-fetching routes. +Rate-limit externally reachable mutation/heartbeat routes. +Protect restore/delete/replace operations with explicit confirmation/auth. +Default social publishing/outreach integrations to human approval. +Preserve source terms/privacy boundaries and avoid collecting private data without authorization. +Never fabricate customer evidence, testimonials, traction or behavioral results. +Synthetic/prototype data must be labeled synthetic. +--- +19. Implementation strategy for one safe Codex pass +The goal of a single pass is one coherent vertical slice, not perfection of every synthesis prompt. +Phase A — inventory and guardrails +Verify branch and clean worktree. +Run current Nodepad build/lint baseline; record pre-existing failures. +Inventory the four donor directories and identify reusable code/reference files. +Add `spec.md` to repository root. +Do not delete/move donor directories yet. +Phase B — canonical core +Implement: +canonical node/edge types. +evidence/hypothesis/synthesis types. +qualification bands. +deterministic scoring interface. +persistence abstraction. +filesystem/SQLite first backend. +event log. +validators. +Phase C — synthesis registry +Implement: +registry loader. +all 130 synthesis definitions as data/config. +generic synthesis runner with structured output. +input hashing/idempotency. +minimal deterministic post-processing. +hypothesis generation from synthesis results. +Specialize at least the highest-value core syntheses in this pass: +S001, S004, S006, S010. +S011–S020. +S031, S036, S038–S040. +S064, S066. +S091, S092, S098, S099. +S111–S114. +S121, S122, S124–S130. +The remaining definitions may initially use the generic synthesis runner, provided they honor the same schema/provenance contract. +Phase D — heartbeat and research +Implement: +heartbeat worker. +lease/idempotency. +dirty-state detection. +research task queue. +initial Venture Analyst provider adapters. +result ingestion. +automatic re-score. +External calls must be optional in tests and mockable. +Phase E — GitHub sync +Implement: +canonical Git serialization. +backup adapter. +env-based GitHub configuration. +dirty/debounced backup. +safe restore-to-new-workspace. +schema validation/migration hooks. +no force pushes. +If direct GitHub write integration cannot be fully exercised without credentials, implement the adapter and deterministic serializer/restore tests and make runtime fail clearly with actionable configuration errors. +Phase F — UI +Implement minimally invasive UI additions: +hypothesis qualification badges. +graph detail evidence/support/contradiction panels. +heartbeat status. +GitHub backup status. +manual “Run heartbeat” action. +manual “Backup now”. +restore browser/preview if safe; otherwise API/CLI restore plus documented UI follow-up. +Do not redesign the entire Nodepad visual system in this pass. +Phase G — Agency-ready API +Implement stable `/api/v1` service routes for at least: +graph/context read. +evidence write/read. +hypothesis read. +synthesis run. +heartbeat run/status. +research task list/result submit. +brain export. +GitHub backup/restore. +Use shared service functions so a later MCP adapter does not duplicate business logic. +Phase H — tests and documentation +Add: +unit tests for scoring/status bands. +graph integrity tests. +synthesis registry completeness test (`>=130`). +heartbeat idempotency test. +contradiction/demotion test. +brain promotion/demotion test. +serialization round-trip test. +GitHub restore staging/validation test. +legacy `.nodepad` compatibility test. +secret exclusion test. +Update README and add focused docs listed earlier. +--- +20. Acceptance criteria +The implementation is acceptable when all of the following are true: +Nodepad still builds and existing classic views remain functional. +Four donor directories remain intact unless an extracted file is proven redundant and deletion is explicitly documented. +Canonical graph supports evidence, hypotheses, syntheses, experiments, results and decisions. +Evidence provenance and contradictions are first-class. +At least 130 synthesis definitions exist in a registry. +A generic runner can execute every registered synthesis through one contract. +Core synthesis definitions have specialized logic/prompts where specified. +New synthesis results can propose hypotheses. +Hypotheses have separate signed evidence score and confidence. +Qualification supports at least the 10 positive/negative bands described above plus contested/stale/unresearched overlays. +New evidence automatically re-scores affected hypotheses. +A strong contradiction can demote a previously qualified hypothesis. +Hypothesis Gardener proposes missing/counter/second-order hypotheses. +Heartbeat can run repeatedly without duplicating unchanged graph state. +Heartbeat can queue targeted research for high-value evidence gaps. +At least HN, GitHub and web research adapters are integrated or cleanly wrapped from Venture Analyst; Reddit/Trends may degrade gracefully if unavailable. +Every research result goes through evidence ingestion before influencing a hypothesis. +Brain export separates knowledge, active hypotheses and disqualified/archive material. +Brain promotion/demotion is deterministic and audited. +Workspace can serialize to diff-friendly GitHub files. +GitHub backup is debounced, non-force-pushing and excludes secrets. +Restore by Git ref/commit is validated in staging before activation. +Serialization→restore round-trip passes tests. +API routes expose shared services without duplicating scoring logic. +Agent writes have actor attribution/idempotency fields. +Existing `.nodepad` import/export remains usable or is migrated with explicit backwards-compatible handling. +Test suite covers graph integrity, scores, registry, heartbeat, brain, backup/restore and secret exclusion. +README documents how to start Nodepad + heartbeat worker and how to configure GitHub backup. +--- +21. Recommended configuration +Example `.env.example` additions: +```bash +# Persistence +NODEPAD_DATA_DIR=./data +NODEPAD_STORE=file + +# Heartbeat +NODEPAD_HEARTBEAT_ENABLED=true +NODEPAD_HEARTBEAT_INTERVAL_SECONDS=900 +NODEPAD_HEARTBEAT_MODE=normal +NODEPAD_RESEARCH_ENABLED=true +NODEPAD_MAX_RESEARCH_TASKS_PER_HEARTBEAT=5 +NODEPAD_MAX_COST_CLASS=free + +# GitHub backup +NODEPAD_GITHUB_SYNC=false +NODEPAD_GITHUB_REPOSITORY= +NODEPAD_GITHUB_BRANCH=nodepad-backup +NODEPAD_GITHUB_PATH=nodepad-workspaces +NODEPAD_GITHUB_TOKEN= +NODEPAD_GITHUB_MIN_COMMIT_INTERVAL_SECONDS=600 + +# API +NODEPAD_API_KEY= +``` +Paid provider escalation must default to disabled. +--- +22. Suggested graph filters/views +Add saved graph filters that later agents can also query: +Qualified brain. +Potentially qualified. +Contested. +Disqualified. +Stale knowledge. +Highest research priority. +High-centrality weak assumptions. +Unresolved contradictions. +New since last heartbeat. +New since last Git backup. +New since last brain snapshot. +Value propositions and their evidence. +Experiments and what they test. +Decisions and their dependency chains. +Agency-agent authored nodes. +--- +23. Event-sourced development history +Every important state transition should append a domain event, including old/new values where reasonable. +Example: +```json +{ + "id": "evt_...", + "type": "hypothesis.qualification_changed", + "workspaceId": "w1", + "entityId": "H23", + "at": "2026-10-04T21:00:00Z", + "actor": {"type": "system", "id": "heartbeat"}, + "runId": "hb_...", + "before": {"evidenceScore": 46, "qualification": "potentially_qualified"}, + "after": {"evidenceScore": 71, "qualification": "qualified"}, + "causedBy": ["E91", "E92"] +} +``` +This event history is part of the developing brain: it records not only what Nodepad believes now, but how those beliefs evolved. +--- +24. Calibration +Preserve Product-Eval's calibration principle: when real outcomes become known, compare prior qualification/confidence to outcomes and tune configurable weights rather than editing history. +Calibration may adjust: +evidence strength weights. +recency decay. +source-type multipliers. +diversity bonus. +behavioral evidence bonus. +qualification thresholds. +Calibration changes must be versioned, documented and applied prospectively/recomputed explicitly. +--- +25. Success state +When this work is complete, Nodepad should be able to do the following without Agency Agents yet: +Receive a batch of customer feedback, URLs, notes or research results. +Normalize them into evidence. +Generate multiple typed syntheses from the same evidence. +Propose new hypotheses and counter-hypotheses. +Show why each hypothesis exists. +Calculate current qualification and confidence. +Identify the evidence most likely to change the decision. +Research that gap using free-first providers. +Ingest new evidence and automatically re-evaluate the hypothesis. +Visibly promote, demote, contest or disqualify hypotheses in the graph. +Grow second-order hypotheses and adjacent opportunities from the updated graph. +Promote robust propositions into a separate brain knowledge layer. +Preserve disqualified hypotheses as negative knowledge. +Continuously back up the workspace and brain to GitHub as normal files. +Restore safely from a previous Git commit. +Expose the graph, research tasks, evidence, syntheses and brain through stable APIs. +Later allow Agency Agents to become specialized workers operating on this shared brain rather than each maintaining separate truth. +That is the intended Nodepad vision. diff --git a/tests/auth.test.ts b/tests/auth.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1bb8444ed5addf91ac8a200c3cddc49a5856836 --- /dev/null +++ b/tests/auth.test.ts @@ -0,0 +1,145 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs" +import { tmpdir } from "node:os" +import path from "node:path" +import { NextRequest } from "next/server" +import { createSession, validateSession, invalidateSession, isAuthenticated, requireAuth, SESSION_COOKIE, SESSION_MAX_AGE, sessionCookieOptions, timingSafeMatch, clientIp, loginRetryAfter } from "../lib/auth/server" +import { POST as login } from "../app/api/auth/login/route" +import { POST as logout } from "../app/api/auth/logout/route" +import { GET as status } from "../app/api/auth/status/route" +import { GET as dataGet } from "../app/api/v1/[...path]/route" +import { GET as modelsGet } from "../app/api/v1/llm/models/route" +import { proxy } from "../proxy" +import { serializeWorkspace } from "../lib/github-sync/serialization" +import { fixture } from "./helpers" +import { loginReducer } from "../components/login-view" + +const request = (url: string, init: NonNullable[1]> = {}) => new NextRequest(`http://localhost${url}`, init) +const loginRequest = (accessKey: string, ip = "192.0.2.10") => request("/api/auth/login", { + method: "POST", headers: { "Content-Type": "application/json", "X-Forwarded-For": ip, Origin: "http://localhost" }, body: JSON.stringify({ accessKey }), +}) + +// Configuration mutations are scoped; tests in other files run in separate workers. +test("Nodepad human sessions and machine bearer authentication", async t => { + const names = ["NODEPAD_LOGIN_KEY", "NODEPAD_API_KEY", "NODEPAD_AUTH_DIR", "NODEPAD_TRUST_PROXY", "NODE_ENV", "OPENAI_URL", "OPENAI_MODEL", "OPENAI_API"] + const saved = Object.fromEntries(names.map(name => [name, process.env[name]])) + const mutableEnv = process.env as Record + const directory = mkdtempSync(path.join(tmpdir(), "nodepad-auth-test-")) + process.env.NODEPAD_LOGIN_KEY = "human-test-key" + process.env.NODEPAD_API_KEY = "machine-test-key" + process.env.NODEPAD_AUTH_DIR = directory + process.env.NODEPAD_TRUST_PROXY = "true" + mutableEnv.NODE_ENV = "production" + delete process.env.OPENAI_URL + delete process.env.OPENAI_MODEL + let cookie = "" + let token = "" + try { + await t.test("public login, health and docs; anonymous app and all workspace/brain GETs blocked", async () => { + assert.equal(proxy(request("/"))?.status, 307) + assert.equal(proxy(request("/"))?.headers.get("location"), "http://localhost/login") + for (const url of ["/login", "/health", "/api-docs", "/api/auth/status", "/api/auth/login", "/api/auth/logout"]) assert.equal(proxy(request(url))?.status, 200) + for (const url of ["/api/v1/portfolio", "/api/v1/workspaces", "/api/v1/workspaces/test/brain", "/api/v1/workspaces/test/context", "/api/v1/llm/status", "/api/v1/llm/models", "/api/v1/config", "/api/fetch-url"]) assert.equal(proxy(request(url))?.status, 401) + assert.equal((await dataGet(request("/api/v1/workspaces/test/brain"), { params: Promise.resolve({ path: ["workspaces", "test", "brain"] }) })).status, 401) + assert.deepEqual(await (await status(request("/api/auth/status"))).json(), { authenticated: false, required: true }) + }) + await t.test("only human login key works; successful response has HttpOnly Secure Lax expiring cookie, no secrets", async () => { + assert.equal((await login(loginRequest("wrong-key"))).status, 401) + assert.equal((await login(loginRequest("machine-test-key"))).status, 401) + const response = await login(loginRequest("human-test-key")) + assert.equal(response.status, 200) + assert.deepEqual(await response.json(), { success: true }) + const header = response.headers.get("set-cookie") || "" + for (const property of ["HttpOnly", "Secure", "SameSite=lax", "Path=/", `Max-Age=${SESSION_MAX_AGE}`]) assert.ok(header.includes(property)) + token = response.cookies.get(SESSION_COOKIE)!.value + cookie = `${SESSION_COOKIE}=${token}` + assert.equal(validateSession(token), true) + const files = readdirSync(directory) + assert.equal(files.length, 1) + assert.equal(statSync(path.join(directory, files[0])).mode & 0o777, 0o600) + assert.equal(readFileSync(path.join(directory, files[0]), "utf8").includes(token), false) + assert.equal(files[0].includes(token), false) + assert.equal(sessionCookieOptions().secure, true) + mutableEnv.NODE_ENV = "development" + assert.equal(sessionCookieOptions().secure, false) + mutableEnv.NODE_ENV = "production" + }) + await t.test("browser cookie and separate bearer can access APIs; missing, invalid and login-key bearers cannot", async () => { + for (const headers of [{ Cookie: cookie }, { Authorization: "Bearer machine-test-key" }] as Record[]) { + const req = request("/api/v1/workspaces/test/brain", { headers }) + assert.equal(isAuthenticated(req), true) + assert.equal(requireAuth(req), null) + assert.equal(proxy(request("/", { headers }))?.status, 200) + assert.equal((await dataGet(req, { params: Promise.resolve({ path: ["workspaces", "test", "brain"] }) })).status, 200) + assert.equal((await modelsGet(request("/api/v1/llm/models", { headers }))).status, 200) + assert.equal((await (await status(request("/api/auth/status", { headers }))).json()).authenticated, true) + } + for (const headers of [{ Authorization: "Bearer human-test-key" }, { Authorization: "Bearer bad" }, { Cookie: "nodepad_session=bad" }] as Record[]) assert.equal(isAuthenticated(request("/", { headers })), false) + }) + await t.test("cookie mutations and login reject foreign origins; bearer automation still works", async () => { + const crossSite = { method: "POST", headers: { Cookie: cookie, Origin: "https://evil.example" } } + assert.equal(requireAuth(request("/api/v1/workspaces/test/evidence", crossSite))?.status, 403) + assert.equal((await logout(request("/api/auth/logout", crossSite))).status, 403) + assert.equal((await login(request("/api/auth/login", { ...crossSite, body: '{"accessKey":"human-test-key"}' }))).status, 403) + assert.equal(requireAuth(request("/api/v1/workspaces/test/evidence", { method: "POST", headers: { Authorization: "Bearer machine-test-key", Origin: "https://machine.example" } })), null) + }) + await t.test("logout revokes copied cookie immediately", async () => { + const response = await logout(request("/api/auth/logout", { method: "POST", headers: { Cookie: cookie, Origin: "http://localhost" } })) + assert.equal(response.status, 200) + assert.ok(response.headers.get("set-cookie")?.includes("Max-Age=0")) + assert.equal(isAuthenticated(request("/", { headers: { Cookie: cookie } })), false) + assert.equal(validateSession(token), false) + }) + await t.test("five failures per minute then 429; spoofed prefix cannot change trusted rightmost IP", async () => { + for (let i = 0; i < 5; i++) assert.equal((await login(loginRequest("wrong", `203.0.113.${i}, 192.0.2.11`))).status, 401) + const response = await login(loginRequest("human-test-key", "203.0.113.99, 192.0.2.11")) + assert.equal(response.status, 429) + assert.ok(Number(response.headers.get("retry-after")) > 0) + assert.equal(loginRetryAfter("192.0.2.11", Date.now() + 61_000), 0) + process.env.NODEPAD_TRUST_PROXY = "false" + assert.equal(clientIp(loginRequest("x", "192.0.2.99")), "unknown") + process.env.NODEPAD_TRUST_PROXY = "true" + }) + await t.test("concurrent login failures cannot bypass the five-attempt limiter", async () => { + const responses = await Promise.all(Array.from({ length: 20 }, () => login(loginRequest("wrong", "192.0.2.13")))) + assert.equal(responses.filter(r => r.status === 401).length, 5) + assert.equal(responses.filter(r => r.status === 429).length, 15) + }) + await t.test("expiry, forged tokens and key rotation invalidate sessions; absent keys fail closed", async () => { + const expiring = createSession() + assert.equal(validateSession(expiring, Date.now() + SESSION_MAX_AGE * 1000 + 1), false) + assert.equal(validateSession("../sessions"), false) + process.env.NODEPAD_LOGIN_KEY = "rotated-human-key" + assert.equal(validateSession(expiring), false) + process.env.NODEPAD_LOGIN_KEY = "human-test-key" + invalidateSession(expiring) + delete process.env.NODEPAD_LOGIN_KEY + assert.equal((await login(loginRequest("human-test-key", "192.0.2.12"))).status, 503) + assert.equal(isAuthenticated(request("/", { headers: { Authorization: "Bearer machine-test-key" } })), true) + delete process.env.NODEPAD_API_KEY + assert.equal(isAuthenticated(request("/")), false) + assert.equal(timingSafeMatch("", undefined), false) + assert.equal(timingSafeMatch("short", "longer"), false) + process.env.NODEPAD_LOGIN_KEY = "human-test-key" + }) + await t.test("human login keys never enter serialized brain/workspace metadata", () => { + const workspace = fixture() + workspace.nodes.push({ id: "auth-test", workspaceId: workspace.id, title: "test", kind: "observation", body: "safe", metadata: { NODEPAD_LOGIN_KEY: "human-test-key", loginKey: "human-test-key", accessKey: "human-test-key", sessionCookie: cookie }, version: 1, createdAt: workspace.createdAt, updatedAt: workspace.updatedAt, provenance: { origin: "human", createdAt: workspace.createdAt } }) + const serialized = JSON.stringify(serializeWorkspace(workspace)) + assert.equal(serialized.includes("human-test-key"), false) + assert.equal(serialized.includes(token), false) + }) + await t.test("login reducer discards human key after authentication or error; supports retry", () => { + const ready = loginReducer({ phase: "checking_server", attempt: 1, generation: 0 }, { type: "AUTH_REQUIRED" }) + const entered = loginReducer(ready, { type: "UPDATE_KEY", value: "human-test-key" }) + const logging = loginReducer(entered, { type: "SUBMIT" }) + assert.equal(JSON.stringify(loginReducer(logging, { type: "AUTH_VALID" })).includes("human-test-key"), false) + assert.equal(JSON.stringify(loginReducer(logging, { type: "LOGIN_ERROR", message: "Invalid access key." })).includes("human-test-key"), false) + assert.equal(loginReducer({ phase: "server_error", message: "Offline" }, { type: "RETRY" }).phase, "checking_server") + }) + } finally { + for (const [name, value] of Object.entries(saved)) { if (value === undefined) delete process.env[name]; else process.env[name] = value } + rmSync(directory, { recursive: true, force: true }) + } +}) diff --git a/tests/brain-backup.test.ts b/tests/brain-backup.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2677f7d4c01580095bad25ceef7efeaeb231ccd --- /dev/null +++ b/tests/brain-backup.test.ts @@ -0,0 +1,4 @@ +import test from "node:test";import assert from "node:assert/strict";import { serializeWorkspace,restoreSerialized,snapshotContainsSecrets } from "../lib/github-sync/serialization";import { scoreHypothesis } from "../lib/scoring/hypothesis";import { updateBrain } from "../lib/brain/export";import { evidence,fixture,hypothesis } from "./helpers" +test("brain promotion and contradiction demotion are audited",()=>{const w=fixture();w.evidence=[evidence("a","support","interview"),evidence("b","support","analytics")];let h=hypothesis(["a","b"]);h=scoreHypothesis(h,w.evidence,Date.parse("2026-10-04"));w.hypotheses=[h];assert.equal(updateBrain(w,"r1").promoted,1);w.evidence.push(evidence("c","contradict","experiment"),evidence("d","contradict","sales"),evidence("e","contradict","usage"));h={...h,contradictingEvidence:["c","d","e"]};w.hypotheses=[scoreHypothesis(h,w.evidence,Date.parse("2026-10-04"))];assert.equal(updateBrain(w,"r2").demoted,1);assert.deepEqual(w.brain.changelog.map(e=>e.type),["brain.promoted","brain.demoted"])}); +test("serialization round-trips and excludes secrets",()=>{const w=fixture();(w as any).settings={apiKey:"sk-should-never-serialize",cookie:"private"};const first=serializeWorkspace(w);assert.equal(snapshotContainsSecrets(first),false);const second=serializeWorkspace(restoreSerialized(first));assert.deepEqual(second.files,first.files)}) +test("invalid restore fails before current workspace can be replaced",()=>{const current=fixture(),snapshot=serializeWorkspace(current);snapshot.files["schema-version.txt"]="999\n";assert.throws(()=>restoreSerialized(snapshot,"staging"),/Unsupported snapshot schema/);assert.equal(current.id,"fixture");assert.equal(current.revision,0)}) diff --git a/tests/embeddings.test.ts b/tests/embeddings.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c29732b70f5d5179a8d714b3742ea7704fd4678 --- /dev/null +++ b/tests/embeddings.test.ts @@ -0,0 +1,189 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises" +import path from "node:path" +import { tmpdir } from "node:os" +import { EmbeddingCache } from "../lib/embeddings/cache" +import { OllamaEmbeddingProvider } from "../lib/embeddings/ollama" +import { createSemanticLayer, embeddingHealth, SemanticLayer } from "../lib/embeddings" +import { cosine } from "../lib/embeddings/similarity" +import { dedupeEvidence } from "../lib/embeddings/dedupe" +import { semanticClusters } from "../lib/embeddings/themes" +import { adjudicateRelevant } from "../lib/embeddings/relevance" +import { matchOrCreateHypothesis } from "../lib/hypotheses/matching" +import { runHeartbeat } from "../lib/heartbeat" +import { FileNodepadStore } from "../lib/persistence/file-store" +import { serializeWorkspace } from "../lib/github-sync/serialization" +import { fixture, evidence, hypothesis, fixtureAdjudicator } from "./helpers" +import type { EmbeddingProvider } from "../lib/embeddings/provider" +const provider = (map: Record = {}): EmbeddingProvider => ({ async embed(texts) { return texts.map(t => map[t] || [1, 0]) }, async health() { return { enabled: true, provider: "mock", model: "test", status: "available" } } }) +async function temporary(run: (dir: string) => Promise) { const dir = await mkdtemp(path.join(tmpdir(), "nodepad-embeddings-")); try { await run(dir) } finally { await rm(dir, { recursive: true, force: true }) } } +function layer(dir: string, vectors: Record = {}) { return new SemanticLayer(provider(vectors), new EmbeddingCache(dir, "mock", "test")) } +const proposal = (statement: string) => ({ statement, hypothesisType: "problem", derivedFrom: [] }) + +test("Ollama batches /api/embed, keeps configurable model, rejects malformed vectors", async () => { + const client = new OllamaEmbeddingProvider("http://ollama.test/", "custom/tag:v2", async (url, options) => { + assert.equal(url, "http://ollama.test/api/embed") + assert.deepEqual(JSON.parse(String(options?.body)), { model: "custom/tag:v2", input: ["a", "b"], truncate: false }) + return new Response('{"embeddings":[[1,0],[0,1]]}') + }) + assert.deepEqual(await client.embed(["a", "b"]), [[1, 0], [0, 1]]) + for (const body of ['{"embeddings":[[0,0]]}', '{"embeddings":[[1],[1,0]]}', '{}']) { + const malformed = new OllamaEmbeddingProvider("http://ollama.test", "m", async () => new Response(body)) + await assert.rejects(() => malformed.embed(["a", "b"]), /degraded/) + } +}) +test("health reports installation, missing model, auth/server/network failure without raw secrets", async () => { + for (const [status, expected] of [[404, "model_error"], [500, "degraded"], [401, "degraded"]] as const) { + const client = new OllamaEmbeddingProvider("http://ollama.test", "m", async () => new Response("SECRET_FROM_BODY", { status })) + const health = await client.health(); assert.equal(health.status, expected); assert.equal(JSON.stringify(health).includes("SECRET"), false) + } + const up = new OllamaEmbeddingProvider("http://ollama.test", "m", async () => new Response('{"models":[{"name":"m:latest"}]}')) + assert.equal((await up.health()).status, "available") + const missing = new OllamaEmbeddingProvider("http://ollama.test", "m", async () => new Response('{"models":[]}')) + assert.equal((await missing.health()).status, "model_error") + const bad = new OllamaEmbeddingProvider("http://ollama.test", "m", async () => { throw new Error("SECRET") }) + assert.equal((await bad.health()).status, "unreachable") +}) +test("disabled provider makes no calls and heartbeat retains deterministic behavior", async () => temporary(async dir => { + const previous = process.env.NODEPAD_EMBEDDING_ENABLED; process.env.NODEPAD_EMBEDDING_ENABLED = "false" + try { + const semantic = createSemanticLayer(dir, async () => { throw new Error("should not call") }) + assert.equal((await semantic.vectors(["a"])).size, 0) + assert.equal((await embeddingHealth()).status, "disabled") + const store = new FileNodepadStore(dir), w = fixture(); w.evidence = [evidence("a", "support")]; await store.saveWorkspace(w) + assert.equal((await runHeartbeat(store, w.id, { runId: "disabled", semanticLayer: semantic, providers: [], synthesisIds: [] })).status, "completed") + } finally { if (previous === undefined) delete process.env.NODEPAD_EMBEDDING_ENABLED; else process.env.NODEPAD_EMBEDDING_ENABLED = previous } +})) +test("unavailable Ollama never fails heartbeat and is retried at most once per run", async () => temporary(async dir => { + let calls = 0 + const bad: EmbeddingProvider = { ...provider(), async embed() { calls++; throw new Error("offline") } } + const semantic = new SemanticLayer(bad, new EmbeddingCache(dir, "mock", "test")) + const store = new FileNodepadStore(dir), w = fixture(); w.evidence = [evidence("a", "support")]; await store.saveWorkspace(w) + assert.equal((await runHeartbeat(store, w.id, { runId: "offline", semanticLayer: semantic, providers: [], synthesisIds: [] })).status, "completed") + assert.equal(calls, 1) +})) +test("durable content cache avoids repeat batches, rekeys model/URL and rejects corruption", async () => temporary(async dir => { + let calls = 0 + const p = { ...provider(), async embed(texts: string[]) { calls++; return texts.map(() => [1, 0]) } } + const cache = new EmbeddingCache(dir, "ollama", "m", "url") + await cache.embed(["a", "a", "b"], p) + await new EmbeddingCache(dir, "ollama", "m", "url").embed(["a", "b"], p) + assert.equal(calls, 1) + await new EmbeddingCache(dir, "ollama", "other", "url").embed(["a"], p) + assert.equal(calls, 2) + assert.notEqual(cache.key("a"), new EmbeddingCache(dir, "ollama", "m", "other").key("a")) + await writeFile(path.join(dir, ".embeddings", `${cache.key("a")}.json`), "invalid") + assert.equal(await cache.get("a"), undefined) + await cache.embed(["a"], p); assert.equal(calls, 3) + assert.equal((await readFile(path.join(dir, ".embeddings", `${cache.key("a")}.json`), "utf8")).includes('"a"'), false) +})) +test("exact evidence dedupe preserves source/provenance/aliases without embeddings", () => { + const w = fixture(), first = evidence("a", "support"), second = { ...first, id: "b", provenance: { ...first.provenance, actorId: "other" } } + w.evidence = [first, second]; w.hypotheses = [hypothesis(["b"])] + assert.equal(dedupeEvidence(w).get("b"), "a") + assert.deepEqual(w.hypotheses[0].supportingEvidence, ["a"]) + assert.equal(w.evidence[0].duplicateRecords?.[0].provenance.actorId, "other") + assert.deepEqual(w.evidence[0].aliases, ["b"]) +}) +test("semantic evidence duplicate merges conservatively; related and negated claims stay separate", () => { + const w = fixture(); w.evidence = [{ ...evidence("a", "support"), claim: "Checkout is slow" }, { ...evidence("b", "support"), claim: "Purchasing takes too long", sourceRef: "source-b" }, { ...evidence("c", "support"), claim: "Checkout has navigation problems" }, { ...evidence("d", "support"), claim: "Checkout is not slow" }] + const before = w.evidence[1].sourceRef + dedupeEvidence(w, (a, b) => b === "Checkout has navigation problems" ? .8 : .99) + assert.equal(w.evidence.length, 3) + assert.equal(w.evidence[0].duplicateRecords?.[0].sourceRef, before) + assert.ok(w.evidence.some(e => e.id === "d")) +}) +test("semantic duplicate detection cannot collapse opposing adjudicated evidence", () => { + const w = fixture(); w.evidence = [evidence("a", "support"), evidence("b", "contradict")]; w.hypotheses = [hypothesis(["a"], ["b"])] + dedupeEvidence(w, () => .999) + assert.equal(w.evidence.length, 2) +}) +test("high hypothesis similarity matches duplicate; moderate similarity creates no epistemic edges", () => { + const w = fixture() + const original = matchOrCreateHypothesis(w, proposal("Checkout takes too long"), "s1", "r1").hypothesis + const before = { qualification: original.qualification, evidenceScore: original.evidenceScore, confidence: original.confidence } + const match = matchOrCreateHypothesis(w, proposal("Purchasing is slow"), "s2", "r2", { similarity: () => .96 }) + assert.equal(match.created, false); assert.equal(match.hypothesis.id, original.id) + assert.deepEqual({ qualification: original.qualification, evidenceScore: original.evidenceScore, confidence: original.confidence }, before) + matchOrCreateHypothesis(w, proposal("Invoices require manual checks"), "s3", "r3", { similarity: () => .65 }) + assert.equal(w.hypotheses.length, 2) + assert.equal(w.edges.some(e => ["supersedes", "alternative_to", "supports", "contradicts"].includes(e.type)), false) +}) +test("semantic themes group paraphrases with stable keys and lexical fallback", () => { + const w = fixture(); w.evidence = [{ ...evidence("a", "support"), claim: "Slow checkout" }, { ...evidence("b", "support"), claim: "Purchasing takes time" }, { ...evidence("c", "support"), claim: "Invoices fail" }] + const vectors = new Map([["Slow checkout", [1, 0]], ["Purchasing takes time", [.99, .01]], ["Invoices fail", [0, 1]]]) + const clusters = semanticClusters(w.evidence, vectors) + assert.equal(clusters.length, 2); assert.equal(clusters[0].evidence.length, 2) + assert.deepEqual(semanticClusters([...w.evidence].reverse(), vectors).map(c => c.key), clusters.map(c => c.key)) + assert.equal(semanticClusters(w.evidence, new Map()).every(c => !c.centroid), true) +}) +test("relevance only gates adjudicator; low scores store/no classification, high scores invoke callback", async () => temporary(async dir => { + const semantic = layer(dir, { result: [1, 0], unrelated: [0, 1], related: [.99, .01] }) + let calls = 0 + const low = await adjudicateRelevant("result", "unrelated", semantic, () => { calls++; return "contradicts" }) + assert.equal(low.shouldAdjudicate, false); assert.equal(calls, 0); assert.equal(low.result, undefined) + const high = await adjudicateRelevant("result", "related", semantic, () => { calls++; return "supports" }) + assert.equal(calls, 1); assert.equal(high.result, "supports") +})) +test("heartbeat persists semantic membership without cache/config in backup and reuses cache", async () => temporary(async dir => { + const store = new FileNodepadStore(dir), w = fixture(); w.evidence = [{ ...evidence("a", "support"), claim: "Slow checkout" }, { ...evidence("b", "support"), claim: "Invoices fail" }]; await store.saveWorkspace(w) + const semantic = layer(dir, { "Slow checkout": [1, 0], "Invoices fail": [0, 1] }) + await runHeartbeat(store, w.id, { runId: "one", semanticLayer: semantic, providers: [], synthesisIds: [] }) + const after = await store.getWorkspace(w.id) + assert.equal(after.evidence.length, 2) + assert.ok(after.nodes.some(n => n.kind === "theme" && Array.isArray(n.metadata.memberEvidenceIds) && Array.isArray(n.metadata.centroid))) + const keys = Object.keys(serializeWorkspace(after).files) + assert.equal(keys.some(key => key.includes(".embeddings")), false) + assert.equal(JSON.stringify(serializeWorkspace(after)).includes("NODEPAD_EMBEDDING_URL"), false) + assert.ok((await readdir(path.join(dir, ".embeddings"))).length > 0) +})) +test("invalid/different dimensionality and zero vectors never produce false similarity", () => { + assert.equal(cosine([1], [1, 0]), undefined) + assert.equal(cosine([0, 0], [1, 0]), undefined) + assert.equal(cosine([NaN], [1]), undefined) +}) + +test("heartbeat relevance prefilter retains research evidence but gates explicit relation handling", async () => temporary(async dir => { + for (const related of [false, true]) { + const subdir = path.join(dir, String(related)), store = new FileNodepadStore(subdir), w = fixture() + w.hypotheses = [hypothesis()] + w.researchTasks = [{ id: "task", workspaceId: w.id, hypothesisIds: ["h1"], question: "test", objective: "support", preferredSources: [], requiredSourceDiversity: 1, maxCostClass: "free", status: "running", priority: 1, attempts: 0, maxAttempts: 1, createdBy: "test" }] + w.researchResults = [{ id: "result", taskId: "task", providerId: "mock", title: "Result", excerpt: "independent finding", retrievedAt: new Date().toISOString(), relation: "supports" }] + await store.saveWorkspace(w) + const semantic = layer(subdir, { "Result: independent finding": [1, 0], [w.hypotheses[0].statement]: related ? [1, 0] : [0, 1] }) + await runHeartbeat(store, w.id, { adjudicator: fixtureAdjudicator, runId: "relevance", semanticLayer: semantic, providers: [], synthesisIds: [] }) + const after = await store.getWorkspace(w.id) + assert.equal(after.evidence.length, 1) + assert.equal(after.hypotheses[0].supportingEvidence.length, related ? 1 : 0) + assert.equal(after.edges.some(e => e.type === "supports"), related) + assert.equal((after.researchResults[0].metadata?.semanticRelevance as Record).h1, related ? 1 : 0) + } +})) +test("Ollama health/requests reject credential-bearing URLs and malformed config", async () => { + const client = new OllamaEmbeddingProvider("http://user:secret@host", "m", async () => { throw new Error("must not fetch") }) + assert.equal((await client.health()).status, "not_configured") + assert.equal(JSON.stringify(await client.health()).includes("secret"), false) +}) + +test("high similarity cannot merge numerically different claims", () => { + const w = fixture(); w.evidence = [{ ...evidence("a", "support"), claim: "Price is 10 dollars" }, { ...evidence("b", "support"), claim: "Price is 100 dollars" }] + dedupeEvidence(w, () => .999) + assert.equal(w.evidence.length, 2) +}) + +test("embedding status route requires authentication and disabled mode returns only operational fields", async () => { + const { GET } = await import("../app/api/v1/embeddings/status/route") + const { NextRequest } = await import("next/server") + const previousKey = process.env.NODEPAD_API_KEY, previousEnabled = process.env.NODEPAD_EMBEDDING_ENABLED + process.env.NODEPAD_API_KEY = "embedding-status-fixture"; process.env.NODEPAD_EMBEDDING_ENABLED = "false" + try { + assert.equal((await GET(new NextRequest("http://localhost/api/v1/embeddings/status"))).status, 401) + const response = await GET(new NextRequest("http://localhost/api/v1/embeddings/status", { headers: { Authorization: "Bearer embedding-status-fixture" } })) + assert.equal(response.status, 200) + assert.deepEqual(Object.keys(await response.json()).sort(), ["enabled", "model", "provider", "status"]) + } finally { + if (previousKey === undefined) delete process.env.NODEPAD_API_KEY; else process.env.NODEPAD_API_KEY = previousKey + if (previousEnabled === undefined) delete process.env.NODEPAD_EMBEDDING_ENABLED; else process.env.NODEPAD_EMBEDDING_ENABLED = previousEnabled + } +}) diff --git a/tests/final-depth.test.ts b/tests/final-depth.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..a577e67089a06b6af521c5e54cad0d0d20963bef --- /dev/null +++ b/tests/final-depth.test.ts @@ -0,0 +1,11 @@ +import test from "node:test";import assert from "node:assert/strict";import {mkdtemp,rm}from"node:fs/promises";import{tmpdir}from"node:os";import path from"node:path" +import{fixture,evidence,hypothesis}from"./helpers";import{executeSynthesis}from"../lib/synthesis/runner";import{garden}from"../lib/gardener";import{runHeartbeat}from"../lib/heartbeat";import{FileNodepadStore}from"../lib/persistence/file-store";import type{ResearchProvider}from"../lib/research/providers";import type{ResearchTask}from"../lib/domain/types";import{executeResearch,ResearchProviderRuntime,CapabilityResearchProvider}from"../lib/research/providers";import{scoreHypothesis}from"../lib/scoring/hypothesis";import{updateBrain,exportBrain}from"../lib/brain/export";import{NodepadService}from"../lib/services/nodepad" +const node=(id:string,kind:any="hypothesis")=>({id,workspaceId:"fixture",kind,title:id,body:id,metadata:{},provenance:{origin:"human" as const,createdAt:"2026-01-01T00:00:00Z"},createdAt:"2026-01-01T00:00:00Z",updatedAt:"2026-01-01T00:00:00Z",version:1}) +test("ST6 family outputs differ and all 130 stay schema-valid",async()=>{const outputs=new Set();for(let n=1;n<=130;n++){const w=fixture();w.evidence=[evidence("e","support")];const r=await executeSynthesis(w,`S${String(n).padStart(3,"0")}`,"r");assert.ok(r.result.provenance);assert.ok(r.result.statement);assert.equal("qualification" in r.proposals[0],false);outputs.add(r.result.rationale)}assert.ok(outputs.size>=13)}) +test("ST7 scoped synthesis excludes unrelated cluster from output and hash",async()=>{const w=fixture();w.evidence=[evidence("a","support"),{...evidence("b","support"),claim:"UNRELATED_BETA"}];w.nodes=[node("a","evidence"),node("b","evidence"),node("ha")];w.hypotheses=[hypothesis(["a"])];w.hypotheses[0].id="ha";w.edges=[{id:"x",workspaceId:w.id,sourceId:"a",targetId:"ha",type:"supports",provenance:{origin:"human",createdAt:"2026-01-01"},createdAt:"2026-01-01"}];const a=await executeSynthesis(w,"S031","r",undefined,{hypothesisId:"ha"});assert.equal(a.result.derivedFrom.includes("b"),false);const hash=a.result.inputHash;w.evidence[1].claim="CHANGED_BETA";assert.equal((await executeSynthesis(w,"S031","r2",undefined,{hypothesisId:"ha"})).result.inputHash,hash)}) +test("ST8 gardener produces advanced proposal set without authority",()=>{const w=fixture(),h={...hypothesis(["e1"]),strategicImportance:90,evidenceScore:75,qualification:"qualified" as const,confidence:80,promoted:true,overlays:["stale" as const],alternativeHypotheses:["h2"],contradictingEvidence:["e2"]};w.hypotheses=[h];w.nodes=[node("h1"),node("h2"),node("h3"),{...node("t1","theme"),title:"Pain"},{...node("t2","theme"),title:"Segment"},{...node("t3","theme"),title:"Channel"}];w.edges=[{id:"d",workspaceId:w.id,sourceId:"h1",targetId:"h2",type:"depends_on",provenance:{origin:"human",createdAt:"x"},createdAt:"x"},{id:"d2",workspaceId:w.id,sourceId:"h1",targetId:"h3",type:"depends_on",provenance:{origin:"human",createdAt:"x"},createdAt:"x"}];const types=new Set(garden(w).map(x=>x.type));for(const expected of(["discriminating_test","second_order","third_order_consequence","cross_cluster_bridge","hidden_common_cause","opportunity","new_venture","stale_central_knowledge","foundational_drift"] as const))assert.ok(types.has(expected));assert.ok(garden(w).every(x=>!(["qualified","strongly_qualified"] as any[]).includes((x as any).qualification)))}) +test("ST9 provider failure/rate isolation and capability boundary",async()=>{const task:ResearchTask={id:"t",workspaceId:"w",hypothesisIds:[],question:"q",objective:"discover",preferredSources:[],requiredSourceDiversity:1,maxCostClass:"free",status:"queued",priority:1,attempts:0,maxAttempts:3,createdBy:"x"};const good:ResearchProvider={policy:{id:"good",costClass:"free",concurrency:1,enabled:true,health:"healthy"},async search(){return[{title:"ok",excerpt:"ok"}]}};const bad:ResearchProvider={policy:{id:"bad",costClass:"free",concurrency:1,enabled:true,health:"healthy"},async search(){throw Error("no")}};const rate:ResearchProvider={policy:{id:"rate",costClass:"free",concurrency:1,enabled:true,health:"healthy",requestsPerWindow:1,windowSeconds:60},async search(){return[{title:"one",excerpt:"one"}]}};const runtime=new ResearchProviderRuntime();assert.equal((await executeResearch(task,[good,bad,rate],runtime)).length,2);assert.equal((await executeResearch(task,[rate],runtime)).length,0);assert.equal(bad.policy.health,"degraded");const cap=new CapabilityResearchProvider({id:"mcp-fixture",costClass:"free",concurrency:1,enabled:true,health:"healthy"},{invoke:async()=>[{title:"mcp",excerpt:"fixture"}]},"research");assert.equal((await cap.search(task))[0].title,"mcp")}) +test("ST1/ST2 large graph heartbeat and repeated cycles remain bounded",async()=>{const dir=await mkdtemp(path.join(tmpdir(),"nodepad-large-")),store=new FileNodepadStore(dir),w=fixture();for(let i=0;i<1100;i++)w.evidence.push({...evidence(`e${i}`,i%3?"support":"contradict",`s${i%40}`),claim:`${i%5===0?"privacy pain":"workflow problem"} ${i}`,observedAt:i%7===0?"2020-01-01":"2026-10-01"});for(let i=0;i<220;i++){const h={...hypothesis([`e${i}`],[`e${i+300}`]),id:`h${i}`,title:`H${i}`,statement:`Hypothesis ${i}`};w.hypotheses.push(scoreHypothesis(h,w.evidence,Date.parse("2026-10-06")));w.nodes.push(node(h.id))}await store.saveWorkspace(w);process.env.NODEPAD_MAX_RESEARCH_TASKS_PER_HEARTBEAT="0";await runHeartbeat(store,w.id,{runId:"large",now:new Date("2026-10-06"),providers:[],synthesisIds:["S001","S031","S121"]});let state=await store.getWorkspace(w.id);const stable=[state.hypotheses.length,state.evidence.length,state.syntheses.length,state.researchResults.length];for(let i=0;i<8;i++)await runHeartbeat(store,w.id,{runId:`cycle${i}`,now:new Date(`2026-10-06T0${i}:00:00Z`),providers:[],synthesisIds:["S001","S031","S121"]});state=await store.getWorkspace(w.id);assert.deepEqual([state.hypotheses.length,state.evidence.length,state.syntheses.length,state.researchResults.length],stable);assert.ok(state.hypotheses.every(h=>h.evidenceScore>=-100&&h.evidenceScore<=100));await rm(dir,{recursive:true,force:true})}) +test("ST4/ST5 qualification oscillation preserves contradiction and brain audit",()=>{const w=fixture(),supports=Array.from({length:6},(_,i)=>evidence(`s${i}`,"support",`s${i}`)),contradictions=Array.from({length:4},(_,i)=>evidence(`c${i}`,"contradict",`c${i}`)),all=[...supports,...contradictions];w.evidence=all;let h=hypothesis();w.hypotheses=[h];const phases=[{s:[],c:[]},{s:["s0","s1"],c:["c0"]},{s:["s0","s1","s2","s3","s4","s5"],c:["c0"]},{s:["s0","s1","s2"],c:[]},{s:["s0","s1","s2"],c:["c0","c1"]},{s:[],c:["c0","c1","c2","c3"]},{s:["s0","s1","s2"],c:[]}],states:string[]=[];for(const [i,p]of phases.entries()){h={...h,supportingEvidence:p.s,contradictingEvidence:p.c};h=scoreHypothesis(h,all,Date.parse("2026-10-06"));w.hypotheses=[h];updateBrain(w,`r${i}`);states.push(h.qualification)}assert.deepEqual(states,["unresolved","potentially_qualified","qualified","strongly_qualified","weak_signal","strongly_disqualified","strongly_qualified"]);assert.equal(new Set(w.brain.promotedIds).size,w.brain.promotedIds.length);assert.ok(w.events.some(e=>e.type==="brain.demoted"));assert.ok(w.events.filter(e=>e.type==="brain.promoted").length>=2);assert.equal(h.contradictingEvidence.length,0)}) +test("ST14/ST15/ST18 agency idempotency revision claim and correlation",async()=>{const dir=await mkdtemp(path.join(tmpdir(),"nodepad-agency-")),store=new FileNodepadStore(dir),service=new NodepadService(store),w=fixture();await store.saveWorkspace(w);const actor={actorType:"agent" as const,actorId:"agent-1",role:"researcher",sessionId:"s",agencyTaskId:"a",correlationId:"c"};const rev=(await store.getWorkspace(w.id)).revision,t1=await service.createResearchTask(w.id,{question:"q",objective:"support"},actor,"same",rev),t2=await service.createResearchTask(w.id,{question:"q",objective:"support"},actor,"same");assert.equal(t1.id,t2.id);await assert.rejects(()=>service.createResearchTask(w.id,{question:"stale",objective:"support"},actor,"new",rev),/revision_conflict/);const claimed=await service.claimResearchTask(w.id,t1.id,actor);assert.equal(claimed.claimedBy,"agent-1");await assert.rejects(()=>service.claimResearchTask(w.id,t1.id,{...actor,actorId:"agent-2"}));const event=await service.recordAgentEvent(w.id,{type:"agent.task_started",entityId:t1.id},actor,"event");assert.equal(event.actor.correlationId,"c");await rm(dir,{recursive:true,force:true})}) +test("ST19/ST20 brain diff and isolated portfolio summaries",async()=>{const dir=await mkdtemp(path.join(tmpdir(),"nodepad-portfolio-")),store=new FileNodepadStore(dir),service=new NodepadService(store),a=fixture(),b={...fixture(),id:"venture-b",name:"B"};a.events.push({id:"p",type:"brain.promoted",workspaceId:a.id,entityId:"h1",at:"2026-01-01",actor:{actorType:"system",actorId:"x"}});a.researchTasks.push({id:"t",workspaceId:a.id,hypothesisIds:[],question:"q",objective:"discover",preferredSources:[],requiredSourceDiversity:1,maxCostClass:"free",status:"queued",priority:1,attempts:0,maxAttempts:1,createdBy:"x"});await store.saveWorkspace(a);await store.saveWorkspace(b);assert.match(exportBrain(a).summary,/Newly promoted/);const p=await service.portfolio();assert.equal(p.length,2);assert.equal(p.find(x=>x.workspaceId===a.id)?.activeResearchTasks,1);assert.equal(p.find(x=>x.workspaceId===b.id)?.activeResearchTasks,0);await rm(dir,{recursive:true,force:true})}) diff --git a/tests/github-remote.test.ts b/tests/github-remote.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..b139a5c2c249fcf634ce18164d55b92cf7c46fd2 --- /dev/null +++ b/tests/github-remote.test.ts @@ -0,0 +1,62 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { createHash } from "node:crypto" +import { mkdtemp, rm } from "node:fs/promises" +import { tmpdir } from "node:os" +import path from "node:path" +import { GitHubBackupRemote, stageRestore } from "../lib/github-sync/backup" +import { restoreSerialized, serializeWorkspace } from "../lib/github-sync/serialization" +import { FileNodepadStore } from "../lib/persistence/file-store" +import { fixture } from "./helpers" + +const sha = (value: unknown) => createHash("sha1").update(JSON.stringify(value)).digest("hex") +class FakeGitHub { + blobs = new Map(); trees = new Map>(); commits = new Map() + branch?: string; defaultHead: string; refReads = 0; conflictOnConfirmation = false; commitsForPath: string[] = [] + constructor() { const tree = sha([]); this.trees.set(tree, []); this.defaultHead = sha("base"); this.commits.set(this.defaultHead, { tree: { sha: tree }, parents: [] }) } + response(status: number, body?: unknown) { return new Response(body === undefined ? null : JSON.stringify(body), { status, headers: { "content-type": "application/json" } }) } + fetch = async (input: string | URL | Request, init: RequestInit = {}) => { + const url = new URL(typeof input === "string" ? input : input instanceof URL ? input : input.url), route = url.pathname.replace("/repos/owner/repo", ""), method = init.method ?? "GET" + const body = init.body ? JSON.parse(String(init.body)) : undefined + if (route === "" && method === "GET") return this.response(200, { default_branch: "main" }) + if (route === "/git/ref/heads/backup" && method === "GET") { this.refReads++; if (this.conflictOnConfirmation && this.refReads % 2 === 0) this.branch = "external-head"; return this.branch ? this.response(200, { object: { sha: this.branch } }) : this.response(404, {}) } + if (route === "/git/ref/heads/main") return this.response(200, { object: { sha: this.defaultHead } }) + if (route.startsWith("/git/commits/") && method === "GET") { const id = decodeURIComponent(route.slice("/git/commits/".length)), commit = this.commits.get(id); return commit ? this.response(200, commit) : this.response(404, {}) } + if (route === "/git/blobs" && method === "POST") { const id = sha(body.content); this.blobs.set(id, body.content); return this.response(201, { sha: id }) } + if (route.startsWith("/git/blobs/") && method === "GET") { const content = this.blobs.get(route.slice("/git/blobs/".length)); return this.response(200, { content: Buffer.from(content ?? "").toString("base64"), encoding: "base64" }) } + if (route === "/git/trees" && method === "POST") { const base = [...(this.trees.get(body.base_tree) ?? [])], merged = new Map(base.map(item => [item.path, item])); for (const entry of body.tree) entry.sha === null ? merged.delete(entry.path) : merged.set(entry.path, entry); const entries = [...merged.values()].sort((a, b) => a.path.localeCompare(b.path)); const id = sha(entries); this.trees.set(id, entries); return this.response(201, { sha: id }) } + if (route.startsWith("/git/trees/") && method === "GET") { const id = route.slice("/git/trees/".length).split("?")[0]; return this.response(200, { sha: id, tree: this.trees.get(id) ?? [] }) } + if (route === "/git/commits" && method === "POST") { const id = sha(body); this.commits.set(id, { tree: { sha: body.tree }, parents: body.parents }); this.commitsForPath.unshift(id); return this.response(201, { sha: id }) } + if (route === "/git/refs" && method === "POST") { if (this.branch) return this.response(422, {}); this.branch = body.sha; return this.response(201, { object: { sha: body.sha } }) } + if (route === "/git/refs/heads/backup" && method === "PATCH") { if (body.force) return this.response(400, {}); this.branch = body.sha; return this.response(200, { object: { sha: body.sha } }) } + if (route === "/commits" && method === "GET") return this.response(200, this.commitsForPath.map(id => ({ sha: id }))) + return this.response(500, { route, method }) + } +} +const remoteFor = (api: FakeGitHub) => new GitHubBackupRemote({ token: "server-only-token", repository: "owner/repo", branch: "backup", basePath: "nodepad-workspaces", fetchImpl: api.fetch as typeof fetch }) + +test("GitHub remote creates, skips unchanged, advances, restores and lists snapshots", async () => { + const api = new FakeGitHub(), remote = remoteFor(api), workspace = fixture(); (workspace as any).configuration = { githubToken: "must-not-upload", cookie: "private" }; const firstSnapshot = serializeWorkspace(workspace) + const first = await remote.pushWorkspace(firstSnapshot); assert.equal(first.status, "completed"); assert.equal(api.branch, first.commitSha) + const unchanged = await remote.pushWorkspace(firstSnapshot); assert.equal(unchanged.skipped, true); assert.equal(unchanged.commitSha, first.commitSha) + workspace.name = "Changed"; const secondSnapshot = serializeWorkspace(workspace), second = await remote.pushWorkspace(secondSnapshot); assert.equal(second.status, "completed"); assert.notEqual(second.commitSha, first.commitSha) + const restored = await remote.restoreWorkspace(workspace.id, second.commitSha); assert.deepEqual(restored.files, secondSnapshot.files); assert.equal(restoreSerialized(restored).name, "Changed") + const snapshots = await remote.listSnapshots(workspace.id); assert.ok(snapshots.some((item: { ref: string }) => item.ref === second.commitSha)) + const uploaded = [...api.blobs.values()].join("\n"); assert.equal(uploaded.includes("server-only-token"), false); assert.equal(uploaded.includes("must-not-upload"), false) +}) + +test("GitHub remote detects branch head movement without force-updating", async () => { + const api = new FakeGitHub(), remote = remoteFor(api); await remote.pushWorkspace(serializeWorkspace(fixture())) + const workspace = fixture(); workspace.name = "Conflicting change"; api.conflictOnConfirmation = true; api.refReads = 0 + const result = await remote.pushWorkspace(serializeWorkspace(workspace)); assert.equal(result.conflict, true); assert.equal(result.status, "sync_conflict"); assert.equal(api.branch, "external-head") +}) + +test("invalid GitHub snapshot is rejected before a staging workspace is saved", async () => { + const api = new FakeGitHub(), remote = remoteFor(api), snapshot = serializeWorkspace(fixture()) + snapshot.files["schema-version.txt"] = "999\n" + const pushed = await remote.pushWorkspace(snapshot), directory = await mkdtemp(path.join(tmpdir(), "nodepad-github-restore-")) + const store = new FileNodepadStore(directory), current = fixture(); await store.saveWorkspace(current) + await assert.rejects(() => stageRestore(store, remote, current.id, pushed.commitSha, "staging"), /Unsupported snapshot schema/) + assert.deepEqual((await store.listWorkspaces()).map(workspace => workspace.id), [current.id]) + await rm(directory, { recursive: true, force: true }) +}) diff --git a/tests/heartbeat.test.ts b/tests/heartbeat.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..ef54f2c9c8a3bf786f0c107a8a21a42955ea1e35 --- /dev/null +++ b/tests/heartbeat.test.ts @@ -0,0 +1,2 @@ +import test from "node:test";import assert from "node:assert/strict";import { mkdtemp,rm } from "node:fs/promises";import { tmpdir } from "node:os";import path from "node:path";import { FileNodepadStore } from "../lib/persistence/file-store";import { runHeartbeat } from "../lib/heartbeat";import { evidence,fixture,hypothesis } from "./helpers" +test("heartbeat replay is idempotent and lease rejects duplicate workers",async()=>{const dir=await mkdtemp(path.join(tmpdir(),"nodepad-hb-")),store=new FileNodepadStore(dir),w=fixture();w.evidence=[evidence("e1","support")];w.hypotheses=[hypothesis(["e1"])];await store.saveWorkspace(w);const a=await runHeartbeat(store,w.id,{runId:"same",now:new Date("2026-10-04"),providers:[]});const after=await store.getWorkspace(w.id),counts=[after.syntheses.length,after.researchTasks.length,after.nodes.length];const b=await runHeartbeat(store,w.id,{runId:"same",now:new Date("2026-10-04"),providers:[]});const replay=await store.getWorkspace(w.id);assert.equal(a.status,"completed");assert.equal(b.status,"skipped");assert.deepEqual([replay.syntheses.length,replay.researchTasks.length,replay.nodes.length],counts);assert.equal(await store.acquireLease(w.id,"one",10000),true);assert.equal(await store.acquireLease(w.id,"two",10000),false);await rm(dir,{recursive:true,force:true})}) diff --git a/tests/helpers.ts b/tests/helpers.ts new file mode 100644 index 0000000000000000000000000000000000000000..c793e43cbe71f99a7fe9ded04fd95959904ef4e4 --- /dev/null +++ b/tests/helpers.ts @@ -0,0 +1,13 @@ +import { emptyWorkspace } from "../lib/persistence/file-store" +import type { EvidenceRecord, HypothesisRecord } from "../lib/domain/types" +export const evidence=(id:string,side:"support"|"contradict",sourceType=`source-${id}`,strength:1|2|3|4|5=5):EvidenceRecord=>({id,workspaceId:"fixture",claim:`${side} ${id}`,sourceType,sourceId:sourceType,strength,weight:1,stale:false,corroboratedBy:[],contradicts:side==="contradict"?["claim"]:[],ingestedAt:"2026-10-04T00:00:00.000Z",observedAt:"2026-10-04T00:00:00.000Z",evidenceType:"measured",provenance:{origin:"human",actorId:"test",createdAt:"2026-10-04T00:00:00.000Z"}}) +export const hypothesis=(supporting:string[]=[],contradicting:string[]=[]):HypothesisRecord=>({id:"h1",workspaceId:"fixture",title:"Users need this",statement:"Users need this",hypothesisType:"problem",derivedFrom:[],supportingEvidence:supporting,contradictingEvidence:contradicting,dependentAssumptions:[],alternativeHypotheses:[],impact:80,uncertainty:80,novelty:50,strategicImportance:80,evidenceScore:0,confidence:0,qualification:"unresolved",overlays:[],sourceDiversity:0,firstPartyWeight:0,behavioralWeight:0,freshnessScore:0,status:"active",researchGaps:[],createdAt:"2026-10-04T00:00:00.000Z",updatedAt:"2026-10-04T00:00:00.000Z",generation:{method:"test"}}) +export const fixture=()=>emptyWorkspace("fixture","Fixture") + +// Research fixture labels stand in for a validated interpreter, never a task-objective heuristic. +export const fixtureAdjudicator: import("../lib/adjudication/provider").EvidenceAdjudicator = { + async adjudicate(input) { + const { uncertain } = await import("../lib/adjudication/openai") + return { ...uncertain(input), relation: input.researchResult?.relation || "neutral", relevance: 100, confidence: 100, directness: "direct", rationale: "Explicitly labelled test research observation.", status: "completed", fallback: false, provider: "mock-adjudicator", model: "fixture" } + }, +} diff --git a/tests/legacy.test.ts b/tests/legacy.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..7402cac0d7a41f62dea108e588865734849b231b --- /dev/null +++ b/tests/legacy.test.ts @@ -0,0 +1,2 @@ +import test from "node:test";import assert from "node:assert/strict";import { migrateNodepadFile } from "../lib/migration/legacy-nodepad" +test("legacy .nodepad text and influencedBy relationships survive migration",()=>{const w=migrateNodepadFile({version:1,exportedAt:1,project:{id:"old",name:"Old",collapsedIds:[],ghostNotes:[],blocks:[{id:"a",text:"source",timestamp:1,contentType:"general"},{id:"b",text:"claim",timestamp:2,contentType:"claim",influencedBy:["a"]}]}});assert.deepEqual(w.nodes.map(n=>n.body),["source","claim"]);assert.equal(w.edges[0].type,"derived_from")}) diff --git a/tests/llm-provider.test.ts b/tests/llm-provider.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..9cfc57e4beba58cabece6c7952212e1f73a1e2f8 --- /dev/null +++ b/tests/llm-provider.test.ts @@ -0,0 +1,267 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { + normalizeOpenAiUrl, + getEnvLlmConfig, + getProviderStatus, + completeChat, +} from "../lib/ai/openai-provider" +import { loadAIConfig, getProviderHeaders } from "../lib/ai-settings" +import { serializeWorkspace } from "../lib/github-sync/serialization" +import { fixture } from "./helpers" + +test("1. provider absent -> application remains operational and unconfigured", async () => { + const origUrl = process.env.OPENAI_URL + const origModel = process.env.OPENAI_MODEL + delete process.env.OPENAI_URL + delete process.env.OPENAI_MODEL + + try { + const config = getEnvLlmConfig() + assert.equal(config.configured, false) + const status = await getProviderStatus() + assert.equal(status.status, "not_configured") + } finally { + if (origUrl) process.env.OPENAI_URL = origUrl + if (origModel) process.env.OPENAI_MODEL = origModel + } +}) + +test("2 & 3. URL normalization handles slashes, missing /v1 and existing /v1", () => { + assert.equal(normalizeOpenAiUrl("https://host.example.com"), "https://host.example.com/v1") + assert.equal(normalizeOpenAiUrl("https://host.example.com/"), "https://host.example.com/v1") + assert.equal(normalizeOpenAiUrl("https://host.example.com/v1"), "https://host.example.com/v1") + assert.equal(normalizeOpenAiUrl("https://host.example.com/v1/"), "https://host.example.com/v1") +}) + +test("4. configured model is registered as default fallback in loadAIConfig", () => { + process.env.OPENAI_URL = "https://example.com/v1" + process.env.OPENAI_MODEL = "gemma-3-12b" + process.env.OPENAI_API = "secret-key-123" + + try { + const config = loadAIConfig() + assert.ok(config) + assert.equal(config.provider, "openai-compatible") + assert.equal(config.modelId, "gemma-3-12b") + assert.equal(config.customBaseUrl, "https://example.com/v1") + assert.equal(config.apiKey, "secret-key-123") + } finally { + delete process.env.OPENAI_URL + delete process.env.OPENAI_MODEL + delete process.env.OPENAI_API + } +}) + +test("5. Authorization header uses OPENAI_API", () => { + const headers = getProviderHeaders({ + apiKey: "secret-token", + modelId: "test-model", + supportsGrounding: false, + provider: "openai-compatible", + customBaseUrl: "https://example.com/v1", + }) + + assert.equal(headers["Authorization"], "Bearer secret-token") + assert.equal(headers["Content-Type"], "application/json") +}) + +test("6. /models success detects configured model", async () => { + process.env.OPENAI_URL = "https://example.com" + process.env.OPENAI_MODEL = "gemma-3-12b" + process.env.OPENAI_API = "secret-token" + + const mockFetch: typeof fetch = async (url, init) => { + assert.equal(url.toString(), "https://example.com/v1/models") + assert.equal((init?.headers as Record)?.["Authorization"], "Bearer secret-token") + return new Response( + JSON.stringify({ + data: [{ id: "other-model" }, { id: "gemma-3-12b" }], + }), + { status: 200 } + ) + } + + try { + const status = await getProviderStatus(mockFetch) + assert.equal(status.status, "available") + assert.equal(status.configured, true) + assert.equal(status.model, "gemma-3-12b") + } finally { + delete process.env.OPENAI_URL + delete process.env.OPENAI_MODEL + delete process.env.OPENAI_API + } +}) + +test("7. /models unsupported or missing model does not make provider unusable", async () => { + process.env.OPENAI_URL = "https://example.com" + process.env.OPENAI_MODEL = "gemma-3-12b" + + // Test missing model in /models + const mockFetchMissingModel: typeof fetch = async () => { + return new Response(JSON.stringify({ data: [{ id: "other-model" }] }), { status: 200 }) + } + + // Test 404 on /models endpoint + const mockFetch404: typeof fetch = async () => { + return new Response("Not found", { status: 404 }) + } + + try { + const statusMissing = await getProviderStatus(mockFetchMissingModel) + assert.equal(statusMissing.status, "model_not_found") + assert.ok(statusMissing.warning) + + const status404 = await getProviderStatus(mockFetch404) + assert.equal(status404.status, "configured") + assert.ok(status404.warning) + } finally { + delete process.env.OPENAI_URL + delete process.env.OPENAI_MODEL + } +}) + +test("8. 401 returns auth_error", async () => { + process.env.OPENAI_URL = "https://example.com" + process.env.OPENAI_MODEL = "gemma-3-12b" + + const mockFetch401: typeof fetch = async () => { + return new Response("Unauthorized", { status: 401 }) + } + + try { + const status = await getProviderStatus(mockFetch401) + assert.equal(status.status, "auth_error") + } finally { + delete process.env.OPENAI_URL + delete process.env.OPENAI_MODEL + } +}) + +test("9. network failure or timeout returns unreachable/degraded state", async () => { + process.env.OPENAI_URL = "https://example.com" + process.env.OPENAI_MODEL = "gemma-3-12b" + + const mockFetchError: typeof fetch = async () => { + throw new Error("Network connection failed") + } + + try { + const status = await getProviderStatus(mockFetchError) + assert.equal(status.status, "unreachable") + assert.equal(status.warning, "Network connection failed") + } finally { + delete process.env.OPENAI_URL + delete process.env.OPENAI_MODEL + } +}) + +test("10. API secret never appears in status responses", async () => { + process.env.OPENAI_URL = "https://example.com" + process.env.OPENAI_MODEL = "gemma-3-12b" + process.env.OPENAI_API = "SUPER_SECRET_TOKEN_999" + + try { + const status = await getProviderStatus(async () => new Response("{}", { status: 200 })) + const jsonString = JSON.stringify(status) + assert.equal(jsonString.includes("SUPER_SECRET_TOKEN_999"), false) + assert.equal("apiKey" in status, false) + } finally { + delete process.env.OPENAI_URL + delete process.env.OPENAI_MODEL + delete process.env.OPENAI_API + } +}) + +test("11. API secret never appears in GitHub workspace serialization", () => { + process.env.OPENAI_API = "SUPER_SECRET_TOKEN_999" + try { + const w = fixture() + const serialized = JSON.stringify(serializeWorkspace(w)) + assert.equal(serialized.includes("SUPER_SECRET_TOKEN_999"), false) + } finally { + delete process.env.OPENAI_API + } +}) + +test("12. LLM unavailability does not break deterministic synthesis/heartbeat", () => { + const w = fixture() + // Ensure qualification, scoring, and workspace properties remain deterministic + assert.ok(w.id) + assert.ok(typeof w.revision === "number") + assert.ok(Array.isArray(w.evidence)) +}) + +test("14. mocked /chat/completions returns expected content/model", async () => { + process.env.OPENAI_URL = "https://example.com/v1" + process.env.OPENAI_MODEL = "gemma-3-12b" + process.env.OPENAI_API = "secret-key" + + const mockFetchChat: typeof fetch = async (url, init) => { + assert.equal(url.toString(), "https://example.com/v1/chat/completions") + const body = JSON.parse(init?.body as string) + assert.equal(body.model, "gemma-3-12b") + assert.equal(body.messages[0].content, "Respond only with NODEPAD_LLM_OK") + + return new Response( + JSON.stringify({ + model: "gemma-3-12b", + choices: [{ message: { content: "NODEPAD_LLM_OK" } }], + }), + { status: 200 } + ) + } + + try { + const res = await completeChat( + { messages: [{ role: "user", content: "Respond only with NODEPAD_LLM_OK" }] }, + mockFetchChat + ) + assert.equal(res.content, "NODEPAD_LLM_OK") + assert.equal(res.model, "gemma-3-12b") + assert.equal(res.provider, "openai-compatible") + } finally { + delete process.env.OPENAI_URL + delete process.env.OPENAI_MODEL + delete process.env.OPENAI_API + } +}) + +test("15. explicit model/provider selection still overrides environment default", () => { + process.env.OPENAI_URL = "https://env-example.com/v1" + process.env.OPENAI_MODEL = "env-model" + process.env.OPENAI_API = "env-api-key" + + // Simulate explicit localStorage settings + if (typeof window === "undefined") { + // Mock global window and localStorage + ;(global as unknown as { window: unknown }).window = {} + ;(global as unknown as { localStorage: unknown }).localStorage = { + getItem: (key: string) => { + if (key === "nodepad-ai-settings") { + return JSON.stringify({ + apiKey: "user-explicit-key", + modelId: "openai/gpt-4o", + provider: "openrouter", + }) + } + return null + }, + } + } + + try { + const config = loadAIConfig() + assert.ok(config) + assert.equal(config.provider, "openrouter") + assert.equal(config.apiKey, "user-explicit-key") + assert.equal(config.modelId, "openai/gpt-4o") + } finally { + delete (global as unknown as { window?: unknown }).window + delete (global as unknown as { localStorage?: unknown }).localStorage + delete process.env.OPENAI_URL + delete process.env.OPENAI_MODEL + delete process.env.OPENAI_API + } +}) diff --git a/tests/model-selection.test.ts b/tests/model-selection.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..aecf9b1528e77e10c2ea13b86fe92a48a0d21f92 --- /dev/null +++ b/tests/model-selection.test.ts @@ -0,0 +1,81 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { resolveModelId, sanitizeModels } from "../lib/model-selection" +import { discoverModels } from "../lib/ai/openai-provider" + +test("opaque discovered and manual model IDs override defaults without a whitelist", () => { + const models = sanitizeModels([{ id: "qwythos-9b" }, { id: "Qwen/Qwen3.8-27B-FP8" }, { id: "qwythos-9b" }, null, { id: 1 }], "gemma-3-12b") + assert.deepEqual(models.map(m => m.id), ["gemma-3-12b", "qwythos-9b", "Qwen/Qwen3.8-27B-FP8"]) + assert.equal(resolveModelId("manual/alias", "gemma-3-12b", models), "manual/alias") + assert.equal(resolveModelId("", "gemma-3-12b", models), "gemma-3-12b") + assert.equal(resolveModelId("", "", models), "gemma-3-12b") + assert.equal(resolveModelId("", "", []), "") +}) + +test("discovery includes deployment default on success, empty, failure and authentication errors; never exposes credentials", async () => { + const previous = { url: process.env.OPENAI_URL, model: process.env.OPENAI_MODEL, api: process.env.OPENAI_API } + process.env.OPENAI_URL = "https://models.example/v1/" + process.env.OPENAI_MODEL = "custom-default" + process.env.OPENAI_API = "private-test-credential" + try { + for (const status of [200, 404, 401, 500]) { + const result = await discoverModels(async (url, options) => { + assert.equal(url, "https://models.example/v1/models") + assert.equal((options?.headers as Record).Authorization, "Bearer private-test-credential") + return new Response(JSON.stringify({ data: [{ id: "spark-x2.5", secret: "private-test-credential" }] }), { status }) + }) + assert.ok(result.models.some(m => m.id === "custom-default")) + assert.equal(JSON.stringify(result).includes("private-test-credential"), false) + if (status === 200) assert.ok(result.models.some(m => m.id === "spark-x2.5")) + if (status === 401) assert.equal(result.discoveryStatus, "auth_error") + } + const failed = await discoverModels(async () => { throw new Error("private-test-credential") }) + assert.equal(failed.discoveryStatus, "unreachable") + assert.equal(resolveModelId("manual-alias", failed.defaultModel, failed.models), "manual-alias") + const empty = await discoverModels(async () => new Response('{"data":[]}')) + assert.equal(empty.discoveryStatus, "fallback") + } finally { + for (const [name, value] of Object.entries({ OPENAI_URL: previous.url, OPENAI_MODEL: previous.model, OPENAI_API: previous.api })) { + if (value === undefined) delete process.env[name]; else process.env[name] = value + } + } +}) + +test("models endpoint follows existing bearer protection and hides deployment credential", async () => { + const { GET } = await import("../app/api/v1/llm/models/route") + const { NextRequest } = await import("next/server") + const previousKey = process.env.NODEPAD_API_KEY + const previousUrl = process.env.OPENAI_URL + process.env.NODEPAD_API_KEY = "nodepad-test-token" + delete process.env.OPENAI_URL + try { + assert.equal((await GET(new NextRequest("http://localhost/api/v1/llm/models"))).status, 401) + const response = await GET(new NextRequest("http://localhost/api/v1/llm/models", { headers: { Authorization: "Bearer nodepad-test-token" } })) + assert.equal(response.status, 200) + assert.equal((await response.json()).configured, false) + assert.equal(response.headers.get("cache-control"), "no-store") + } finally { + if (previousKey === undefined) delete process.env.NODEPAD_API_KEY; else process.env.NODEPAD_API_KEY = previousKey + if (previousUrl === undefined) delete process.env.OPENAI_URL; else process.env.OPENAI_URL = previousUrl + } +}) + +test("browser custom endpoint settings keep arbitrary manual selections", async () => { + const { loadAIConfig } = await import("../lib/ai-settings") + const priorWindow = Object.getOwnPropertyDescriptor(globalThis, "window") + const priorStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage") + Object.defineProperty(globalThis, "window", { configurable: true, value: {} }) + Object.defineProperty(globalThis, "localStorage", { configurable: true, value: { + getItem: () => JSON.stringify({ apiKey: "browser-test-key", provider: "openai", customBaseUrl: "https://custom.example/v1", modelId: "manual/not-enumerated", webGrounding: true }), + } }) + try { + const config = loadAIConfig() + assert.equal(config?.modelId, "manual/not-enumerated") + assert.equal(config?.customBaseUrl, "https://custom.example/v1") + assert.equal(config?.apiKey, "browser-test-key") + assert.equal(config?.supportsGrounding, false) + } finally { + if (priorWindow) Object.defineProperty(globalThis, "window", priorWindow); else Reflect.deleteProperty(globalThis, "window") + if (priorStorage) Object.defineProperty(globalThis, "localStorage", priorStorage); else Reflect.deleteProperty(globalThis, "localStorage") + } +}) diff --git a/tests/predeploy-models.test.ts b/tests/predeploy-models.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d81bf9ebde98982d047cc03c2d267b1f026c66e --- /dev/null +++ b/tests/predeploy-models.test.ts @@ -0,0 +1,98 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { readFileSync } from "node:fs" +import { completeChat, discoverModels, getEnvLlmConfig, getProviderStatus, normalizeOpenAiUrl } from "../lib/ai/openai-provider" +import { resolveModelId } from "../lib/model-selection" + +const secret = "SUPER_SECRET_OPENAI_928374" +async function configured(run: () => Promise) { + const names = ["OPENAI_URL", "OPENAI_API", "OPENAI_MODEL"] + const before = names.map(name => process.env[name]) + Object.assign(process.env, { OPENAI_URL: "https://llm.example.test", OPENAI_API: secret, OPENAI_MODEL: "qwythos-9b" }) + try { await run() } finally { names.forEach((name, i) => { if (before[i] === undefined) delete process.env[name]; else process.env[name] = before[i] }) } +} + +test("MODEL-01 environment URL variants normalize exactly once", async () => configured(async () => { + for (const suffix of ["", "/", "/v1", "/v1/"]) assert.equal(normalizeOpenAiUrl(`https://llm.example.test${suffix}`), "https://llm.example.test/v1") + assert.equal(getEnvLlmConfig().model, "qwythos-9b") +})) +test("MODEL-02/03 arbitrary IDs and missing configured default remain available", async () => configured(async () => { + const ids = ["Spark-X2.5", "Qwen/Qwen3.8-27B-FP8", "strange/custom:model@v2"] + const result = await discoverModels(async () => new Response(JSON.stringify({ data: ids.map(id => ({ id })) }))) + assert.deepEqual(result.models.map(m => m.id), ["qwythos-9b", ...ids]) +})) +test("MODEL-04 discovery failures all retain default; no commercial fallback", async () => configured(async () => { + const mocks: typeof fetch[] = [async () => new Response("", { status: 404 }), async () => new Response("", { status: 500 }), + async () => { throw new DOMException("timed out", "AbortError") }, async () => { throw new Error("offline") }, + async () => new Response("not JSON"), async () => new Response('{"data":[]}')] + for (const mock of mocks) { + const result = await discoverModels(mock) + assert.deepEqual(result.models, [{ id: "qwythos-9b", label: "qwythos-9b" }]) + assert.equal(resolveModelId("", result.defaultModel, result.models), "qwythos-9b") + } +})) +test("MODEL-05/06/07 opaque persisted/manual selection wins over defaults", () => { + const selected = JSON.parse(JSON.stringify({ modelId: "Spark-X2.5" })) + assert.equal(resolveModelId(selected.modelId, "qwythos-9b", []), "Spark-X2.5") + assert.equal(resolveModelId("my-private-model-v37", "qwythos-9b", []), "my-private-model-v37") + assert.equal(resolveModelId("", "", []), "") +}) +test("MODEL-08/09 settings and main UI share refreshable registry without selection mutation", () => { + const settings = readFileSync("lib/ai-settings.ts", "utf8") + const sidebar = readFileSync("components/project-sidebar.tsx", "utf8") + const hook = readFileSync("lib/use-available-models.ts", "utf8") + assert.match(settings, /useAvailableModels\(settings\)/) + assert.match(sidebar, /useAvailableModels\(draft\)/) + assert.match(sidebar, /onClick=\{refresh\}/) + assert.match(hook, /setRevision\(n => n \+ 1\)/) + assert.doesNotMatch(hook, /updateSettings|localStorage\.setItem/) + assert.match(sidebar, /Use custom model ID/) +}) +test("MODEL-10/11 completion uses default or explicit arbitrary model", async () => configured(async () => { + for (const selected of [undefined, "Spark-X2.5"]) { + const result = await completeChat({ messages: [{ role: "user", content: "test" }], model: selected }, async (url, options) => { + assert.equal(url, "https://llm.example.test/v1/chat/completions") + assert.equal(JSON.parse(String(options?.body)).model, selected || "qwythos-9b") + assert.ok((options?.headers as Record).Authorization === `Bearer ${secret}`, "provider bearer sent server-side") + return new Response(JSON.stringify({ choices: [{ message: { content: "NODEPAD_LLM_OK" } }] })) + }) + assert.equal(result.model, selected || "qwythos-9b") + } +})) +test("MODEL-12 provider errors classified and secret safe, including repeated echoes", async () => configured(async () => { + for (const status of [401, 403, 404, 429, 500]) { + await assert.rejects(() => completeChat({ messages: [] }, async () => new Response(`${secret} ${secret}`, { status })), error => { + assert.equal(String(error).includes(secret), false) + assert.match(String(error), status === 401 || status === 403 ? /authentication/ : status === 404 ? /not found/ : status === 429 ? /rate limit/ : /request failed/) + return true + }) + } + await assert.rejects(() => completeChat({ messages: [] }, async () => { throw new DOMException("timeout", "AbortError") }), /timed out/) + for (const body of ["bad JSON", "{}", '{"choices":[]}']) await assert.rejects(() => completeChat({ messages: [] }, async () => new Response(body))) +})) +test("SEC-01/03 provider status exception cannot reveal credential", async () => configured(async () => { + const result = await getProviderStatus(async () => { throw new Error(`failed ${secret} ${secret}`) }) + assert.equal(JSON.stringify(result).includes(secret), false) +})) + + +test("MODEL-07 actual settings reload preserves selection without deployment credential", async () => { + const { loadAIConfig, setDeploymentModel } = await import("../lib/ai-settings") + const originalWindow = Object.getOwnPropertyDescriptor(globalThis, "window") + const originalStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage") + const persisted = JSON.stringify({ modelId: "Spark-X2.5" }) + Object.defineProperty(globalThis, "window", { configurable: true, value: {} }) + Object.defineProperty(globalThis, "localStorage", { configurable: true, value: { getItem: () => persisted } }) + setDeploymentModel("qwythos-9b") + try { + const loaded = loadAIConfig() + assert.equal(loaded?.modelId, "Spark-X2.5") + assert.equal(loaded?.apiKey, "") + assert.equal(loaded?.customBaseUrl, "/api/v1/llm") + assert.equal(persisted.includes(secret), false) + } finally { + setDeploymentModel("") + if (originalWindow) Object.defineProperty(globalThis, "window", originalWindow); else Reflect.deleteProperty(globalThis, "window") + if (originalStorage) Object.defineProperty(globalThis, "localStorage", originalStorage); else Reflect.deleteProperty(globalThis, "localStorage") + } +}) diff --git a/tests/predeploy-routes.test.ts b/tests/predeploy-routes.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..189c18e5949dd73f0ad05982b59ef316b9a4bd16 --- /dev/null +++ b/tests/predeploy-routes.test.ts @@ -0,0 +1,46 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { mkdtempSync, rmSync } from "node:fs" +import { tmpdir } from "node:os" +import path from "node:path" +import { NextRequest } from "next/server" +import { POST as login } from "../app/api/auth/login/route" +import { nodepadStore } from "../lib/server" +import { POST as mutation } from "../app/api/v1/[...path]/route" +import { createSession, isAuthenticated, loginRetryAfter, recordLoginFailure, SESSION_COOKIE } from "../lib/auth/server" + +test("AUTH-05/API-02 malformed/empty login and limiter do not invalidate existing sessions", async () => { + const dir = mkdtempSync(path.join(tmpdir(), "predeploy-routes-")) + const names = ["NODEPAD_LOGIN_KEY", "NODEPAD_AUTH_DIR", "NODEPAD_API_KEY"] + const before = names.map(name => process.env[name]) + Object.assign(process.env, { NODEPAD_LOGIN_KEY: "routes-human-fixture", NODEPAD_AUTH_DIR: dir, NODEPAD_API_KEY: "routes-machine-fixture" }) + try { + const token = createSession() + for (const body of ["{", "{}", '{"accessKey":""}']) { + const result = await login(new NextRequest("http://localhost/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body })) + assert.equal(result.status, 401) + assert.deepEqual(await result.json(), { error: "Invalid access key." }) + } + for (let i = 0; i < 5; i++) recordLoginFailure("fixture-ip") + assert.ok(loginRetryAfter("fixture-ip") > 0) + assert.equal(isAuthenticated(new NextRequest("http://localhost/", { headers: { Cookie: `${SESSION_COOKIE}=${token}` } })), true) + } finally { + names.forEach((name, i) => { if (before[i] === undefined) delete process.env[name]; else process.env[name] = before[i] }) + rmSync(dir, { recursive: true, force: true }) + } +}) +test("SEC-05 oversized chunked mutations rejected without Content-Length", async () => { + const previous = process.env.NODEPAD_API_KEY + const previousRoot = nodepadStore.root + const temporary = mkdtempSync(path.join(tmpdir(), "predeploy-body-")) + nodepadStore.root = temporary + process.env.NODEPAD_API_KEY = "routes-machine-fixture" + try { + const body = new ReadableStream({ start(controller) { controller.enqueue(new TextEncoder().encode(JSON.stringify({ claim: "x".repeat(1_000_001) }))); controller.close() } }) + const req = new NextRequest("http://localhost/api/v1/workspaces/oversized/evidence", { method: "POST", headers: { Authorization: "Bearer routes-machine-fixture", "Content-Type": "application/json" }, body, duplex: "half" } as ConstructorParameters[1]) + assert.equal(req.headers.has("content-length"), false) + const response = await mutation(req, { params: Promise.resolve({ path: ["workspaces", "oversized", "evidence"] }) }) + assert.equal(response.status, 400) + assert.equal((await response.json()).error, "request_body_too_large") + } finally { nodepadStore.root = previousRoot; rmSync(temporary, { recursive: true, force: true }); if (previous === undefined) delete process.env.NODEPAD_API_KEY; else process.env.NODEPAD_API_KEY = previous } +}) diff --git a/tests/predeploy-security.test.ts b/tests/predeploy-security.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..38e9e2a14f9291f69df54bb73fbf26cdf7a9dbe4 --- /dev/null +++ b/tests/predeploy-security.test.ts @@ -0,0 +1,26 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { readFileSync } from "node:fs" +import { serializeWorkspace, restoreSerialized } from "../lib/github-sync/serialization" +import { exportBrain } from "../lib/brain/export" +import { isBlockedHost } from "../app/api/fetch-url/route" +import { fixture } from "./helpers" + +test("SEC-01/CORE-10/11 backup, brain and restoration exclude auth/model metadata", () => { + const w = fixture() + const secrets = ["SUPER_SECRET_LOGIN_928374", "SUPER_SECRET_NODEPAD_API_928374", "SUPER_SECRET_OPENAI_928374"] + w.nodes.push({ id: "safe", workspaceId: w.id, kind: "observation", title: "safe", metadata: { NODEPAD_LOGIN_KEY: secrets[0], NODEPAD_API_KEY: secrets[1], OPENAI_API: secrets[2], sessionToken: "private-session", authorization: "Bearer private" }, version: 1, createdAt: w.createdAt, updatedAt: w.updatedAt, provenance: { origin: "human", createdAt: w.createdAt } }) + const snapshot = serializeWorkspace(w) + for (const secret of [...secrets, "private-session"]) assert.equal(JSON.stringify(snapshot).includes(secret), false) + for (const secret of secrets) assert.equal(JSON.stringify(exportBrain(w)).includes(secret), false) + assert.equal(restoreSerialized(snapshot).nodes.length, 1) +}) +test("SEC-04 SSRF localhost/private/file targets blocked", () => { + for (const url of ["http://localhost", "http://127.0.0.1", "http://10.0.0.1", "http://169.254.169.254", "http://192.168.1.1", "http://[::1]", "file:///etc/passwd"]) assert.equal(isBlockedHost(url), true) +}) +test("AUTH-04/12 timing-safe comparison and Automaker-derived login UI states", () => { + assert.match(readFileSync("lib/auth/server.ts", "utf8"), /timingSafeEqual/) + const source = readFileSync("components/login-view.tsx", "utf8") + for (const text of ["Authentication Required", "Nodepad", 'type="password"', "Authenticating", "Invalid access key", "Server Unavailable", "Retry Connection"]) assert.ok(source.includes(text)) + assert.doesNotMatch(source.replace(/^\/\/.*$/gm, ""), /Automaker/) +}) diff --git a/tests/reasoning.test.ts b/tests/reasoning.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..84d6579123fa030eb2dc12d4a1999c6299ee3f0c --- /dev/null +++ b/tests/reasoning.test.ts @@ -0,0 +1,228 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { mkdtemp, rm } from "node:fs/promises" +import path from "node:path" +import { tmpdir } from "node:os" +import { OpenAiEvidenceAdjudicator, uncertain } from "../lib/adjudication/openai" +import { adjudicateEvidence } from "../lib/adjudication/pipeline" +import { SemanticLayer } from "../lib/embeddings" +import { EmbeddingCache } from "../lib/embeddings/cache" +import { LlmSynthesisGenerator } from "../lib/synthesis/llm" +import { executeSynthesis, DeterministicSynthesisGenerator } from "../lib/synthesis/runner" +import { matchOrCreateHypothesis } from "../lib/hypotheses/matching" +import { runHeartbeat } from "../lib/heartbeat" +import { FileNodepadStore } from "../lib/persistence/file-store" +import { serializeWorkspace, restoreSerialized } from "../lib/github-sync/serialization" +import type { ChatCompletion } from "../lib/ai/reasoning" +import type { AdjudicationInput } from "../lib/adjudication/provider" +import { fixture, evidence, hypothesis } from "./helpers" +const interpretation = (extra: Record = {}) => ({ relevance: 95, confidence: 95, relation: "supports", directness: "direct", rationale: "Independent observation directly confirms the claim.", ...extra }) +const chat = (value: unknown): ChatCompletion => async request => ({ content: JSON.stringify(value), model: request.model || "default", provider: "openai-compatible" }) +async function configured(run: () => Promise) { + const names = ["OPENAI_MODEL", "OPENAI_URL", "OPENAI_API", "NODEPAD_LLM_ADJUDICATION_MODEL", "NODEPAD_LLM_SYNTHESIS_MODEL", "NODEPAD_LLM_ESCALATION_MODEL", "NODEPAD_SYNTHESIS_MODE", "NODEPAD_MAX_RESEARCH_TASKS_PER_HEARTBEAT"] + const before = names.map(n => process.env[n]) + Object.assign(process.env, { OPENAI_MODEL: "default-alias", OPENAI_URL: "http://fixture.invalid/v1", OPENAI_API: "REASONING_SECRET_FIXTURE", NODEPAD_LLM_ADJUDICATION_MODEL: "fast-alias", NODEPAD_LLM_SYNTHESIS_MODEL: "synthesis-alias", NODEPAD_SYNTHESIS_MODE: "hybrid", NODEPAD_MAX_RESEARCH_TASKS_PER_HEARTBEAT: "0" }) + delete process.env.NODEPAD_LLM_ESCALATION_MODEL + try { await run() } finally { names.forEach((n,i) => { if (before[i] === undefined) delete process.env[n]; else process.env[n] = before[i] }) } +} +async function temporary(run: (dir: string) => Promise) { const dir = await mkdtemp(path.join(tmpdir(), "nodepad-reasoning-")); try { await run(dir) } finally { await rm(dir,{recursive:true,force:true}) } } +const input = (): AdjudicationInput => ({ hypothesis: hypothesis(), evidence: evidence("e1", "support"), runId: "test-run", inputHash: "test-hash" }) +const proposal = (statement: string) => ({ statement, hypothesisType: "problem", derivedFrom: [] }) + +test("moderate lexical and semantic similarity creates no strategic relationship", () => { + const w = fixture() + matchOrCreateHypothesis(w, proposal("Customers need faster checkout payment"), "s1", "r1") + matchOrCreateHypothesis(w, proposal("Customers need simpler checkout navigation"), "s2", "r2", { similarity: () => .6 }) + assert.equal(w.hypotheses.length, 2) + assert.equal(w.edges.some(e => ["alternative_to", "supersedes", "depends_on", "supports", "contradicts"].includes(e.type)), false) +}) +test("high-confidence fast model distinguishes disconfirm objective from supportive observation", async () => configured(async () => { + const i = input(); i.task = { id: "task", workspaceId: "fixture", hypothesisIds: ["h1"], question: "Try falsifying", objective: "disconfirm", preferredSources: [], requiredSourceDiversity: 1, maxCostClass: "free", status: "running", priority: 1, attempts: 0, maxAttempts: 1, createdBy: "test" } + let calls = 0 + const adjudicator = new OpenAiEvidenceAdjudicator(async request => { + calls++; assert.equal(request.model,"fast-alias"); assert.ok(request.responseFormat?.json_schema?.strict) + assert.match(request.messages[0].content, /objective is NOT evidence relation/) + return chat(interpretation())(request) + }) + const result = await adjudicator.adjudicate(i) + assert.equal(result.relation,"supports"); assert.equal(result.fallback,false); assert.equal(calls,1) + assert.equal(result.promptVersion,"evidence-v1"); assert.deepEqual(result.sourceEvidenceIds,["e1"]) +})) +test("low-confidence strategic case escalates; missing/failed escalation stays uncertain", async () => configured(async () => { + const i = input(); i.hypothesis.strategicImportance = 90 + const calls: string[] = [] + const engine = new OpenAiEvidenceAdjudicator(async request => { + calls.push(request.model || "") + return chat(interpretation({ confidence: request.model === "expert-alias" ? 96 : 40 }))(request) + }) + assert.equal((await engine.adjudicate(i)).relation,"uncertain") + process.env.NODEPAD_LLM_ESCALATION_MODEL = "expert-alias" + const result = await engine.adjudicate(i) + assert.equal(result.relation,"supports"); assert.equal(result.escalated,true); assert.equal(result.model,"expert-alias") + assert.deepEqual(calls,["fast-alias","fast-alias","expert-alias"]) + i.hypothesis.strategicImportance = 10 + assert.equal((await engine.adjudicate(i)).relation,"uncertain") + const broken = new OpenAiEvidenceAdjudicator(async request => { if(request.model === "expert-alias")throw new Error("timeout");return chat(interpretation({confidence:40}))(request) }) + i.hypothesis.strategicImportance=90 + assert.equal((await broken.adjudicate(i)).relation,"uncertain") +})) +test("malformed/prose/authority-injection LLM responses cannot become evidence relations", async () => configured(async () => { + for (const value of ["prose", '{bad', JSON.stringify(interpretation({confidence:101})), JSON.stringify(interpretation({qualification:"qualified"})), JSON.stringify({relation:"supports"})]) { + const result = await new OpenAiEvidenceAdjudicator(async () => ({content:value,model:"bad",provider:"openai-compatible"})).adjudicate(input()) + assert.equal(result.relation,"uncertain"); assert.equal(result.fallback,true) + } +})) +test("embedding prefilter gates LLM, accepted relations produce deterministic transition audit and durable backup", async () => configured(async () => temporary(async dir => { + for (const related of [false,true]) { + const w = fixture(); w.hypotheses=[hypothesis()]; w.evidence=[evidence("e1","support")] + const h = w.hypotheses[0] + const layer = new SemanticLayer({ async embed(texts){return texts.map(t => t === h.statement && !related ? [0,1] : [1,0])}, async health(){return {enabled:true,provider:"mock",model:"mock",status:"available"}} }, new EmbeddingCache(path.join(dir,String(related)),"mock","mock")) + let calls=0 + const record = await adjudicateEvidence(w,h,w.evidence[0],{runId:"run",layer,adjudicator:new OpenAiEvidenceAdjudicator(async request=>{calls++;return chat(interpretation())(request)})}) + assert.equal(calls,related?1:0); assert.equal(w.evidence.length,1) + assert.equal(h.supportingEvidence.length,related?1:0) + if(related){ + assert.ok(h.evidenceScore>0) + assert.ok(w.events.some(e=>e.causedBy?.includes(record.id))) + const event=w.events.find(e=>e.type==="hypothesis.qualification_changed")! + assert.ok((event.after as any).adjudicationIds.includes(record.id)); assert.equal(typeof(event.before as any).confidence,"number") + } else assert.equal(h.qualification,"unresolved") + // Fixture nodes are required by the graph validator. + w.nodes=[{id:h.id,workspaceId:w.id,kind:"hypothesis",title:h.title,metadata:{},version:1,createdAt:w.createdAt,updatedAt:w.updatedAt,provenance:{origin:"human",createdAt:w.createdAt}},{id:"e1",workspaceId:w.id,kind:"evidence",title:"Evidence",metadata:{},version:1,createdAt:w.createdAt,updatedAt:w.updatedAt,provenance:{origin:"human",createdAt:w.createdAt}}] + const snapshot=serializeWorkspace(w) + assert.equal(restoreSerialized(snapshot).adjudications?.[0].id,record.id) + assert.equal(JSON.stringify(snapshot).includes("REASONING_SECRET_FIXTURE"),false) + } +}))) +test("neutral, uncertain and below-threshold validated interpretations create no score-bearing edge",async()=>configured(async()=>{ + for(const value of [interpretation({relation:"neutral"}),interpretation({relation:"uncertain"}),interpretation({relevance:30})]){ + const w=fixture();w.hypotheses=[hypothesis()];w.evidence=[evidence("e1","support")] + await adjudicateEvidence(w,w.hypotheses[0],w.evidence[0],{runId:"r",adjudicator:new OpenAiEvidenceAdjudicator(chat(value))}) + assert.equal(w.edges.length,0);assert.equal(w.hypotheses[0].evidenceScore,0);assert.equal(w.adjudications?.length,1) + } +})) +const synthesis = {title:"Root cause",statement:"Manual coordination may explain delays",rationale:"Test this explanation.",unknowns:["Causal mechanism"],suggestedResearch:["Observe workflow"],generationConfidence:80,hypotheses:[{title:"Coordination",statement:"Coordination creates delays",hypothesisType:"root_cause",derivedFrom:["e1"],rationale:"Needs validation"}]} +test("LLM synthesis emits unvalidated proposals with complete safe provenance and role routing",async()=>configured(async()=>{ + const w=fixture();w.evidence=[evidence("e1","support")] + const output=await executeSynthesis(w,"S012","run",new LlmSynthesisGenerator(async request=>{assert.equal(request.model,"synthesis-alias");return chat(synthesis)(request)})) + assert.deepEqual(output.proposals[0].supportingEvidence,[]);assert.deepEqual(output.proposals[0].contradictingEvidence,[]) + const h=matchOrCreateHypothesis(w,output.proposals[0],output.result.id,"run").hypothesis + assert.equal(h.qualification,"unresolved");assert.equal(h.evidenceScore,0) + assert.equal(output.result.provenance.reasoning?.provider,"openai-compatible") + assert.equal(output.result.provenance.reasoning?.model,"synthesis-alias") + assert.equal(h.generation.reasoning?.promptVersion,"synthesis-v1") +})) +test("hybrid synthesis fails safely; mechanical definitions remain deterministic; budgets bound requests",async()=>configured(async()=>{ + for(const failure of [async()=>{throw new Error("offline")},async()=>({content:"bad JSON",model:"m",provider:"openai-compatible" as const})]){ + const w=fixture();w.evidence=[evidence("e1","support")] + const output=await executeSynthesis(w,"S012","r",new LlmSynthesisGenerator(failure)) + assert.equal(output.result.provenance.reasoning?.fallback,true) + assert.ok(output.result.statement) + } + let calls=0;const generator=new LlmSynthesisGenerator(async request=>{calls++;return chat(synthesis)(request)},1) + for(const type of ["S001","S012","S124"]){const w=fixture();w.evidence=[evidence("e1","support")];await executeSynthesis(w,type,"r",generator)} + assert.equal(calls,1) + process.env.NODEPAD_SYNTHESIS_MODE="deterministic" + const w=fixture();w.evidence=[evidence("e1","support")] + const output=await executeSynthesis(w,"S012","r",new DeterministicSynthesisGenerator()) + assert.equal(output.result.provenance.reasoning?.provider,"deterministic") +})) +test("heartbeat embedding/adjudication/rescore loop survives inference failures and retains audit",async()=>configured(async()=>temporary(async dir=>{ + for(const failure of [false,true]){ + const store=new FileNodepadStore(path.join(dir,String(failure))),w=fixture();w.hypotheses=[hypothesis()] + w.researchTasks=[{id:"t",workspaceId:w.id,hypothesisIds:["h1"],question:"falsify",objective:"disconfirm",preferredSources:[],requiredSourceDiversity:1,maxCostClass:"free",status:"running",priority:1,attempts:0,maxAttempts:1,createdBy:"test"}] + w.researchResults=[{id:"r",taskId:"t",providerId:"mock",title:"Observed success",excerpt:"Users completed the task",retrievedAt:new Date().toISOString()}] + await store.saveWorkspace(w) + const adjudicator=new OpenAiEvidenceAdjudicator(async request=>{if(failure)throw new DOMException("timeout","AbortError");return chat(interpretation())(request)}) + const semanticLayer = new SemanticLayer({async embed(texts){return texts.map(()=>[1,0])},async health(){return {enabled:true,provider:"mock",model:"mock",status:"available"}}},new EmbeddingCache(path.join(dir,String(failure)),"mock","v1")) + const result=await runHeartbeat(store,w.id,{runId:"hb",adjudicator,semanticLayer,providers:[],synthesisIds:[]}) + assert.equal(result.status,"completed") + const after=await store.getWorkspace(w.id) + assert.equal(after.adjudications?.length,1) + assert.equal(after.adjudications?.[0].semanticRelevance,1) + assert.equal(after.hypotheses[0].supportingEvidence.length,failure?0:1) + assert.equal(after.hypotheses[0].contradictingEvidence.length,0) + } +}))) + +test("role-specific models fall back to OPENAI_MODEL; deterministic mode never requires LLM",async()=>configured(async()=>{ + delete process.env.NODEPAD_LLM_ADJUDICATION_MODEL + const result=await new OpenAiEvidenceAdjudicator(async request=>{assert.equal(request.model,"default-alias");return chat(interpretation())(request)}).adjudicate(input()) + assert.equal(result.model,"default-alias") + process.env.NODEPAD_SYNTHESIS_MODE="deterministic" + delete process.env.OPENAI_URL;delete process.env.OPENAI_MODEL + const w=fixture();w.evidence=[evidence("e1","support")] + const output=await executeSynthesis(w,"S012","r") + assert.equal(output.result.provenance.reasoning?.provider,"deterministic") +})) +test("research submission and subsequent heartbeat reuse evidence and adjudication for every target",async()=>configured(async()=>temporary(async dir=>{ + const {NodepadService}=await import("../lib/services/nodepad") + const store=new FileNodepadStore(dir),w=fixture() + w.hypotheses=[hypothesis(),{...hypothesis(),id:"h2",statement:"A competing explanation"}] + w.researchTasks=[{id:"t",workspaceId:w.id,hypothesisIds:["h1","h2"],question:"try falsifying",objective:"disconfirm",preferredSources:[],requiredSourceDiversity:1,maxCostClass:"free",status:"claimed",priority:1,attempts:0,maxAttempts:1,createdBy:"test"}] + await store.saveWorkspace(w) + let calls=0;const adjudicator=new OpenAiEvidenceAdjudicator(async request=>{calls++;return chat(interpretation())(request)}) + const service=new NodepadService(store,{adjudicator}) + const actor={actorType:"agent" as const,actorId:"test"} + const result=await service.submitResearchResult(w.id,"t",{providerId:"fixture",title:"Observed success",excerpt:"Actual evidence supports the hypothesis",relation:"contradicts"},actor,"same") + assert.equal(calls,2) + const after=await store.getWorkspace(w.id) + assert.equal(after.evidence.length,1);assert.equal(after.adjudications?.length,2) + assert.ok(after.hypotheses.every(h=>h.supportingEvidence.length===1&&h.contradictingEvidence.length===0)) + const repeat=await service.submitResearchResult(w.id,"t",{providerId:"fixture",title:"Observed success",excerpt:"same"},actor,"same") + assert.equal(repeat.id,result.id);assert.equal(calls,2) + await runHeartbeat(store,w.id,{runId:"after-submit",providers:[],synthesisIds:[],adjudicator}) + const final=await store.getWorkspace(w.id) + assert.equal(final.evidence.length,1);assert.equal(final.adjudications?.length,2);assert.equal(calls,2) +}))) +test("provider output cannot leak quoted credentials into adjudication or synthesis provenance",async()=>configured(async()=>{ + process.env.OPENAI_API='fixture-secret-with-"quotes"' + const record=await new OpenAiEvidenceAdjudicator(chat(interpretation({rationale:`Reason ${process.env.OPENAI_API}`}))).adjudicate(input()) + assert.equal(record.rationale.includes(process.env.OPENAI_API),false) + const w=fixture();w.evidence=[evidence("e1","support")] + const output=await executeSynthesis(w,"S012","r",new LlmSynthesisGenerator(chat({...synthesis,rationale:`Reason ${process.env.OPENAI_API}`}))) + assert.equal(JSON.stringify(output).includes(process.env.OPENAI_API),false) +})) + +test("model failures are bounded within a run and retried by a fresh run",async()=>configured(async()=>{ + const engine=new OpenAiEvidenceAdjudicator(async()=>{calls++;throw new Error("offline")}) + let calls=0 + const w=fixture();w.hypotheses=[hypothesis()];w.evidence=[evidence("e1","support"),evidence("e2","support")] + for(const e of w.evidence)await adjudicateEvidence(w,w.hypotheses[0],e,{runId:"failed-run",adjudicator:engine}) + assert.equal(calls,1);assert.equal(w.adjudications?.length,2);assert.equal(w.hypotheses[0].evidenceScore,0) + await adjudicateEvidence(w,w.hypotheses[0],w.evidence[0],{runId:"retry-run",adjudicator:new OpenAiEvidenceAdjudicator(chat(interpretation()))}) + assert.equal(w.hypotheses[0].supportingEvidence.length,1) +})) +test("a confident neutral reclassification removes a prior scored link with causal audit",async()=>configured(async()=>{ + const w=fixture();w.hypotheses=[hypothesis()];w.evidence=[evidence("e1","support")] + await adjudicateEvidence(w,w.hypotheses[0],w.evidence[0],{runId:"support",adjudicator:new OpenAiEvidenceAdjudicator(chat(interpretation()))}) + assert.ok(w.hypotheses[0].evidenceScore>0) + w.hypotheses[0].statement="A revised and unrelated claim" + const record=await adjudicateEvidence(w,w.hypotheses[0],w.evidence[0],{runId:"neutral",adjudicator:new OpenAiEvidenceAdjudicator(chat(interpretation({relation:"neutral"})))}) + assert.equal(w.hypotheses[0].supportingEvidence.length,0);assert.equal(w.hypotheses[0].evidenceScore,0) + assert.equal(w.edges.some(e=>e.type==="supports"),false) + const transition=w.events.find(e=>e.causedBy?.includes(record.id))! + assert.equal((transition.after as any).relationChange.before,"supports");assert.equal((transition.after as any).relationChange.after,"neutral") +})) +test("synthesis hashes actual claim content and rejects authoritative/citation injection",async()=>configured(async()=>{ + const w=fixture();w.evidence=[evidence("e1","support")] + const first=await executeSynthesis(w,"S012","one",new DeterministicSynthesisGenerator()) + w.evidence[0].claim="A different observed fact" + const second=await executeSynthesis(w,"S012","two",new DeterministicSynthesisGenerator()) + assert.notEqual(first.result.inputHash,second.result.inputHash) + for(const value of [{...synthesis,confidence:100},{...synthesis,hypotheses:[{...synthesis.hypotheses[0],derivedFrom:["unknown-id"]}]},{...synthesis,hypotheses:[{...synthesis.hypotheses[0],supportingEvidence:["e1"]}]}]){ + const workspace=fixture();workspace.evidence=[evidence("e1","support")] + const output=await executeSynthesis(workspace,"S012","r",new LlmSynthesisGenerator(chat(value))) + assert.equal(output.result.provenance.reasoning?.fallback,true) + assert.ok(output.proposals.every(p=>p.supportingEvidence?.length===0&&p.contradictingEvidence?.length===0)) + } +})) + +test("corrupt adjudication snapshots fail restore while valid records retain provenance",async()=>configured(async()=>{ + const w=fixture();w.hypotheses=[hypothesis()];w.evidence=[evidence("e1","support")] + const record=await adjudicateEvidence(w,w.hypotheses[0],w.evidence[0],{runId:"r",adjudicator:new OpenAiEvidenceAdjudicator(chat(interpretation({relation:"neutral"})))}) + const snapshot=serializeWorkspace(w) + assert.equal(restoreSerialized(snapshot).adjudications?.[0].promptVersion,"evidence-v1") + snapshot.files["adjudications/records.ndjson"]=JSON.stringify({...record,confidence:200})+"\n" + assert.throws(()=>restoreSerialized(snapshot),/invalid interpretation certainty/) +})) diff --git a/tests/scoring.test.ts b/tests/scoring.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..18bd2885f450d7489fdf28225175ef6128e0c886 --- /dev/null +++ b/tests/scoring.test.ts @@ -0,0 +1,3 @@ +import test from "node:test";import assert from "node:assert/strict";import { qualificationForScore,scoreHypothesis } from "../lib/scoring/hypothesis";import { evidence,hypothesis } from "./helpers" +test("all ten signed qualification bands are deterministic",()=>{assert.deepEqual([-100,-79,-54,-29,-9,10,30,50,70,90].map(qualificationForScore),["strongly_disqualified","disqualified","leaning_disqualified","weakly_disfavored","unresolved","weak_signal","potentially_qualified","provisionally_qualified","qualified","strongly_qualified"])}); +test("contradiction is preserved, contested, and can reverse a hypothesis",()=>{const s=evidence("s","support","interviews"),c=evidence("c","contradict","analytics");let h=scoreHypothesis(hypothesis([s.id],[]),[s],Date.parse("2026-10-04"));assert.ok(h.evidenceScore>0);h={...h,contradictingEvidence:[c.id]};h=scoreHypothesis(h,[s,c],Date.parse("2026-10-04"));assert.equal(h.overlays.includes("contested"),true);h=scoreHypothesis({...h,supportingEvidence:[],contradictingEvidence:[c.id]},[s,c],Date.parse("2026-10-04"));assert.equal(h.qualification,"strongly_disqualified")}) diff --git a/tests/synthesis-registry.test.ts b/tests/synthesis-registry.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..744ab191a36676f75e34ab1f7a78202e6774cd74 --- /dev/null +++ b/tests/synthesis-registry.test.ts @@ -0,0 +1,3 @@ +import test from "node:test";import assert from "node:assert/strict";import { synthesisRegistry } from "../lib/synthesis/registry";import { executeSynthesis, runSynthesis } from "../lib/synthesis/runner";import { evidence,fixture } from "./helpers" +test("registry has 130 unique executable definitions",async()=>{assert.equal(synthesisRegistry.length,130);assert.equal(new Set(synthesisRegistry.map(x=>x.id)).size,130);const w=fixture();w.evidence=[evidence("e1","support")];for(const d of synthesisRegistry)assert.equal((await runSynthesis(w,d.id,"run")).synthesisType,d.id)}) +test("representative synthesis families return structured hypothesis proposals",async()=>{for(const id of ["S012","S031","S038","S051","S091","S121","S122","S124","S127","S128","S130"]){const w=fixture();w.evidence=[evidence("e1","support")];const execution=await executeSynthesis(w,id,"proposal-run");assert.equal(execution.proposals.length,1);assert.equal(execution.proposals[0].derivedFrom[0],"e1");assert.equal("qualification" in execution.proposals[0],false)}}) diff --git a/tests/validation-security-regression.test.ts b/tests/validation-security-regression.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..a7b98b2ad58d41877c9870c2b8fae36d1ae3a535 --- /dev/null +++ b/tests/validation-security-regression.test.ts @@ -0,0 +1,5 @@ +import test from "node:test";import assert from "node:assert/strict";import{readFile}from"node:fs/promises";import{fixture,evidence,hypothesis}from"./helpers";import{serializeWorkspace,restoreSerialized,snapshotContainsSecrets}from"../lib/github-sync/serialization";import{validateDecision,validateEdge,validateEvidence,validateExperiment,validateHypothesis,validateNode,validateSynthesis,validateTheme,validateWorkspace,validateWorkflowContracts}from"../lib/validation";import{isBlockedHost}from"../app/api/fetch-url/route";import{parseNodepadFile,serialiseProject}from"../lib/nodepad-format" +test("ST12 secret variants never enter snapshot and frontmatter is readable",()=>{const w=fixture();w.nodes.push({id:"n",workspaceId:w.id,kind:"observation",title:"n",metadata:{githubToken:"ghp_abcdefghijklmnopqrstuvwxyz",api_key:"sk-abcdefghijklmnopqrstuv",authorization:"Bearer abcdefghijklmnopqrst",cookie:"session=x",oauthToken:"oauth",password:"pw"},provenance:{origin:"human",createdAt:w.createdAt},createdAt:w.createdAt,updatedAt:w.updatedAt,version:1});const snap=serializeWorkspace(w),all=Object.values(snap.files).join("\n");assert.equal(snapshotContainsSecrets(snap),false);for(const value of["ghp_abcdefghijklmnopqrstuvwxyz","sk-abcdefghijklmnopqrstuv","Bearer abcdefghijklmnopqrst","session=x","oauth","password: pw"])assert.equal(all.includes(value),false);assert.match(snap.files["graph/nodes/n.md"],/schemaVersion: 1/);assert.deepEqual(serializeWorkspace(restoreSerialized(snap)).files,snap.files)}) +test("ST13 validators reject corrupt domain records",()=>{const w=fixture();w.nodes=[{id:"a",workspaceId:w.id,kind:"theme",title:"t",metadata:{},provenance:{origin:"human",createdAt:w.createdAt},createdAt:w.createdAt,updatedAt:w.updatedAt,version:1}];w.edges=[{id:"bad",workspaceId:w.id,sourceId:"a",targetId:"missing",type:"supports",provenance:{origin:"human",createdAt:w.createdAt},createdAt:w.createdAt}];assert.equal(validateWorkspace(w).valid,false);assert.equal(validateTheme(w.nodes[0]).valid,false);assert.equal(validateEdge(w.edges[0],new Set(["a"])).valid,false);assert.equal(validateNode({...w.nodes[0],version:0}).valid,false);assert.equal(validateEvidence({...evidence("e","support"),strength:7 as any}).valid,false);assert.equal(validateHypothesis({...hypothesis(),evidenceScore:101}).valid,false);assert.equal(validateExperiment({...w.nodes[0],kind:"experiment",metadata:{}}).valid,false);assert.equal(validateDecision({...w.nodes[0],kind:"decision",metadata:{}}).valid,false);assert.equal(validateSynthesis({synthesisType:"bad"} as any).valid,false);w.researchTasks=[{id:"t",workspaceId:w.id,hypothesisIds:[],question:"",objective:"discover",preferredSources:[],requiredSourceDiversity:1,maxCostClass:"free",status:"queued",priority:1,attempts:2,maxAttempts:1,createdBy:"x"}];assert.equal(validateWorkflowContracts(w).valid,false)}) +test("ST16/ST17 legacy Ghost/import export and workspace move/copy surfaces remain",async()=>{const exported=serialiseProject({id:"p",name:"P",blocks:[{id:"b",text:"text",timestamp:1,contentType:"general"}],collapsedIds:[],ghostNotes:[{id:"g",text:"ghost",category:"bridge",isGenerating:true}]});const parsed=parseNodepadFile(JSON.stringify(exported),[]);assert.equal(parsed.blocks[0].text,"text");assert.equal(parsed.ghostNotes[0].text,"ghost");assert.equal(parsed.ghostNotes[0].isGenerating,false);const page=await readFile("app/page.tsx","utf8"),tiling=await readFile("components/tiling-area.tsx","utf8"),kanban=await readFile("components/kanban-area.tsx","utf8");for(const source of[page,tiling,kanban]){assert.match(source,/onMoveToWorkspace/);assert.match(source,/onCopyToWorkspace/)}for(const view of["tiling","kanban","graph"])assert.match(page,new RegExp(view))}) +test("security blocks direct private/reserved fetch targets",()=>{for(const url of["http://localhost","http://127.0.0.1","http://169.254.169.254","http://10.0.0.1","file:///etc/passwd","http://[::1]"])assert.equal(isBlockedHost(url),true);assert.equal(isBlockedHost("https://example.com"),false)}) diff --git a/tests/vertical-loop.test.ts b/tests/vertical-loop.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..c151bcc3519ae87a84e2669a2412d847c2acec80 --- /dev/null +++ b/tests/vertical-loop.test.ts @@ -0,0 +1,105 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { mkdtemp, rm } from "node:fs/promises" +import { tmpdir } from "node:os" +import path from "node:path" +import { FileNodepadStore } from "../lib/persistence/file-store" +import { runHeartbeat } from "../lib/heartbeat" +import type { ResearchProvider, ResearchArtifact } from "../lib/research/providers" +import { evidence, fixture, hypothesis, fixtureAdjudicator } from "./helpers" +import { stableId } from "../lib/domain/id" + +class MockProvider implements ResearchProvider { + policy = { id: "web", costClass: "free" as const, concurrency: 1, enabled: true, health: "healthy" as const } + constructor(private artifacts: ResearchArtifact[], id = "web") { this.policy.id = id } + async search() { return this.artifacts } +} +const nodeForHypothesis = (h: ReturnType) => ({ id: h.id, workspaceId: "fixture", kind: "hypothesis" as const, title: h.title, body: h.statement, metadata: {}, provenance: { origin: "human" as const, actorId: "test", createdAt: h.createdAt }, createdAt: h.createdAt, updatedAt: h.updatedAt, version: 1 }) + +import { NodepadService } from "../lib/services/nodepad" + +test("external evidence canonical linkage", async () => { + const directory = await mkdtemp(path.join(tmpdir(), "nodepad-canonical-")), store = new FileNodepadStore(directory) + const workspace = fixture(), h1 = hypothesis([], []); h1.strategicImportance = 100 + workspace.hypotheses = [h1]; workspace.nodes = [nodeForHypothesis(h1)] + await store.saveWorkspace(workspace) + const service = new NodepadService(store), actor = { actorType: "system" as const, actorId: "test-client" } + + // Missing hypothesis + await assert.rejects(service.addEvidence(workspace.id, { claim: "Bad", hypothesisId: "does_not_exist" }, actor), /hypothesis_not_found/) + + // Qualification injection attempt + await assert.rejects(service.addEvidence(workspace.id, { claim: "Inject", hypothesisId: h1.id, qualification: "strongly_qualified" } as any, actor), /authoritative scoring fields/) + + // Supporting evidence + const e1 = await service.addEvidence(workspace.id, { claim: "Supporting claim", hypothesisId: h1.id, relation: "supports" }, actor, "key1") + const w1 = await store.getWorkspace(workspace.id), h1Updated = w1.hypotheses[0] + assert.equal(w1.evidence.length, 1); assert.equal(h1Updated.supportingEvidence[0], e1.id) + assert.equal(h1Updated.contradictingEvidence.length, 0) + assert.ok(w1.edges.some(edge => edge.type === "supports" && edge.sourceId === e1.id && edge.targetId === h1.id)) + assert.ok(h1Updated.evidenceScore > 0) + + // Contradicting evidence + const e2 = await service.addEvidence(workspace.id, { claim: "Contradicting claim", hypothesisId: h1.id, relation: "contradicts" }, actor, "key2") + const w2 = await store.getWorkspace(workspace.id), h1Updated2 = w2.hypotheses[0] + assert.equal(w2.evidence.length, 2); assert.equal(h1Updated2.contradictingEvidence[0], e2.id) + assert.ok(w2.edges.some(edge => edge.type === "contradicts" && edge.sourceId === e2.id && edge.targetId === h1.id)) + assert.ok(h1Updated2.evidenceScore < h1Updated.evidenceScore) + + // Neutral evidence + const e3 = await service.addEvidence(workspace.id, { claim: "Neutral claim", hypothesisId: h1.id, relation: "neutral" }, actor, "key3") + const w3 = await store.getWorkspace(workspace.id), h1Updated3 = w3.hypotheses[0] + assert.equal(w3.evidence.length, 3); assert.equal(h1Updated3.supportingEvidence.length, 1); assert.equal(h1Updated3.contradictingEvidence.length, 1) + + // Idempotency + const e1Repeat = await service.addEvidence(workspace.id, { claim: "Supporting claim", hypothesisId: h1.id, relation: "supports" }, actor, "key1") + assert.equal(e1Repeat.id, e1.id) + const w4 = await store.getWorkspace(workspace.id) + assert.equal(w4.evidence.length, 3); assert.equal(w4.edges.filter(e => e.type === "supports").length, 1) + + // Conflicting Idempotency + await assert.rejects(service.addEvidence(workspace.id, { claim: "Different claim", hypothesisId: h1.id, relation: "supports" }, actor, "key1"), /idempotency_conflict/) + + await rm(directory, { recursive: true, force: true }) +}) + +test("automatic heartbeat closes synthesis, hypothesis, research, evidence, score and brain loop", async () => { + const directory = await mkdtemp(path.join(tmpdir(), "nodepad-vertical-")), store = new FileNodepadStore(directory) + const workspace = fixture(), target = { ...hypothesis(), strategicImportance: 100, uncertainty: 100 } + workspace.evidence = [evidence("starting", "support", "initial")] + workspace.hypotheses = [target]; workspace.nodes = [nodeForHypothesis(target)] + await store.saveWorkspace(workspace) + process.env.NODEPAD_MAX_RESEARCH_TASKS_PER_HEARTBEAT = "1" + const support = [new MockProvider([{ title: "support web", excerpt: "Observed successful behavior", url: "https://support/web", relation: "supports" }], "web"), new MockProvider([{ title: "support HN", excerpt: "Observed successful behavior", url: "https://support/hn", relation: "supports" }], "hacker-news"), new MockProvider([{ title: "support GitHub", excerpt: "Observed successful behavior", url: "https://support/github", relation: "supports" }], "github")] + const first = await runHeartbeat(store, workspace.id, { adjudicator: fixtureAdjudicator, runId: "vertical-support", now: new Date("2026-10-05"), providers: support, synthesisIds: ["S012"] }) + const supported = await store.getWorkspace(workspace.id), promoted = supported.hypotheses.find(item => item.id === target.id)! + assert.equal(first.status, "completed"); assert.equal(supported.syntheses.length, 1); assert.ok(supported.syntheses[0].generatedHypotheses.length > 0) + assert.ok(supported.hypotheses.length > 1); assert.equal(supported.researchTasks.find(item => item.hypothesisIds.includes(target.id))?.status, "complete") + assert.equal(promoted.supportingEvidence.length, 3); assert.equal(promoted.qualification, "strongly_qualified"); assert.equal(promoted.promoted, true) + assert.ok(supported.edges.some(edge => edge.type === "produced")); assert.ok(supported.edges.some(edge => edge.type === "supports" && edge.targetId === target.id)) + const hypothesisCount = supported.hypotheses.length + await runHeartbeat(store, workspace.id, { adjudicator: fixtureAdjudicator, runId: "vertical-repeat", now: new Date("2026-10-05T01:00:00Z"), providers: [], synthesisIds: ["S012"] }) + const repeated = await store.getWorkspace(workspace.id); assert.equal(repeated.hypotheses.length, hypothesisCount) + const disconfirmTaskId = stableId("task", target.id, "manual-disconfirm") + repeated.researchTasks.push({ id: disconfirmTaskId, workspaceId: repeated.id, hypothesisIds: [target.id], question: "Try to falsify target", objective: "disconfirm", preferredSources: ["web"], requiredSourceDiversity: 1, maxCostClass: "free", status: "queued", priority: 101, attempts: 0, maxAttempts: 3, createdBy: "test" }) + await store.saveWorkspace(repeated) + const contradiction = new MockProvider(Array.from({ length: 6 }, (_, index) => ({ title: `contradiction ${index}`, excerpt: "Observed failure", url: `https://contradict/${index}`, relation: "contradicts" as const }))) + await runHeartbeat(store, workspace.id, { adjudicator: fixtureAdjudicator, runId: "vertical-disconfirm", now: new Date("2026-10-06"), providers: [contradiction], synthesisIds: ["S012"] }) + const demotedWorkspace = await store.getWorkspace(workspace.id), demoted = demotedWorkspace.hypotheses.find(item => item.id === target.id)! + assert.ok(demoted.contradictingEvidence.length >= 6); assert.ok(demoted.evidenceScore < 0); assert.ok(["leaning_disqualified", "disqualified", "weakly_disfavored"].includes(demoted.qualification)); assert.equal(demoted.promoted, false) + assert.ok(demotedWorkspace.events.some(event => event.type === "brain.promoted")); assert.ok(demotedWorkspace.events.some(event => event.type === "brain.demoted")); assert.ok(demotedWorkspace.events.some(event => event.type === "hypothesis.qualification_changed")) + const counts = [demotedWorkspace.hypotheses.length, demotedWorkspace.evidence.length, demotedWorkspace.researchResults.length] + const replay = await runHeartbeat(store, workspace.id, { adjudicator: fixtureAdjudicator, runId: "vertical-disconfirm", now: new Date("2026-10-06"), providers: [contradiction], synthesisIds: ["S012"] }) + const unchanged = await store.getWorkspace(workspace.id); assert.equal(replay.status, "skipped"); assert.deepEqual([unchanged.hypotheses.length, unchanged.evidence.length, unchanged.researchResults.length], counts) + await rm(directory, { recursive: true, force: true }) +}) + +test("staleness marks important hypotheses and queues one stable refresh task", async () => { + const directory = await mkdtemp(path.join(tmpdir(), "nodepad-stale-")), store = new FileNodepadStore(directory) + const workspace = fixture(), staleEvidence = evidence("old", "support", "interview"); staleEvidence.observedAt = "2020-01-01T00:00:00.000Z" + const target = { ...hypothesis(["old"]), strategicImportance: 90 }; workspace.evidence = [staleEvidence]; workspace.hypotheses = [target]; workspace.nodes = [nodeForHypothesis(target)] + await store.saveWorkspace(workspace); process.env.NODEPAD_MAX_RESEARCH_TASKS_PER_HEARTBEAT = "0"; process.env.NODEPAD_EVIDENCE_STALE_DAYS = "30" + await runHeartbeat(store, workspace.id, { runId: "stale", now: new Date("2026-10-05"), providers: [], synthesisIds: [] }) + const result = await store.getWorkspace(workspace.id); assert.equal(result.evidence[0].stale, true); assert.ok(result.hypotheses[0].overlays.includes("stale")); assert.equal(result.researchTasks.filter(item => item.objective === "refresh").length, 1); assert.ok(result.events.some(event => event.type === "hypothesis.stale")) + await rm(directory, { recursive: true, force: true }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..48d6d8209c1377f1992a937b4cce222ce01e64bf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "target": "ES6", + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/venture-analyst/LICENSE b/venture-analyst/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..03cdd5ddf1e8225d26c387d4bb33f42e877d4095 --- /dev/null +++ b/venture-analyst/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 VeyraLabs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/venture-analyst/README.md b/venture-analyst/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7e8fb32dd175647fb09c8ac939aa97e16fbcc2c4 --- /dev/null +++ b/venture-analyst/README.md @@ -0,0 +1,59 @@ +# venture-analyst + +![npm](https://img.shields.io/npm/v/@veyralabs/venture-analyst) ![license](https://img.shields.io/badge/license-MIT-green) + +Startup and SaaS idea validation skill for Claude Code. Research market evidence, map competitors, score viability, and generate concrete validation experiments - no API keys required. + +Part of [venture-suite](https://github.com/veyralabsgroup/venture-suite) by [VeyraLabs](https://github.com/veyralabsgroup). + +## Install + +```bash +npx @veyralabs/skills install venture-analyst +``` + +Or via the main package: + +```bash +npx @veyralabs/skills install venture-suite +``` + +## What it does + +Four phases, each with structured output: + +1. **Problem Discovery** - collects evidence the problem exists (HN, Reddit, GitHub issues, Google Trends) +2. **Competitor Intelligence** - maps the landscape, extracts pricing signals, finds gaps +3. **Validation Experiments** - generates 3 prioritized experiments (Mom Test, fake door, concierge MVP) +4. **Verdict** - Bull/Bear/Judge debate with a scored recommendation + +## Usage + +``` +/venture-analyst [idea description] +``` + +Example: +``` +/venture-analyst A tool that helps freelancers track billable hours without manual timers +``` + +## Zero friction + +Works immediately after install. No API keys, no configuration, no paid accounts required. + +Uses HN Algolia (no auth), Reddit .json endpoints, GitHub REST API (unauthenticated), ddgs for web search, and trendspyg for Google Trends. Auto-detects and uses available enhancements (SearXNG via Docker, optional env keys) without asking the user to configure anything. + +## Includes + +- `SKILL.md` - main orchestrator with 4-phase workflow +- `scripts/sources.py` - zero-key data collection +- `scripts/enhance_detect.py` - Level 2/3 auto-detection +- `scripts/scraper.py` - competitor page scraping with Cloudflare fallback +- `scripts/experiments.py` - validation experiment generator +- `references/` - Lean Startup, Customer Development, Mom Test, Blue Ocean, Traction +- `templates/` - experiment spec, verdict output + +## License + +MIT. Built by [VeyraLabs](https://veyralabs.com). diff --git a/venture-analyst/SKILL.md b/venture-analyst/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..65072afc8c85444e5d06128f68077053aaaef971 --- /dev/null +++ b/venture-analyst/SKILL.md @@ -0,0 +1,661 @@ +--- +name: venture-analyst +description: Startup and SaaS idea validation. Researches market evidence, maps competitors, scores viability, and generates concrete validation experiments. Zero API keys required. +--- + +# Venture Analyst + +Research a startup or SaaS idea and determine if it's worth building. No fluff - real evidence from real sources, structured reasoning, and a committed decision. + +## What this does + +Six phases, each producing structured output: + +1. **Problem Discovery** - Find evidence the problem actually exists (Reddit, HN, GitHub issues, trends) +2. **Competitor Intelligence** - Map the landscape, find gaps, extract pricing signals +3. **Validation Experiments** - Generate 3 prioritized experiments to test demand before building +4. **Verdict** - Bull/Bear/Judge debate with Confidence Engine and scored recommendation +5. **Decision Intelligence** - Contradiction detection, founder trap check, reality check, distribution plan, time-to-first-dollar +6. **Execution & Moat** - 30-day roadmap, moat scoring, PMF simulation, market timing + +## How to use + +Describe your idea. Include: +- What it does (one sentence) +- Who it's for (target customer) +- What problem it solves + +Optional: budget for experiments, market type (B2C/B2B), known competitors, founder background. + +## Phase 1 - Problem Discovery + +**Goal:** Find evidence the problem is real and people talk about it unprompted. + +Use `scripts/sources.py` to collect evidence: + +```python +from scripts.sources import search_hn, search_hn_comments, search_reddit, search_github_issues, get_trends, calculate_evidence_score + +hn_stories = search_hn(query, limit=20) +hn_comments = search_hn_comments(query, min_points=3, limit=30) +reddit_posts = search_reddit(query, limit=25, timeframe="year") +gh_issues = search_github_issues(query, limit=20) +trend_data = get_trends(keyword) +``` + +**Evidence Quality (not just quantity):** + +Evaluate source mix and signal strength, not just raw counts. + +| Source | Quality signal | Weight | +|--------|---------------|--------| +| HN Ask/Show with 50+ points | People actively seeking solutions | High | +| Reddit complaints with 100+ upvotes | Widespread frustration | High | +| GitHub issues with many reactions | Developers hitting the same wall | High | +| HN/Reddit mentions of spending money | Willingness to pay | Very high | +| Rising trend | Growing problem | High | +| Trend flat | Established problem | Medium | +| Trend declining | Dying problem | Negative | +| Generic blog posts | Low signal noise | Low | + +Evidence Quality grades: +- **A** - Multiple high-quality sources, direct pain quotes, spending signals +- **B** - Good source mix, clear pain but limited spending signals +- **C** - Some signal but concentrated in one source +- **D** - Thin signal, only indirect evidence +- **F** - Essentially no evidence + +**Synthesize findings:** + +Strongest pain signals (prioritize): +- People actively spending money on imperfect solutions +- Recurring complaints with no satisfying answer +- "Is there a tool that does X?" posts with many upvotes +- GitHub issues with many reactions, still open + +Red flags: +- Zero discussion anywhere (even if search terms varied) +- Problem exists but everyone's workaround is "good enough" +- Only a few power users care, no broad market +- Discussion peaked years ago (dying category) + +**Output format:** +``` +## Problem Evidence + +Evidence Score: [0-100] +Evidence Quality: [A/B/C/D/F] + +Source breakdown: +- HN: [n] discussions, strongest: "[quote]" +- Reddit: [n] posts, strongest: "[quote]" +- GitHub: [n] issues, strongest reaction count: [n] +- Trend: [rising/stable/declining], avg interest: [n] + +Quality notes: +- [what makes this evidence strong] +- [what's missing] +``` + +## Phase 2 - Competitor Intelligence + +**Goal:** Map who's already solving this. Find pricing, positioning gaps, weak points. + +```python +from scripts.scraper import scrape_competitor +from scripts.sources import search_github_repos, search_web + +repos = search_github_repos(f"{idea} tool", limit=8) +web_results = search_web(f"{idea} software alternatives", limit=8) + +for url in competitor_urls: + data = scrape_competitor(url) + # data: title, tagline, description, pricing, features, tech_stack +``` + +**Competitive map:** +``` +| Name | Pricing | Target | Weakness | Stars/Users | +|------|---------|--------|----------|-------------| +``` + +**Gaps to identify:** +- Price ceiling: tier missing between free and enterprise? +- Audience gap: power users vs beginners vs enterprise underserved? +- Feature gap: what do reviews complain about repeatedly? +- Distribution gap: channel nobody is using? + +**Opportunity Score vs Startup Score:** + +These are two different things. Conflating them is a common mistake. + +- **Opportunity Score** = how good is the market? (demand, size, willingness to pay) +- **Startup Score** = how viable is this as a startup? (scalability, defensibility, execution path) + +A market can be huge (high opportunity) but impossible to win as a bootstrapped startup (low startup score). Example: "A better Stripe" - huge opportunity, near-zero startup score for most founders. + +Score both separately in the competitor output section. + +## Phase 3 - Validation Experiments + +**Goal:** Before writing code, find out if people will actually pay. + +```python +from scripts.experiments import generate_experiments, format_experiment_output + +experiments = generate_experiments( + idea=idea, + target_customer=target, + market_type="b2b", # or "b2c" + competition_level="medium", + budget="zero", +) +print(format_experiment_output(experiments, idea)) +``` + +Present experiments in priority order. Cheapest + highest-signal first. + +**Mom Test enforcement** - when helping design interviews or outreach: +- See `references/mom-test.md` for good vs bad questions +- Flag any future-hypothetical question ("would you use X?") +- Replace with past-behavior questions ("how do you currently handle X?") + +**Time-To-First-Dollar estimate:** + +For each experiment path, estimate realistic time to first revenue: + +| Market type | Typical range | What accelerates it | +|------------|--------------|---------------------| +| B2C consumer SaaS | 30-90 days | Viral loop, strong landing page CTR | +| B2B SMB | 30-60 days | Warm outreach, concierge MVP | +| B2B mid-market | 60-180 days | Champion inside company, ROI clear | +| B2B enterprise | 6-18 months | Do not start here without traction | +| Developer tool (paid) | 14-45 days | Show HN, Product Hunt, X/Twitter post | +| Marketplace | 90-180+ days | Cold start problem, both sides | +| Content-led SaaS | 60-120 days | SEO takes time, need existing audience | + +This is not the time to build - it's the time from starting experiments to first paying customer. + +## Phase 4 - Verdict + +**Goal:** Simulate a debate between Bull, Bear, and Judge. Reach a committed conclusion. + +### Confidence Engine + +The confidence score must explain itself. Not "Confidence: High" alone. Show the reasoning. + +``` +Confidence: [0-100] + +High confidence because: ++ [n] Reddit mentions across [n] subreddits ++ [n] GitHub issues with [n]+ reactions ++ [n] competitors validated with real pricing ++ Rising trend over [period] ++ [specific spending signal found] + +Confidence reduced because: +- [specific gap, e.g. weak monetization signals] +- [e.g. no willingness-to-pay evidence found] +- [e.g. fragmented ICP - different pain in different segments] +- [e.g. limited search volume data] +``` + +This matters because users need to see the reasoning is transparent, not magic. + +### Bull case (write this first) +- Strongest evidence for building it +- Market timing arguments +- Why this team / why now +- Best-case scenario with numbers + +### Bear case (steelman the opposition) +- Strongest evidence AGAINST building it +- Why existing solutions might be good enough +- Market risks, timing risks, competition risks +- Why it might fail even if the problem is real + +### Judge verdict + +Apply these criteria: + +| Signal | Weight | +|--------|--------| +| Evidence score > 60 | +2 | +| Evidence quality A or B | +1 | +| Trend = rising | +1 | +| Competitors have clear weakness | +1 | +| No dominant player (>50% market) | +1 | +| B2B with willingness-to-pay signals | +1 | +| Price ceiling exists | +1 | +| Evidence score < 30 | -3 | +| Evidence quality D or F | -2 | +| Trend = declining | -2 | +| 1+ competitor with >100k users + free tier | -2 | +| Problem is niche (<10k potential users) | -1 | +| Founder trap detected (high priority) | -2 | +| Reality check failed | -2 | + +**Verdict:** +``` +Recommendation: BUILD / VALIDATE FIRST / AVOID +Confidence: [0-100] +Score: [+N or -N] + +Judge's reasoning: +[2-3 sentences. Direct. No hedging. Reference the specific evidence that tipped the scale.] +``` + +## Phase 5 - Decision Intelligence + +Run this after the Verdict. It does not change the verdict score - it adds context that makes the decision actionable. + +### Contradiction Detector + +Look for contradictions between sources. The most valuable insight is often in the gap between what people say and what the market shows. + +Common contradiction patterns: + +**Pain high, market growing:** +``` +Reddit: strong complaints about current solutions +Competitors: growing, raising funding + +Interpretation: users hate existing tools but still pay. +This is an improvement opportunity, not a replacement play. +Consider: better UX, better pricing, better ICP focus. +``` + +**Pain high, no competitors:** +``` +Evidence of real pain. +Zero viable competitors found. + +Two interpretations: +1. Blue ocean - opportunity exists +2. Graveyard - others tried and died + +Check: search for failed startups in this space. If multiple failed, +investigate why before treating this as opportunity. +``` + +**Trend rising, community silent:** +``` +Google Trends shows growing interest. +Reddit/HN have minimal discussion. + +Possible: problem is new, community hasn't formed yet (early). +Risk: trend is noise, not genuine demand. +``` + +**Competition dense, pain still high:** +``` +Multiple established competitors. +Pain signals still strong and recent. + +Interpretation: nobody has actually solved it. +Look for the structural reason: price, UX, missing feature, wrong ICP. +``` + +When a contradiction is found, output: +``` +Contradiction detected: [name] + +[source A] shows: [signal] +[source B] shows: [signal] + +Most likely interpretation: [explanation] +What to verify: [specific experiment or question that resolves this] +``` + +### Founder Trap Detector + +Check `references/founder-traps.md` for full criteria. + +Evaluate the evidence against each trap pattern. Flag any trap where 3+ signals match. + +Output when trap found: +``` +Founder Trap: [trap name] +Evidence: [which signals matched] +Risk level: [high/medium] +Does not invalidate the opportunity, but changes the execution approach. +``` + +### Reality Check + +This is not about the market - it's about whether this founder can execute this idea. + +Gather context: +- What is the founder's background? (from idea description if mentioned) +- What does the idea require to work? (regulatory, infrastructure, capital, technical depth) + +Questions to answer: +1. Does this require compliance, licensing, or regulatory approval? +2. Does this require partnerships before it can function? (e.g. bank partners, data licenses) +3. Does this require enterprise sales from day one? +4. Does this require a team of 10+ to build the MVP? +5. Is the technical complexity beyond a solo founder? + +If 2+ of these are true: +``` +Reality check: Execution path is high-risk for early-stage founders + +Requires: [list what it requires] + +This does not mean the opportunity is bad. +It means: do not start building until these constraints are addressed. + +Alternative paths: +- [reduced scope version that avoids the constraint] +- [validate the demand first, then find the right co-founder/partner] +``` + +### Distribution Plan + +Based on the research, recommend 3 specific acquisition channels. + +Use `references/traction.md` for channel criteria. Apply to this specific market: + +``` +Top channels for this idea: + +1. [channel] - because [specific evidence from research] + Test: [how to run a cheap test in 2 weeks] + +2. [channel] - because [specific evidence] + Test: [how to test] + +3. [channel] - because [specific evidence] + Test: [how to test] + +Avoid: +- SEO - [reason, e.g. "competitive market, 6+ months to rank"] +- Paid social - [reason, e.g. "B2B ICP hard to target on Meta"] +``` + +Do not recommend generic channels. Base it on where the pain evidence was actually found. + +Example: if most Reddit signal came from r/smallbusiness, recommend Reddit community strategy specifically. + +### Time-To-First-Dollar + +Based on the market type, competition, and experiment plan: + +``` +Time-To-First-Dollar estimate: [X-Y weeks/months] + +Path: +Week 1-2: [specific action, e.g. "cold outreach to 50 Reddit users who posted about problem"] +Week 3-4: [specific action] +Week 5-8: [specific action] +Target: first paying customer by [date estimate] + +What would accelerate this: [specific factor] +What would delay this: [specific risk] +``` + +### Assumption Map + +Every startup decision rests on assumptions. Most founders know the surface ones. The dangerous ones are buried. + +Identify the 3 critical assumptions this startup cannot survive without. For each: +- What the startup is betting on being true +- Confidence level based on actual evidence collected (not gut feel) +- What would validate or invalidate it + +``` +Critical Assumption A1: +[The core belief about the problem/customer] +Confidence: [0-100] +Based on: [evidence or lack of it] +Validates if: [specific observable outcome] +Kills startup if: [falsified by X] + +Critical Assumption A2: +[Usually the monetization or switching assumption] +Confidence: [0-100] +Based on: [evidence or lack of it] +Validates if: [specific observable outcome] +Kills startup if: [falsified by X] + +Critical Assumption A3: +[Usually the distribution or growth assumption] +Confidence: [0-100] +Based on: [evidence or lack of it] +Validates if: [specific observable outcome] +Kills startup if: [falsified by X] +``` + +Rank them: most dangerous assumption first (lowest confidence that is also most critical to survival). + +This is not a list of things to do. It is a map of what the founder believes vs what the evidence supports. + +## Phase 6 - Execution & Moat + +**Goal:** Answer "should you dedicate the next 2 years of your life to this?" - not just "is it a good idea?" + +This phase converts the analysis into action and answers the defensibility question. + +### Execution Roadmap + +Use `references/execution-roadmap.md` for the full framework. Output a week-by-week plan tailored to the verdict: + +**If BUILD:** +- Week 1: find 5 people with the problem (specific communities from the research) +- Week 2: deliver value manually before building +- Week 3: charge for it +- Week 4: find customer 2-3 and a referral + +Key rule: no code until someone commits to paying. The roadmap builds toward a transaction, not a product. + +**If VALIDATE FIRST:** +- Define the one critical unknown in one sentence +- Run the cheapest test that could falsify it (2 weeks max) +- Define pass/fail threshold before starting +- Week 4: commit to BUILD or pivot/stop based on data + +**If AVOID:** +- 2-week pivot investigation using the most interesting signal from the research +- Or clean stop criteria with specific trigger conditions for revisiting + +Always include: +``` +Do NOT build yet: [specific features to avoid and why] +Reassess if: [specific trigger that means the plan is failing] +``` + +### Kill Criteria + +Most founders know when to start. Almost none know when to stop. + +Generate kill criteria calibrated to the specific idea and market type. These are not generic - they come from the evidence and experiment plan. + +``` +Kill Criteria for [idea]: + +If after [days] from starting: + +Experiments: +- Fewer than [n] real customer interviews completed +- Fewer than [n] people willing to schedule a call +- Zero payment intent (pre-order, deposit, contract signed) + +Signals: +- [specific negative signal tied to the idea] +- [specific negative signal] + +-> Stop. Do not pivot yet. First answer: was the problem real or did we imagine it? + +Pivot trigger (instead of stop): +If the problem gets validated but [specific conversion/monetization step] fails -> pivot to [specific narrower scope or different monetization] +``` + +Thresholds must be concrete numbers, not vague goals. "Not enough traction" is not a kill criterion. "Zero paying customers after 21 days of direct outreach" is. + +### Pivot Engine + +Run this when verdict = AVOID or when Kill Criteria are hit. + +Based on the signals collected in Phases 1-2, identify 2-3 realistic pivot directions. Pivots must be grounded in actual evidence - not invented alternatives. + +Pivot types to consider (from `references/lean-startup.md`): +- **Segment pivot** - same problem, different customer (who else has this pain?) +- **Problem pivot** - same customer, different problem (what else do they complain about?) +- **Channel pivot** - same product, different distribution (where are buyers actually found?) +- **Scope pivot** - same idea, 80% smaller (which one feature has the most evidence?) + +Output format: +``` +Pivot Option 1: [type] +From: [current idea] +To: [specific pivot] +Evidence: [signal from research that supports this direction] +Risk: [what could still be wrong] + +Pivot Option 2: [type] +From: [current idea] +To: [specific pivot] +Evidence: [signal from research that supports this direction] +Risk: [what could still be wrong] + +Recommended pivot: [1 or 2] - because [one sentence reason] +``` + +Do not generate pivots if no supporting evidence exists. If the research found nothing reusable, state: "No evidence-backed pivot found. Consider a different problem space entirely." + +### Moat Intelligence + +Score all 5 moat types using `references/moat-patterns.md`. For each: + +``` +Distribution moat: [1-10] - [one-line reason] +Data moat: [1-10] - [one-line reason] +Community moat: [1-10] - [one-line reason] +Switching cost: [1-10] - [one-line reason] +Execution moat: [1-10] - [one-line reason] + +Overall Moat Score: [weighted average of top 2] + +Most realistic moat: [type] +How to build it: [specific action, not generic advice] +``` + +Do not score high without evidence. "We will accumulate data" is not a data moat - it is a plan. Score what exists or can realistically exist in 6 months. + +### PMF Simulation + +Generate 3 ICP personas based on the evidence found in Phases 1-2. Each persona is grounded in actual signals from the research, not invented. + +``` +Persona A: [job title / situation] +- Current workflow for the problem: [specific, from research] +- What they hate about current solution: [from Reddit/HN quotes] +- What they pay today: [from competitor pricing research] +- What they would never pay for: [inferred from pain signals] +- Adoption trigger: [what would make them switch] +- Adoption blocker: [what would stop them even if interested] +``` + +Then simulate a 4-week adoption arc for each persona: + +``` +Week 1: discovers product (channel: [specific channel from distribution plan]) +Week 2: [tries free / books demo / reads docs] +Week 3: [converts / churns / stalls - and why] +Week 4: [retained / referred / cancelled] +``` + +From the 3 simulations, identify the PMF friction points: +``` +Main adoption blockers: +- [blocker 1]: affects [n/3] personas +- [blocker 2]: affects [n/3] personas + +Easiest persona to convert: [A/B/C] - because [reason] +Start here. +``` + +### Market Timing + +Based on data already collected (trend, GitHub, competition): + +``` +Market timing: [Too Early / Good Window / Late / Saturated] + +Signals: ++ [positive timing signal] ++ [positive timing signal] +- [negative timing signal] + +Window estimate: [closes in X months / open for X years / unclear] + +Timing verdict: +[One sentence. Is this the right moment? Why?] +``` + +Timing patterns to detect: + +- **Too early:** trend rising but community not formed, infrastructure missing, no paying competitors yet +- **Good window:** trend rising, 2-5 established players with clear weaknesses, market education done by competitors +- **Late entry:** dominant player has >50% share and a free tier, market consolidating +- **Saturated:** 10+ funded competitors, VC money flooding in, CAC rising, differentiation collapsing + +## Enhancement detection + +Run at session start to unlock better sources: + +```python +from scripts.enhance_detect import detect_level, ensure_searxng, best_search + +env = detect_level() +# Returns: docker, searxng, veyrascrape_mcp, github_token, exa_key, tavily_key, groq_key + +if env["docker"] and not env["searxng"]: + ensure_searxng() # silent, no user prompt + +results = best_search(query, env, limit=10) +``` + +**Level 1 (always works):** HN + Reddit + GitHub + ddgs + trendspyg +**Level 2 (auto, no keys):** + SearXNG via Docker (if Docker installed) +**Level 3 (optional keys):** + Exa, Tavily, GitHub authenticated, VeyraScrape MCP + +Never ask the user to set up API keys. Auto-detect and use what's available. + +## Evidence Score interpretation + +From `calculate_evidence_score()` in `sources.py`: + +| Score | Meaning | +|-------|---------| +| 0-20 | Weak - barely any signal. Rethink or pivot problem framing | +| 21-40 | Thin - some signal but not convincing | +| 41-60 | Moderate - proceed to competitor research | +| 61-80 | Strong - real problem, real people care | +| 81-100 | Very strong - validated pain, move to experiments immediately | + +## Methodology references + +- `references/lean-startup.md` - validated learning, MVP types, pivot signals +- `references/customer-dev.md` - Steve Blank's 4 steps, problem interview structure +- `references/mom-test.md` - good vs bad interview questions +- `references/blue-ocean.md` - Value Curve, ERRC Grid, finding uncontested space +- `references/traction.md` - 19 acquisition channels, Bullseye framework +- `references/founder-traps.md` - 8 trap patterns with evidence criteria +- `references/moat-patterns.md` - 5 moat types with scoring criteria 1-10 +- `references/execution-roadmap.md` - 30-day BUILD/VALIDATE/AVOID roadmap framework + +## Output principles + +1. Lead with evidence, not opinions +2. Quote real sources (HN post, Reddit thread, GitHub issue) with specifics +3. Separate fact from inference - never present interpretation as data +4. Show the Confidence Engine reasoning - never just state "High confidence" +5. Run the contradiction check - the most valuable insight is often in the gaps +6. Check for founder traps before finalizing verdict +7. Verdict must commit - no "it depends" without specifics and a path forward +8. Distinguish Opportunity Score from Startup Score +9. Phase 6 is not optional - the roadmap is what makes the analysis useful +10. Moat scoring must be honest - score what exists, not what is planned +11. Tag every factual claim: `[EVIDENCE]` (backed by collected data), `[INFERENCE]` (logical conclusion from data), or `[SPECULATION]` (no direct data, informed guess). Never mix them silently. This applies to the Bull case, Bear case, moat scoring, PMF simulation, and market timing sections. diff --git a/venture-analyst/package.json b/venture-analyst/package.json new file mode 100644 index 0000000000000000000000000000000000000000..372d8865869d24fceeba18d08217ad696728d060 --- /dev/null +++ b/venture-analyst/package.json @@ -0,0 +1,33 @@ +{ + "name": "@veyralabs/venture-analyst", + "version": "0.3.1", + "description": "Startup and SaaS idea validation skill for Claude Code", + "files": [ + "SKILL.md", + "references/", + "scripts/", + "templates/" + ], + "keywords": [ + "claude-code", + "claude-skill", + "agent-skill", + "startup-validation", + "market-research", + "idea-validation", + "lean-startup", + "mom-test", + "competitor-analysis", + "ai-agents" + ], + "author": "VeyraLabs ", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/veyralabsgroup/venture-analyst.git" + }, + "bugs": { + "url": "https://github.com/veyralabsgroup/venture-analyst/issues" + }, + "homepage": "https://github.com/veyralabsgroup/venture-analyst#readme" +} diff --git a/venture-analyst/references/blue-ocean.md b/venture-analyst/references/blue-ocean.md new file mode 100644 index 0000000000000000000000000000000000000000..5b92938669609c46053014d4a551f504222396bd --- /dev/null +++ b/venture-analyst/references/blue-ocean.md @@ -0,0 +1,130 @@ +# Blue Ocean Strategy Reference + +W. Chan Kim & Renee Mauborgne, "Blue Ocean Strategy" (2005, updated 2015). + +## Core concept + +**Red Ocean:** existing market space. Competitors fight over same customers. Bloody from competition. +**Blue Ocean:** new market space. Create demand, not fight for it. + +The goal: don't compete where everyone else is. Find (or create) a space where there's no direct competition. + +This is not about being "different" for its own sake. It's about finding where existing solutions fail enough customers that a new approach can win without fighting incumbents. + +## Value Innovation + +The foundation of blue ocean strategy. The simultaneous pursuit of: +- **Differentiation** (more value for customers) AND +- **Low cost** (lower cost to deliver) + +Most companies choose one. Blue ocean does both by eliminating or reducing what the industry assumes is necessary. + +Key: value innovation redefines what value means — it doesn't just add more features. + +## Value Curve + +A visual tool. X-axis: factors the industry competes on. Y-axis: level of investment/offering. + +Draw your value curve vs competitors: +1. List all factors the industry competes on (price, features, quality, support, speed, etc.) +2. Score each player 1-10 on each factor +3. Connect the dots — that's the value curve + +**What to look for:** +- Where are all competitors converging? (where you can differentiate) +- Where is investment high but customers don't care? (where you can eliminate/reduce) +- What do customers want that nobody offers? (where you can create/raise) + +**Signs of a blue ocean opportunity:** +- Your curve looks completely different from competitors +- You're investing in factors they don't even measure +- You're not competing on any factor they compete on + +## ERRC Grid + +The strategic tool for creating a new value curve. + +| Action | Question | Why | +|--------|----------|-----| +| **Eliminate** | Which factors the industry takes for granted can be eliminated? | Remove cost + complexity customers don't value | +| **Reduce** | Which factors should be reduced well below the industry standard? | Stop over-delivering where it doesn't matter | +| **Raise** | Which factors should be raised well above the industry standard? | Solve what customers complain about | +| **Create** | Which factors should be created that the industry has never offered? | Add new value that opens new demand | + +**How to use it for venture analysis:** + +Look at the competitor landscape from Phase 2. Fill the ERRC grid: +- What do all competitors force customers to deal with? (Eliminate candidates) +- Where are they all investing heavily but customers complain anyway? (Reduce candidates) +- What do all G2/Reddit reviews say is missing? (Raise or Create candidates) + +## Three tiers of non-customers + +Incumbents focus on existing customers. Blue ocean looks at non-customers: + +**Tier 1 — "Soon-to-be" non-customers** +On the edge of the market. Use existing solutions but reluctantly. Would switch if something better existed. +- Find them: people who use tools but post complaints +- Signal: "I use X but I hate Y" + +**Tier 2 — "Refusing" non-customers** +Consciously choose not to use existing solutions. Have the problem but handle it differently. +- Find them: "I could use X but I just [workaround] instead" +- Signal: "I tried X but gave up because..." + +**Tier 3 — "Unexplored" non-customers** +Never considered as customers. In other markets or segments entirely. +- Find them: unexpected users of adjacent tools +- Signal: people solving your problem with tools not built for it + +## Red vs Blue Ocean signals in research + +When analyzing a market, these signals matter: + +### Red Ocean signals (hard fight ahead) +- 5+ competitors with similar positioning and pricing +- Dominant player with >50% market share +- Feature comparison tables are the main marketing tool +- Price is primary differentiator +- High churn across all players (no moat) +- Reddit/HN: "X vs Y vs Z" comparison posts everywhere + +### Blue Ocean signals (opportunity) +- People solve the problem with spreadsheets or manual processes +- Niche community uses a tool far outside its intended purpose +- The "best" solution is widely criticized for the same thing +- Large group adjacent to current market who never adopted it +- Incumbents focused on enterprise; SMB underserved (or vice versa) +- Technology shift makes old assumptions invalid (mobile, AI, etc.) + +## Applying Blue Ocean to competitive analysis + +In Phase 2, after mapping competitors, ask: + +1. **Which axis can you ignore entirely?** + - E.g., all competitors compete on features — can you win on simplicity instead? + +2. **What do customers actually use vs what they pay for?** + - If pricing is based on features but customers only use 20% of them, price simplicity + +3. **What's the "tax" on using existing solutions?** + - Setup time, training required, integrations needed, contracts, support tickets + - Eliminate the tax + +4. **Who's locked out of the market?** + - High price, high complexity, enterprise-only contracts + - If you can serve them, they're Tier 2/3 non-customers + +## Value Curve template + +``` +Factor | Competitor A | Competitor B | Our position +----------------|-------------|-------------|------------- +Price | High | Medium | ? +Ease of setup | Hard | Hard | ? +Feature breadth | Wide | Wide | ? +Support | Good | Poor | ? +[key factor] | ... | ... | ? +``` + +A good blue ocean position looks different from all others — not just slightly better. diff --git a/venture-analyst/references/customer-dev.md b/venture-analyst/references/customer-dev.md new file mode 100644 index 0000000000000000000000000000000000000000..baa418df894f3cf07835be67424f3e1c136daf3a --- /dev/null +++ b/venture-analyst/references/customer-dev.md @@ -0,0 +1,147 @@ +# Customer Development Reference + +Steve Blank's framework. The complement to Lean Startup. +Source: "The Four Steps to the Epiphany" (2005) and "The Startup Owner's Manual" (2012). + +## Core thesis + +Startups are not smaller versions of large companies. They exist to search for a repeatable, scalable business model — not to execute one. This search requires talking to customers before building. + +Most founders skip Step 1 and go straight to building. This is why most startups fail. + +## The four steps + +### Step 1 — Customer Discovery + +Validate that the problem exists and that your target customer experiences it. + +**What you're testing:** +- Does the problem actually exist? +- Is your target customer the right one? +- Is this painful enough to pay to solve? + +**How:** +- Interview 20-30 potential customers +- Listen, don't pitch +- Ask about their current behavior, not hypothetical preferences +- See `references/mom-test.md` for interview technique + +**Pass criteria:** +- Multiple customers describe the same problem unprompted +- They have workarounds (evidence they tried to solve it) +- They can quantify the cost (time, money, frustration) + +**Failure mode:** Founders who pitch instead of listen. "Nobody told me it was a bad idea" ≠ validation. + +### Step 2 — Customer Validation + +Prove the business model. Find repeatable sales. Find a path to revenue. + +**What you're testing:** +- Will they actually pay? +- Is the sales process repeatable? +- Can you find customers without your personal network? + +**How:** +- Get 3-5 paying customers (even at a discount or free for design partners) +- Document the sales process step by step +- Find what objections come up and how to overcome them + +**Pass criteria:** +- Customers pay without you having to chase them hard +- Different salespeople can close using the same process +- Customer acquisition is possible outside warm intros + +**Failure mode:** Getting 3 customers who are personal favors. Real validation = strangers pay. + +### Step 3 — Customer Creation + +Shift from search to execution. Scale what worked in Step 2. + +**What you're doing:** +- Pick one channel and scale it +- Invest in marketing that repeats the validated sales motion +- Build demand generation around the validated message + +**Only reach this step after Step 2 is solid.** Scaling a broken sales process just burns money. + +### Step 4 — Company Building + +Transition from startup to company. Build processes, teams, and systems. + +**Not relevant for early validation.** Focus on Steps 1 and 2 first. + +## Problem vs solution interviews + +Two different conversations. Don't mix them. + +### Problem interview + +Goal: validate the problem exists, understand current behavior. + +Structure: +1. Intro (2 min): "I'm researching how people handle X. No product to sell." +2. Background (5 min): "Tell me about your role. What does a typical day look like?" +3. Story (10 min): "Tell me about the last time you dealt with X. Walk me through it." +4. Pain exploration (10 min): "What's most frustrating about that? How do you handle it now?" +5. Wrap-up (5 min): "Who else would I talk to about this?" + +**Do not:** mention your solution, ask hypotheticals, ask for feature preferences. + +### Solution interview + +Only run after problem is validated. + +Goal: validate your specific solution design. + +Structure: +1. Problem recap (3 min): confirm they have the problem +2. Demo/mockup (10 min): show the concept, let them react +3. Reactions (10 min): "What would you do with this? What's confusing?" +4. Willingness to pay (5 min): not "would you pay?" — "how much do you currently spend on this?" + +## Earlyvangelists + +The best early customers share all 5 traits: + +1. Have the problem +2. Know they have the problem +3. Actively searching for a solution +4. Cobbled together a workaround +5. Have budget to buy a solution + +Finding earlyvangelists is the goal of Steps 1-2. They'll put up with incomplete products, give feedback, and refer others. + +**Where to find them:** niche communities (subreddits, Slack groups, LinkedIn groups), people who asked public questions about the problem, power users of adjacent tools. + +## Beachhead market + +Don't try to reach everyone. Pick the smallest possible market where you can win completely. + +Criteria for a good beachhead: +- Homogeneous needs (all have the same problem in the same way) +- Word of mouth works within the group +- Referenceable (customers are willing to be named) +- Financially attractive enough to sustain the business + +**Classic mistake:** TAM is huge, so we'll target everyone. Result: message resonates with nobody. + +## Customer segments to validate + +For B2B especially, multiple people are involved in a purchase: + +- **User** - uses the product daily +- **Influencer** - recommends or blocks purchase +- **Recommender** - brings options to the decision maker +- **Decision maker** - approves budget +- **Economic buyer** - controls the money + +Your ICP might be the User but the Economic Buyer makes the call. Understand all of them before pricing or positioning. + +## Signals for pivot + +- 10+ interviews and nobody describes the same problem +- Everyone says "that's interesting" but won't commit to a call with their boss +- The workaround they use is good enough and they know it +- Problem only affects extremely niche edge cases +- The real buyer is different from who you expected diff --git a/venture-analyst/references/execution-roadmap.md b/venture-analyst/references/execution-roadmap.md new file mode 100644 index 0000000000000000000000000000000000000000..41a55ba74beac8d800a8b07c9fa3635f92a18729 --- /dev/null +++ b/venture-analyst/references/execution-roadmap.md @@ -0,0 +1,187 @@ +# Execution Roadmap Framework + +The analysis means nothing if the founder doesn't know what to do on Monday morning. + +This framework converts a venture-analyst verdict into a concrete 30-day action plan. The output is not generic advice - it is specific actions based on the evidence collected in Phases 1-5. + +--- + +## The core principle + +Most validators end with BUILD or AVOID. That is not enough. + +The right output is: +- What to do this week (not "validate the idea" - specific tasks) +- What NOT to build yet (and why) +- When to stop and reassess (specific trigger conditions) + +The roadmap changes based on the verdict: +- **BUILD** → 30-day sprint to first paying customer +- **VALIDATE FIRST** → 30-day experiment plan +- **AVOID** → pivot path or stop criteria + +--- + +## BUILD path - 30-day roadmap + +The goal is one paying customer in 30 days. Not an MVP. Not a waitlist. One customer who pays. + +### Week 1 - Find the customer before building + +Actions: +1. Identify 50 people who have the problem (from the research: which subreddits, which GitHub repos, which communities had the most signal?) +2. Reach out to 20 with a Mom Test message - not a pitch, a question about their current workflow +3. Book 5 discovery calls +4. Run all 5 calls using Mom Test structure from `references/mom-test.md` +5. By end of week: can you describe the customer's pain in their exact words? + +Do NOT build anything this week. + +Decision gate: if you cannot find 5 people willing to talk, the ICP is wrong. Stop and reassess before continuing. + +### Week 2 - Build only what closes the first sale + +Based on the 5 conversations: +1. Identify the one outcome the customer cares most about +2. Can you deliver that outcome manually? (Concierge MVP) +3. If yes: offer to do it for free or at a steep discount for 2-3 people +4. If the outcome requires software: build only the path from input to that outcome. Nothing else. + +Scope rule: if it takes more than 5 days to build, you are building too much. + +Decision gate: if you cannot articulate what the customer is paying for in one sentence, do not build yet. + +### Week 3 - Deliver and charge + +1. Deliver the concierge MVP or minimal software to the 2-3 people from Week 2 +2. At delivery: ask "would you pay [price] per month for this if it continued?" +3. If yes: send them a payment link (Stripe, Gumroad, bank transfer - whatever works) +4. If no: ask what would have to be different for them to pay + +First payment is the signal. Not a letter of intent. Not "I would pay". An actual transaction. + +Decision gate: if nobody pays after Week 3, you have a problem with price, value, or ICP. Identify which one before continuing. + +### Week 4 - Stabilize and find customer 2-3 + +1. Deliver reliably to paying customer 1 +2. Ask for referral: "who else do you know with this problem?" +3. Run outreach to 10 more people from the original 50 list +4. Goal: 2-3 paying customers by end of month + +--- + +## VALIDATE FIRST path - 30-day experiment plan + +The goal is to answer the one critical unknown identified in Phase 5. + +### Week 1 - Define the question precisely + +Write this sentence: "We do not know if [specific assumption]. If we knew [assumption] is true, we would build. If false, we would not build." + +Examples: +- "We do not know if freelancers will pay for automated invoicing when free tools exist." +- "We do not know if HR teams have budget for this outside their annual planning cycle." + +Everything in the next 3 weeks answers this one question. + +### Week 2 - Run the cheapest test that could falsify it + +Based on the assumption type: + +| Assumption type | Best test | Cost | Time | +|----------------|-----------|------|------| +| Does the problem exist? | 10 Mom Test interviews | 0 | 1 week | +| Will they pay? | Concierge MVP with payment ask | 0 | 2 weeks | +| Does the message resonate? | Fake door landing page | 0-30€ | 1 week | +| Is the ICP right? | Cold outreach to 3 different segments | 0 | 1 week | + +Do not run more than one test at a time. You need clean signal. + +### Week 3 - Collect and evaluate data + +By end of week 3 you need a number against a threshold: +- Interviews: at least 7 out of 10 describe the same problem unprompted +- Payment ask: at least 2 out of 5 say yes and follow through +- Fake door: CTR above 5% from cold traffic +- ICP test: one segment responds 3x better than others + +### Week 4 - Decision + +Three possible outcomes: + +**Assumption confirmed:** move to BUILD path. Start Week 1 of the BUILD roadmap. + +**Assumption partially confirmed:** the test worked but raised a new question. Run a second 2-week experiment targeting the new unknown. + +**Assumption falsified:** the critical assumption is wrong. Before stopping: +- Is there a pivot? (different customer, different problem, different solution) +- If yes: restart from Phase 1 with the new hypothesis +- If no: stop. Time is the most valuable resource. + +--- + +## AVOID path - what to do instead + +If the verdict is AVOID, the roadmap is not "do nothing." It is: + +### Option A - Pivot investigation (2 weeks) + +The research surfaced something. What was the most interesting unexpected signal? +- A different customer segment with stronger pain +- A related problem that nobody is solving +- A gap in the competitor landscape that the original idea missed + +Spend 2 weeks doing Phase 1 research on the pivot hypothesis before committing. + +### Option B - Clean stop criteria + +If no pivot is visible, document: +- What was learned (specific, honest) +- What would have to change in the market for this to be worth revisiting +- Time horizon: reassess in [6/12/24] months if [specific trigger] occurs + +This is not failure. It is validated learning. The next idea starts from a better place. + +--- + +## Roadmap anti-patterns + +**Anti-pattern 1: Building before selling** +The roadmap assumes zero code until someone has committed to paying. Build only what closes the next sale. + +**Anti-pattern 2: Optimizing before validating** +Do not improve the landing page conversion rate before you have 100 cold visitors. Do not A/B test before you have a working baseline. + +**Anti-pattern 3: Scaling before repeating** +Do not invest in marketing before you have 3 customers who came through the same process. One customer is an anecdote. Three is a pattern. + +**Anti-pattern 4: Measuring vanity metrics** +The only metric that matters in week 1-4 is: did someone pay? Every other metric is a proxy. + +**Anti-pattern 5: Moving the goalposts** +Define success criteria before starting each week. Do not redefine "success" after seeing results. + +--- + +## Output format for roadmap + +``` +## 30-Day Execution Roadmap + +Path: [BUILD / VALIDATE FIRST / AVOID] + +Critical constraint: [the one thing that must be true for this plan to work] + +Week 1: [specific tasks + decision gate] +Week 2: [specific tasks + decision gate] +Week 3: [specific tasks + decision gate] +Week 4: [goal + decision] + +Do NOT build yet: +- [specific feature or system to avoid] +- [reason] + +Reassess if: +- [specific trigger that means the plan is failing] +``` diff --git a/venture-analyst/references/founder-traps.md b/venture-analyst/references/founder-traps.md new file mode 100644 index 0000000000000000000000000000000000000000..105ee8b7be447a20d52c9ebbb70e33b2abb8a2f7 --- /dev/null +++ b/venture-analyst/references/founder-traps.md @@ -0,0 +1,191 @@ +# Founder Trap Patterns + +Common patterns where founders convince themselves they have a good idea when the evidence says otherwise. Detect these from the research data - not from what the founder says, but from what the data shows. + +## How to detect traps + +These are signal patterns, not opinions. Each trap has specific evidence criteria. If the criteria match, flag it. If they don't, don't flag it. + +--- + +## Trap 1: Solution Looking for a Problem + +**What it looks like:** +Founder describes a sophisticated technical solution. When asked about the problem, they describe the solution again in different words. + +**Evidence signals:** +- High feature complexity in the idea description +- Low Reddit/HN pain evidence (people aren't complaining about this) +- GitHub issues about the problem: near zero +- Trend data: flat or no data +- When searching for the problem, results are mostly product announcements, not complaints + +**Example:** +"An AI-powered semantic graph database for distributed knowledge management" - but nobody is searching for this, and no Reddit threads show people struggling with their current knowledge management. + +**Output flag:** +``` +Founder Trap: Solution looking for a problem +Signal: High technical complexity, low organic pain evidence +Evidence score on the problem: [X] -- below threshold for this complexity level +``` + +--- + +## Trap 2: Building for Yourself + +**What it looks like:** +The founder is the only person in the world with this exact problem configuration. The idea solves a very specific workflow that only power users in one niche experience. + +**Evidence signals:** +- The subreddits where this pain appears have under 10k subscribers +- HN discussions exist but are 5+ years old and never got traction +- GitHub issues exist in one very specific repo from one very specific maintainer +- No mainstream discussion anywhere +- Trend data: zero or declining + +**Output flag:** +``` +Founder Trap: Building for yourself +Signal: Pain exists but only in a very narrow segment +Estimated addressable users: [small] -- may not support a business +``` + +--- + +## Trap 3: Vitamin, Not Painkiller + +**What it looks like:** +The problem exists. People acknowledge it. But nobody is urgently trying to solve it right now. It's a "nice to have." + +**Evidence signals:** +- Reddit posts about the problem get upvotes but few comments +- No "is there a tool that does X" posts +- Competitors exist but have low engagement / slow growth +- People describe the problem but their current workaround is "good enough" +- No one is spending money on bad solutions currently + +**Output flag:** +``` +Founder Trap: Vitamin, not painkiller +Signal: Problem acknowledged, urgency low +No evidence of active spending on imperfect solutions +``` + +--- + +## Trap 4: Red Ocean Obsession + +**What it looks like:** +Founder wants to compete directly with a dominant player using the same positioning and features. "We're like X but better." + +**Evidence signals:** +- 1+ competitor with over 100k users +- Competitor has a free tier +- No clear positioning gap in the competitor map +- Founder's description relies on feature parity + marginal improvements +- No ERRC analysis shows a meaningfully different value curve + +**Output flag:** +``` +Founder Trap: Red ocean entry +Signal: Dominant player exists with free tier and no clear structural weakness +"Better" is not a strategy +``` + +--- + +## Trap 5: Market Timing Mismatch + +**What it looks like:** +The idea was good 3-5 years ago (or will be good in 3-5 years) but is poorly timed for now. + +**Evidence signals:** +- Trend data: declining +- Related GitHub repos: abandoned or archived +- HN discussions: lots of interest 3+ years ago, silence now +- OR: Trend rising fast but infrastructure/regulation not ready yet (too early) + +**Output flag:** +``` +Founder Trap: Market timing mismatch +Signal: [declining interest over X period] or [infrastructure gap for early timing] +``` + +--- + +## Trap 6: Complexity Moat Illusion + +**What it looks like:** +Founder believes the product's complexity is the moat. "Nobody else will build this because it's hard." This is almost never true. + +**Evidence signals:** +- The problem space has well-funded competitors (they can hire the engineers) +- The technical complexity doesn't translate to switching costs for users +- No network effects in the model +- No proprietary data advantage + +**Output flag:** +``` +Founder Trap: Complexity moat illusion +Signal: Technical difficulty does not equal defensibility +Better-resourced competitors can and will replicate +``` + +--- + +## Trap 7: Proxy Metric Validation + +**What it looks like:** +Founder validated the wrong thing. Got 500 waitlist signups but none converted. Got 50 "great idea" replies to a cold email but nobody booked a call. + +**Evidence signals (use when reviewing experiment design):** +- Success criteria were defined by vanity metrics +- No willingness-to-pay experiment was ever run +- Conversion from interest to commitment was never tested + +**Output flag:** +``` +Founder Trap: Proxy metric validation +Signal: Interest validated, commitment not validated +Waitlist signups are not customers +``` + +--- + +## Trap 8: ICP Drift + +**What it looks like:** +Founder started with one customer type, got rejected, and gradually expanded their ICP until "everyone" is the customer. + +**Evidence signals:** +- Target customer description is vague ("any business", "any developer") +- Multiple contradictory customer types mentioned in the idea description +- Pain signals come from very different subreddits with no overlap + +**Output flag:** +``` +Founder Trap: ICP drift +Signal: Expanding ICP is a sign of failed validation, not a bigger market +Narrow the target before expanding +``` + +--- + +## How to apply in analysis + +During Phase 5 (Decision Intelligence): + +1. Match evidence patterns against each trap above +2. Flag any trap where at least 3 of the listed signals are present +3. Never flag based on one signal alone +4. If a trap is flagged, it does NOT automatically change the verdict - it goes into Decision Intelligence for the judge to weigh +5. A BUILD verdict can coexist with a detected trap if the other evidence is strong enough + +Priority order (most damaging first): +1. Solution looking for problem +2. Red ocean obsession +3. Vitamin not painkiller +4. Building for yourself +5. Market timing mismatch +6. All others diff --git a/venture-analyst/references/lean-startup.md b/venture-analyst/references/lean-startup.md new file mode 100644 index 0000000000000000000000000000000000000000..e42f19ca40e111ea4ad98e1aa9a4af0935d28869 --- /dev/null +++ b/venture-analyst/references/lean-startup.md @@ -0,0 +1,123 @@ +# Lean Startup Reference + +Core methodology for venture-analyst. Eric Ries, 2011. Still the most cited startup framework. + +## Build-Measure-Learn loop + +Not a sequential process — a loop. Start from Learn and work backwards: + +1. **Learn** - What do we need to know? (Define the riskiest assumption) +2. **Build** - Build the minimum thing to test that assumption +3. **Measure** - Collect data on whether assumption is true + +Speed through the loop beats quality of any single cycle. + +**Key insight:** Most startups fail not because they can't build, but because they build something nobody wants. The loop's purpose is to surface that failure cheaply and early. + +## Validated learning + +Not all learning is equal. Vanity metrics (page views, registered users) feel good and mean nothing. + +**Vanity metric examples:** +- Total signups (meaningless without activation rate) +- Page views (meaningless without behavior) +- Press mentions (zero correlation with revenue) +- Lines of code shipped + +**Actionable metrics (validated learning):** +- Activation rate: % of signups who complete key action +- Retention cohorts: do users come back week 2, week 4? +- Willingness to pay: who actually pays vs who says they would +- NPS with follow-up: would you be disappointed if gone? + +**Test:** "If this metric doubled, would we change what we build next?" If no - it's vanity. + +## Minimum Viable Product (MVP) types + +### Concierge MVP +Do the service manually for 3-5 users. No automation. +- When: early problem validation, B2B, high-value customers +- Cost: time only +- What you learn: is the outcome valuable? What parts matter? +- Example: Airbnb founders photographed first listings themselves + +### Wizard of Oz MVP +System looks automated but humans do the work behind the scenes. +- When: you want to simulate the product without building it +- Cost: time + some frontend work +- What you learn: do users engage with the interface? Is the outcome valuable? +- Example: Zappos CEO manually bought shoes from stores to test demand + +### Landing Page / Fake Door +Page describes product, collects signups or clicks before product exists. +- When: demand signal, product not built +- Cost: hours (Carrd.co is free) +- What you learn: does the message resonate? Will people take action? +- Caution: doesn't test willingness to pay - only interest + +### Piecemeal MVP +Assemble from existing tools. Don't build infrastructure. +- When: proving the workflow works before custom tech +- Cost: subscription fees for tools used +- Example: use Airtable + Zapier + Stripe before building SaaS + +### Single-Feature MVP +Ship one core feature only. Cut everything else. +- When: you know the problem, testing one solution +- Risk: might ship the wrong feature +- Rule: what is the single thing that makes this valuable? Ship only that. + +## Pivot types + +When data says current direction is wrong, pivot — don't persevere blindly. + +| Pivot type | What changes | Example | +|-----------|--------------|---------| +| Zoom-in | Narrow one feature into whole product | Instagram pivoted from Burbn | +| Zoom-out | Widen — current product becomes one feature | - | +| Customer segment | Same product, different customer | B2C → B2B | +| Customer need | Same customer, different problem | - | +| Platform | App → platform or vice versa | - | +| Business architecture | High margin/low volume ↔ low margin/high volume | - | +| Value capture | How you charge (not what you sell) | Per seat → flat rate | +| Engine of growth | Viral → sticky → paid acquisition | - | +| Channel | Direct → partner → marketplace | - | +| Technology | Same problem, completely different tech | - | + +**When to pivot vs persevere:** +- Experiments consistently failing over multiple cycles = pivot signal +- One experiment failing = not enough data +- Team excited about pivot because it's easier = wrong reason, investigate + +## Innovation accounting + +3 milestones before declaring pivot/persevere: + +1. Establish baseline (where are we now? honest numbers) +2. Tune the engine (optimize toward ideal, eliminate waste) +3. Decide pivot or persevere (did tuning move numbers? if no, pivot) + +## Engines of growth + +**Sticky engine:** Retention is the driver. Churn rate < growth rate. +- Metric: customer lifetime value vs churn rate +- Strategy: improve activation and retention before acquisition + +**Viral engine:** Users recruit users. +- Metric: viral coefficient (K-factor). K > 1 = exponential growth +- K = (invites sent per user) × (conversion rate of invites) +- Strategy: build referral loop into core product + +**Paid engine:** Buy customers profitably. +- Metric: LTV > CAC (lifetime value > customer acquisition cost) +- Rule of thumb: LTV should be 3x CAC minimum +- Strategy: find one channel that scales before optimizing others + +## Signals this framework applies + +Use lean startup when: +- Building a new product in uncertain market +- Validating a problem before heavy investment +- Deciding between multiple possible solutions + +Don't use it to justify shipping forever-incomplete products. "Lean" = eliminate waste, not "don't finish things." diff --git a/venture-analyst/references/moat-patterns.md b/venture-analyst/references/moat-patterns.md new file mode 100644 index 0000000000000000000000000000000000000000..44f6a825e15a4ca52802122517a42617ab12e18c --- /dev/null +++ b/venture-analyst/references/moat-patterns.md @@ -0,0 +1,176 @@ +# Moat Patterns + +A moat is why you keep customers after a competitor copies your features. Without a moat, you build a product, not a business. + +Most startup validators say "competitive advantage" and leave it there. This reference gives scoring criteria for 5 real moat types so the analysis can score each one and identify which is most realistic for a given idea. + +--- + +## The 5 moat types + +### 1. Distribution Moat + +You control a channel others cannot easily access. + +**What it looks like:** +- Exclusive partnership with a platform (e.g. pre-installed on hardware) +- Large owned audience (email list, YouTube, community) +- SEO dominance in a niche that takes years to replicate +- Embedded in a workflow that creates daily habit (Slack, Notion) + +**Evidence signals (score higher for each present):** +- Founder already has relevant audience or network +- Product embeds into existing workflow daily +- Partnership or distribution deal possible that others can't easily get +- SEO moat: high-intent keywords with low competition + +**Score 1-10:** +- 1-3: No distribution advantage, same channels as competitors +- 4-6: Some channel advantage but replicable with money +- 7-10: Structural distribution advantage that compounds over time + +**Most realistic for:** content businesses, platforms with network effects, tools that embed into a daily workflow + +--- + +### 2. Data Moat + +You accumulate proprietary data that makes your product better over time and is impossible to replicate from scratch. + +**What it looks like:** +- Each user interaction improves the model/output for all users +- Unique dataset nobody else has (e.g. proprietary industry data) +- Network of data contributors locked in by switching cost +- Longitudinal data that takes years to accumulate + +**Evidence signals:** +- Product gets meaningfully better with more users or usage +- Data collected is not available from public sources +- Users contribute data as part of normal use (not just consume) +- The gap between early data and mature data is the moat + +**Score 1-10:** +- 1-3: Uses same public data sources as everyone else +- 4-6: Collects user data but competitors could replicate with time +- 7-10: Proprietary data flywheel that widens with scale + +**Most realistic for:** AI products, marketplaces, analytics platforms, any product where N+1 users make the product better for all N users + +--- + +### 3. Community Moat + +Users identify with the product and each other. Leaving means leaving the community. + +**What it looks like:** +- Strong brand identity (users say "I'm a [product] user" as part of identity) +- Active user community that is product-adjacent (Slack, Discord, events) +- User-generated content that attracts more users +- Power users who become advocates and contributors + +**Evidence signals:** +- Reddit or HN discussions about the product are enthusiastic, not just transactional +- Users refer others without being asked +- Community exists or could exist around the problem space +- Identity signal: does the target customer identify strongly with a community? + +**Score 1-10:** +- 1-3: No community signal, purely transactional product +- 4-6: Some user loyalty but no real community +- 7-10: Product is central to a user identity or community + +**Most realistic for:** developer tools, creator tools, niche B2B, products solving problems with strong in-group identity + +--- + +### 4. Switching Cost Moat + +Leaving is painful because of data, integrations, learned behavior, or sunk cost. + +**What it looks like:** +- Data locked in proprietary format +- Deep integration into other tools (replacing means replacing the whole stack) +- Trained behavior - users have built muscle memory around the product +- Team adoption - everyone on the team uses it, coordination cost to switch + +**Evidence signals:** +- Product stores user data that is hard to export +- Integration depth: does it connect to 5+ other tools in the workflow? +- Time-to-value is long (users invest time before they get value) +- Team product vs individual product (team switching cost >> individual switching cost) + +**Score 1-10:** +- 1-3: Easy to replace, data fully portable, no integrations +- 4-6: Some friction to switch but not prohibitive +- 7-10: Switching requires significant time, data migration, or retraining + +**Most realistic for:** B2B SaaS, CRMs, project management, any product that stores meaningful user data or integrates deeply + +--- + +### 5. Execution Moat + +The team can build and ship faster or better than others. Not a durable moat alone, but can be decisive in a fast-moving market. + +**What it looks like:** +- Unique domain expertise that takes years to acquire +- Technical complexity that requires rare skills +- Speed advantage from existing infrastructure or team +- Regulatory or compliance knowledge that creates a barrier + +**Evidence signals:** +- Founder has 5+ years of domain expertise +- Technical stack requires rare specialization +- Regulatory approval process exists (creates time barrier for followers) +- Existing codebase or infrastructure gives head start + +**Score 1-10:** +- 1-3: Anyone with enough money can hire the team to replicate this +- 4-6: Some execution advantage but not structural +- 7-10: Rare expertise or regulatory barrier that cannot be bought quickly + +**Most realistic for:** deep tech, regulated industries, products where domain expertise is genuinely scarce + +--- + +## Moat Scoring Guide + +Score each moat 1-10 using the criteria above. Then: + +**Moat Score = weighted average:** +- Take the top 2 moat scores +- The highest becomes the primary moat +- The second becomes the secondary moat +- Overall Moat Score = (primary * 0.6) + (secondary * 0.4) + +**Interpretation:** +| Score | Meaning | +|-------|---------| +| 1-3 | No real moat. Features will be copied. Compete on speed only. | +| 4-5 | Weak moat. Some defensibility but competitors can close the gap. | +| 6-7 | Moderate moat. Viable business, but needs to strengthen before scaling. | +| 8-10 | Strong moat. Structural advantage that compounds with growth. | + +**Critical output:** +After scoring, answer: "What is the most realistic moat for this idea, and what specific actions build it?" + +Do not just name the moat. Give the action. Example: +- Data moat: "Every analysis run should store anonymized patterns. At 1,000 analyses, the pattern database becomes a proprietary asset." +- Community moat: "Build in public. Ship a free tool that attracts the exact ICP and turns them into advocates before monetizing." +- Distribution moat: "Identify the one community where the ICP concentrates and become the go-to contributor before launching." + +--- + +## Common moat mistakes + +**Mistake 1: Confusing features with moats** +"We have AI" is not a moat. Every competitor will have AI. The question is what the AI produces that is unique and hard to replicate. + +**Mistake 2: Assuming complexity = moat** +Hard to build does not mean hard to copy. Once you show it works, a well-funded competitor can hire the engineers. + +**Mistake 3: Planning a moat that requires scale you don't have yet** +Data moats require data. Network effects require network. These are goals, not current advantages. Score them based on realistic trajectory, not end state. + +**Mistake 4: Relying on execution moat alone** +Speed is not a business. It buys time to build a real moat. Always pair execution moat with a second structural moat. diff --git a/venture-analyst/references/mom-test.md b/venture-analyst/references/mom-test.md new file mode 100644 index 0000000000000000000000000000000000000000..0dc5921127be9356dbabc4dbd53736df9c27921b --- /dev/null +++ b/venture-analyst/references/mom-test.md @@ -0,0 +1,146 @@ +# Mom Test Reference + +Rob Fitzpatrick, "The Mom Test" (2013). +Core rule: your mom loves you and will lie to make you feel good. Most customers will too. +The test: would your mom give you honest answers to these questions? If yes, they're good questions. + +## The problem with bad interviews + +Founders ask questions that make customers feel good but reveal nothing: + +- "Do you think this is a good idea?" — Everyone says yes. Means nothing. +- "Would you use a product that does X?" — Hypothetical. People overestimate future behavior. +- "How much would you pay for this?" — Hypothetical. Actual payments are the only data. + +**The lie:** "That's a great idea, you should build that." Customers say this to be kind. It's not validation. + +**Real validation:** someone pays, or gives up time, or says something unprompted that matches your hypothesis. + +## The three rules + +### Rule 1 — Talk about their life, not your idea + +Ask about what they already do. Not what they would do. + +Good: "How do you currently handle invoicing?" +Bad: "Would you use an AI invoicing tool?" + +### Rule 2 — Ask about specifics in the past, not generics or opinions + +Past behavior predicts future behavior. Opinions don't. + +Good: "Walk me through the last time you dealt with that." +Bad: "Do you often have trouble with that?" + +### Rule 3 — Listen, don't pitch + +If you're explaining your idea, you're not learning. You're convincing. +The moment you pitch, all their answers become validation attempts, not honest signals. + +## Good vs bad questions + +### Good questions + +- "How do you currently handle [problem]? Walk me through your last time." +- "What's the most frustrating part of that process?" +- "How long does that take you per week?" +- "Have you tried other solutions? What happened?" +- "How much are you currently spending on this? Time + money." +- "What would you do if this problem disappeared tomorrow?" +- "Who else in your team deals with this?" +- "How do you find out about tools like this?" +- "What would make you switch away from your current solution?" + +### Bad questions + +- "Would you use X if it existed?" +- "Do you think this is a good idea?" +- "What features would you want?" +- "How much would you pay for this?" +- "Would you pay $X/month for this?" +- "Don't you think [problem] is really bad?" +- "If we built X, would that solve your problem?" + +## Detecting false positives + +Customers say things that sound like validation but aren't. + +**Compliments (ignore these):** +- "That's really interesting." +- "I could see that being useful." +- "You should definitely build that." +- "I know people who would use that." + +**Weak signals (not enough alone):** +- "I would use it if it had X feature." +- "I might sign up when it's live." +- "Send me info when you launch." + +**Strong signals (real validation):** +- They ask when it will be available and push for a date +- They offer to pay now, even before it's built +- They ask for a demo with their boss on the call +- They describe a specific workflow where they'd use it +- They give you a referral without being asked + +**The commitment test:** +"Would you be willing to [try the beta / join a pilot / pay a deposit / introduce me to your VP]?" + +Their reaction tells you more than anything they said before. + +## Extracting signal from praise + +When they say "I love it" — push: + +- "What specifically would you use it for?" +- "How often would that come up?" +- "How much time/money is that costing you now?" +- "What would you stop doing if you had this?" + +Turn abstract enthusiasm into concrete use cases and quantified pain. + +## Signs you found a real customer + +1. They describe the problem in detail without being asked +2. They have a workaround they're not happy with +3. They ask questions about your solution (not just compliment it) +4. They connect you to other people with the same problem +5. They put time/money on the line (small commitment = real interest) + +## Signs this is the wrong customer + +1. They can't remember a specific recent example of the problem +2. Their "solution" is to just not deal with it +3. They're enthusiastic but can't make a buying decision +4. Every answer is "it depends" without specifics +5. The problem you're describing is news to them + +## Interview format that works + +**Setup (2 min):** "I'm researching how [people like you] handle [domain]. Not selling anything. Want honest feedback." + +**Warm-up (3 min):** Ask about their role, day-to-day. Build context. + +**Core (15 min):** Story-based questions about past behavior. +- "Tell me about the last time..." +- "Walk me through what you did..." +- "What happened after that?" + +**Quantify (5 min):** Pin down real numbers. +- "How often does this happen?" +- "How long does it take?" +- "What does it cost you?" + +**Wrap-up (3 min):** "Who else would you suggest I talk to about this?" + +Total: 25-30 minutes. Don't go over — respect their time. + +## Pattern recognition across interviews + +After 10+ interviews: +- Which problems do they all mention? (signal) +- Which problems do only 1-2 mention? (noise or niche) +- Which solutions do they all currently use? (incumbent to beat) +- What do they all want but nobody builds? (gap) + +Don't average opinions. Look for patterns in specific behaviors. diff --git a/venture-analyst/references/traction.md b/venture-analyst/references/traction.md new file mode 100644 index 0000000000000000000000000000000000000000..e6c633cfdcfff59a3a05d1c5b989f2db15d65916 --- /dev/null +++ b/venture-analyst/references/traction.md @@ -0,0 +1,154 @@ +# Traction Reference + +Gabriel Weinberg & Justin Mares, "Traction" (2014). +Framework for finding customer acquisition channels that actually work. + +## Core insight + +Most startups fail not because of product problems but traction problems. Building product and finding traction should happen in parallel, not sequentially. + +Half your time should be spent on traction, not product — even pre-launch. + +## The 19 traction channels + +1. **Viral marketing** - product spreads through existing users (Dropbox, Slack) +2. **PR** - traditional press coverage +3. **Unconventional PR** - stunts, giveaways, memorable campaigns +4. **Search engine marketing (SEM)** - paid search (Google Ads) +5. **Social and display ads** - Meta, LinkedIn, programmatic +6. **Offline ads** - billboards, radio, TV, print +7. **Search engine optimization (SEO)** - organic search +8. **Content marketing** - blog, YouTube, podcast as acquisition +9. **Email marketing** - list building and outbound sequences +10. **Engineering as marketing** - free tools, calculators, embeds that drive signups +11. **Targeting blogs** - get covered by influential niche blogs +12. **Business development** - partnerships with complementary products +13. **Sales** - direct outbound sales, inside sales teams +14. **Affiliate programs** - revenue share for referrals +15. **Existing platforms** - App Store, Shopify App Store, Chrome extensions, marketplace distribution +16. **Trade shows** - industry events, conferences +17. **Offline events** - meetups, workshops, demos +18. **Speaking engagements** - conferences, podcasts, webinars +19. **Community building** - Slack groups, forums, ambassador programs + +## Bullseye Framework + +Don't try all 19 channels. Use the Bullseye to find the one that works. + +### Step 1 — Brainstorm (outer ring) +For each of the 19 channels, answer: +- How could we use this channel? +- What would success look like? +- How could we test it cheaply? + +Produces 19 rough hypotheses. + +### Step 2 — Rank (middle ring) +Score each channel on 3 criteria (1-3): +- Probability of working +- Volume potential (enough customers to matter) +- Cost per acquisition (time + money) + +Pick the top 6 channels. These are your "promising" channels. + +### Step 3 — Test (inner ring — the bullseye) +Pick the 3 channels with highest combined score. Run cheap, fast tests on each simultaneously. + +Each test: +- Fixed budget (~€200-500) +- Fixed time (2-4 weeks) +- One metric: cost per lead, or signups, or revenue + +### Step 4 — Focus +One test will outperform the others. Focus 100% on that channel. Optimize it until it no longer scales. + +When the channel plateaus, go back to Step 2. + +## Channel selection by stage + +Different channels dominate at different stages: + +| Stage | Best channels | +|-------|--------------| +| Pre-PMF | Direct sales, outreach, niche blogs, communities | +| Early growth | Content, SEO, partnerships, email | +| Scaling | Paid acquisition (once LTV:CAC proven), viral, affiliates | +| Dominant | Brand, PR, events | + +**Pre-PMF rule:** channels that require scale to work (SEO, paid ads, viral) are wrong for early stage. You don't have enough data to optimize them. Use channels where you control the targeting. + +## Channel-market fit + +Some channels only work for specific markets: + +| Channel | Works for | +|---------|-----------| +| Cold outbound | B2B with clear ICP, high LTV | +| Content/SEO | Markets that Google actively | +| Product-led viral | Consumer, collaboration tools | +| Community | Niche markets with strong identity | +| Paid social | B2C with wide audience | +| Trade shows | B2B with offline buying decisions | +| Partnerships | Products with complementary distribution | + +Picking the wrong channel for your market wastes 6 months. + +## Cost benchmarks (rough, 2024) + +| Channel | Metric | B2C benchmark | B2B benchmark | +|---------|--------|--------------|--------------| +| Facebook/Instagram ads | CPL | €3-8 | €15-30 | +| Google Search | CPL | €5-15 | €20-60 | +| LinkedIn ads | CPL | - | €40-100 | +| Cold email | CPL | - | €20-80 (time cost) | +| Content/SEO | CPL | €5-20 (long term) | €15-50 (long term) | +| Referral/viral | CPL | €1-5 | €5-20 | + +LTV must be at least 3x CAC for paid channels to be viable. + +## Traction metrics by channel + +**Viral / product-led:** +- K-factor = (invites per user) × (invite conversion rate). Target K > 0.5 for meaningful viral effect +- Time to viral loop: how fast does one user create another? + +**Content / SEO:** +- Organic sessions per month +- Keyword rankings for high-intent terms +- Time to rank: SEO takes 3-6 months minimum to show results + +**Email:** +- List growth rate per week +- Open rate: >30% is good for cold; >50% for warm +- Click-through: >5% is strong + +**Paid acquisition:** +- CPL and CAC (cost per acquired customer) +- Payback period: how many months to recoup CAC? +- ROAS (return on ad spend) + +**Sales:** +- Leads per week +- Conversion rate (leads to paying) +- Sales cycle length +- Average contract value + +## Applying to venture analysis + +In Phase 3 (validation), recommend channels based on: + +1. Market type (B2B vs B2C) +2. LTV estimate (high LTV = more channels viable) +3. Available budget +4. Target customer location (online? offline? specific communities?) + +For zero-budget startups, the viable channels are: +- Direct outreach (cold email/DM) — time only +- Niche community posting (Reddit, specific Slack/Discord groups) — time only +- Engineering as marketing — build a free tool that attracts ICPs +- Content on a specific topic ICP searches for + +For startups with €100-500/month budget: +- Fake door test (landing page + small paid traffic) +- LinkedIn outreach with Sales Navigator trial +- Sponsored newsletter in a niche (often €100-300 per send) diff --git a/venture-analyst/scripts/enhance_detect.py b/venture-analyst/scripts/enhance_detect.py new file mode 100644 index 0000000000000000000000000000000000000000..3b66e7fb46add710f49a66e0ae40a86e80d0291b --- /dev/null +++ b/venture-analyst/scripts/enhance_detect.py @@ -0,0 +1,172 @@ +""" +Level 2 auto-detection. +Silently detects available enhancements. Never asks the user for config. +""" +import os +import subprocess +import time +import requests + + +def detect_level() -> dict: + """ + Detect what's available. Returns a capability map. + Called once at session start — results cached for the session. + """ + return { + "docker": _has_docker(), + "searxng": _has_searxng(), + "veyrascrape_mcp": _has_env("VEYRASCRAPE_API_KEY"), + "github_token": _has_env("GITHUB_TOKEN"), + "exa_key": _has_env("EXA_API_KEY"), + "tavily_key": _has_env("TAVILY_API_KEY"), + "groq_key": _has_env("GROQ_API_KEY"), + } + + +def _has_env(key: str) -> bool: + val = os.environ.get(key, "").strip() + return bool(val) + + +def _has_docker() -> bool: + try: + result = subprocess.run( + ["docker", "info"], + capture_output=True, + timeout=4, + ) + return result.returncode == 0 + except Exception: + return False + + +def _has_searxng(ports: list[int] = None) -> bool: + if ports is None: + ports = [8080, 8888, 4000] + for port in ports: + try: + r = requests.get( + f"http://localhost:{port}/search", + params={"q": "test", "format": "json"}, + timeout=2, + ) + if r.status_code == 200: + return True + except Exception: + pass + return False + + +def ensure_searxng() -> dict: + """ + Launch SearXNG via Docker if available and not already running. + Returns status dict. + """ + if _has_searxng(): + return {"available": True, "url": "http://localhost:8080", "launched": False} + + if not _has_docker(): + return {"available": False, "reason": "docker_not_found"} + + try: + subprocess.run( + [ + "docker", "run", "-d", + "--name", "venture-searxng", + "-p", "8080:8080", + "searxng/searxng", + ], + capture_output=True, + timeout=45, + ) + # give it time to start + for _ in range(6): + time.sleep(2) + if _has_searxng(): + return {"available": True, "url": "http://localhost:8080", "launched": True} + return {"available": False, "reason": "searxng_start_timeout"} + except Exception as e: + return {"available": False, "reason": str(e)} + + +def search_searxng(query: str, url: str = "http://localhost:8080", limit: int = 10) -> list[dict]: + """Search via local SearXNG instance.""" + try: + r = requests.get( + f"{url}/search", + params={"q": query, "format": "json", "pageno": 1}, + timeout=10, + ) + results = r.json().get("results", [])[:limit] + return [ + { + "source": "searxng", + "title": res.get("title", ""), + "url": res.get("url", ""), + "text": res.get("content", ""), + "engine": res.get("engine", ""), + } + for res in results + ] + except Exception: + return [] + + +def search_with_exa(query: str, api_key: str, limit: int = 10) -> list[dict]: + """Semantic search via Exa API (optional Level 3 enhancement).""" + try: + import exa_py + exa = exa_py.Exa(api_key=api_key) + results = exa.search(query, num_results=limit, use_autoprompt=True) + return [ + { + "source": "exa", + "title": r.title or "", + "url": r.url, + "text": r.text or "", + } + for r in results.results + ] + except Exception: + return [] + + +def best_search(query: str, env: dict, limit: int = 10) -> list[dict]: + """ + Use the best available search method based on detected environment. + Priority: Exa > SearXNG > Tavily > ddgs + """ + # Level 3: Exa + if env.get("exa_key"): + results = search_with_exa(query, os.environ["EXA_API_KEY"], limit) + if results: + return results + + # Level 2: SearXNG + if env.get("searxng"): + results = search_searxng(query, limit=limit) + if results: + return results + + # Level 2: Tavily + if env.get("tavily_key"): + try: + from tavily import TavilyClient + client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"]) + response = client.search(query, max_results=limit) + return [ + { + "source": "tavily", + "title": r.get("title", ""), + "url": r.get("url", ""), + "text": r.get("content", ""), + } + for r in response.get("results", []) + ] + except Exception: + pass + + # Level 1: ddgs (always available) + from scripts.sources import search_web + return search_web(query, limit) diff --git a/venture-analyst/scripts/experiments.py b/venture-analyst/scripts/experiments.py new file mode 100644 index 0000000000000000000000000000000000000000..db641f8e223c7d26cf283e2b9b854a640edd58bc --- /dev/null +++ b/venture-analyst/scripts/experiments.py @@ -0,0 +1,228 @@ +""" +Validation experiment generator. +Based on Lean Startup + Mom Test methodology. +Outputs concrete, actionable experiments — not analysis. +""" + +EXPERIMENTS = { + "cold_outreach": { + "name": "Cold Outreach Test", + "type": "discovery", + "cost": "0€", + "duration": "5-7 days", + "effort": "medium", + "goal": "Validate problem exists and find early adopters", + "process": [ + "Identify 50 potential customers on LinkedIn, Reddit, or niche communities", + "Send short message: 'I'm researching [problem area] — would you spare 15 min?'", + "Do NOT pitch. Ask about their current process and pain.", + "Apply Mom Test: ask about past behavior, not future intentions", + ], + "metric": "Response rate + calls booked", + "success_criteria": "≥10% response rate, ≥3 discovery calls completed", + "red_flags": [ + "Everyone says 'great idea' but nobody wants a call", + "People say they'd pay but won't commit to a free beta", + "You have to explain the problem before they understand it", + ], + "mom_test_questions": [ + "Tell me how you currently handle [problem]. Walk me through your last time.", + "What's the most frustrating part of that process?", + "Have you tried other solutions? What happened?", + "How much time/money does this cost you per month right now?", + "What would you do if this problem disappeared tomorrow?", + ], + "bad_questions_to_avoid": [ + "Would you use a tool that does X?", + "Do you think this is a good idea?", + "Would you pay $X for this?", + "How much would you pay for this?", + ], + }, + + "fake_door": { + "name": "Fake Door Test", + "type": "demand_signal", + "cost": "0-30€", + "duration": "7-14 days", + "effort": "low", + "goal": "Measure real demand before building anything", + "process": [ + "Build a landing page (Carrd.co free, 30 min)", + "Describe the value proposition clearly — no features, only outcome", + "Add a CTA button: 'Join Waitlist' or 'Get Early Access'", + "When clicked: show 'Thanks, we'll notify you' — collect email", + "Drive traffic: 2-3 Reddit posts in relevant communities + LinkedIn", + "Track: page visits, CTA clicks, emails collected", + ], + "metric": "CTA click-through rate (CTR)", + "success_criteria": "≥5% CTR from cold traffic, ≥50 emails in 14 days", + "tools": [ + "Carrd.co — free landing pages", + "Tally.so — free forms with email collection", + "Google Analytics (free) — traffic tracking", + "Plausible.io — privacy-friendly alternative", + ], + "headline_formula": "[Outcome they want] without [current pain]", + "subheadline_formula": "[Product name] helps [target customer] [achieve outcome] by [key mechanism]", + "red_flags": [ + "CTR below 2% consistently", + "High visits but nobody clicks CTA", + "Traffic only from your own network — zero cold traffic", + ], + }, + + "waitlist": { + "name": "Waitlist Campaign", + "type": "demand_signal", + "cost": "0€", + "duration": "14-30 days", + "effort": "medium", + "goal": "Build an audience before building the product", + "process": [ + "Build landing with value proposition + email signup", + "Write 3-5 posts for relevant communities (Reddit, LinkedIn, Twitter)", + "Frame as 'I'm building X because Y — who's interested?'", + "Respond to every comment — this is customer research", + "Track: emails per day, most common questions, what resonates", + ], + "metric": "Email signups per week", + "success_criteria": "100 organic signups in 30 days without paid ads", + "tools": [ + "Beehiiv (free for <2500 subs) — email list", + "Carrd.co — landing", + "Reddit (organic, zero cost)", + "LinkedIn (organic, zero cost)", + ], + "red_flags": [ + "Only your friends/connections sign up", + "High unsubscribe rate on first email", + "Nobody shares it organically", + ], + }, + + "concierge_mvp": { + "name": "Concierge MVP", + "type": "value_validation", + "cost": "0€", + "duration": "2-4 weeks", + "effort": "high", + "goal": "Deliver the core value manually to 3-5 real users", + "process": [ + "Find 3-5 people with the problem (from outreach or waitlist)", + "Offer to do the service manually for free", + "Do it by hand: spreadsheets, email, calls, manual research", + "Observe where they get value, where they get frustrated", + "At the end: 'Would you pay X/month for this if it was automated?'", + ], + "metric": "Willingness to pay (real commitment, not hypothetical)", + "success_criteria": "≥3/5 users say yes to paying target price, 1+ pre-orders", + "key_question": "If this service disappeared tomorrow, what would you do?", + "what_to_track": [ + "Which parts of the manual process they value most", + "Which parts they don't care about", + "How often they'd use it", + "What would make them stop using it", + "Who else they'd recommend it to", + ], + "red_flags": [ + "'I'd use it but can't commit right now'", + "They use it but never refer anyone", + "The manual process takes so long it's not viable to automate", + ], + }, + + "smoke_test_ads": { + "name": "Paid Smoke Test", + "type": "demand_signal", + "cost": "30-100€", + "duration": "7 days", + "effort": "medium", + "goal": "Measure demand with cold paid traffic — no bias from network", + "process": [ + "Set up landing page with single CTA", + "Run €30-50 in Meta or Google Ads to target audience", + "B2C: Facebook/Instagram interest targeting", + "B2B: LinkedIn job title targeting (more expensive, ~€5-10/click)", + "Measure CPL (cost per lead) — this is your true demand signal", + ], + "metric": "Cost Per Lead (CPL)", + "success_criteria": "B2C CPL < 5€, B2B CPL < 40€", + "tools": [ + "Meta Ads Manager (€30 minimum budget)", + "Google Ads (€10/day minimum)", + "LinkedIn Ads (most expensive — B2B only, €15-50/click)", + ], + "red_flags": [ + "CPL 5x higher than benchmark — market doesn't respond to messaging", + "Clicks but no conversions — landing page or offer problem", + "Can't target audience precisely — unclear ICP", + ], + }, +} + + +def generate_experiments( + idea: str, + target_customer: str, + market_type: str = "b2c", + competition_level: str = "medium", + budget: str = "zero", +) -> list[dict]: + """ + Generate 3 prioritized validation experiments. + Always starts with the cheapest, highest-signal experiments first. + """ + selected = [] + + # Always: Cold outreach first (zero cost, real signal) + exp = EXPERIMENTS["cold_outreach"].copy() + exp["priority"] = 1 + exp["customized_for"] = idea + exp["target"] = target_customer + selected.append(exp) + + # Always: Fake door (low cost, measurable) + exp = EXPERIMENTS["fake_door"].copy() + exp["priority"] = 2 + exp["customized_for"] = idea + selected.append(exp) + + # B2B with budget: concierge MVP + # B2C with budget: waitlist + if market_type.lower() == "b2b" or competition_level == "high": + exp = EXPERIMENTS["concierge_mvp"].copy() + exp["priority"] = 3 + exp["customized_for"] = idea + exp["target"] = target_customer + selected.append(exp) + else: + exp = EXPERIMENTS["waitlist"].copy() + exp["priority"] = 3 + exp["customized_for"] = idea + selected.append(exp) + + return selected + + +def format_experiment_output(experiments: list[dict], idea: str) -> str: + """Format experiments as markdown output.""" + lines = [f"# Validation Experiments — {idea}\n"] + lines.append("Start with Experiment 1. Only move to the next if the previous fails.\n") + + for exp in experiments: + lines.append(f"## Experiment {exp['priority']}: {exp['name']}") + lines.append(f"**Cost:** {exp['cost']} | **Duration:** {exp['duration']} | **Effort:** {exp['effort']}") + lines.append(f"\n**Goal:** {exp['goal']}\n") + lines.append("**Process:**") + for step in exp.get("process", []): + lines.append(f"- {step}") + lines.append(f"\n**Metric:** {exp['metric']}") + lines.append(f"**Success:** {exp['success_criteria']}") + if exp.get("red_flags"): + lines.append("\n**Red flags to watch:**") + for flag in exp["red_flags"]: + lines.append(f"- {flag}") + lines.append("") + + return "\n".join(lines) diff --git a/venture-analyst/scripts/scraper.py b/venture-analyst/scripts/scraper.py new file mode 100644 index 0000000000000000000000000000000000000000..d5617be22ba179cc4ed640374c38376a8db80fb6 --- /dev/null +++ b/venture-analyst/scripts/scraper.py @@ -0,0 +1,194 @@ +""" +Scrapling wrapper for venture-analyst. +Handles competitor pages, pricing pages, and review sites. +Falls back gracefully on blocked sites. +""" +import re +from typing import Optional + + +def scrape_competitor(url: str) -> Optional[dict]: + """ + Scrape a competitor website. + Tries basic Fetcher first, falls back to StealthyFetcher for Cloudflare. + """ + try: + from scrapling import Fetcher + page = Fetcher(auto_match=False).get(url, timeout=15, stealthy_headers=True) + if _is_empty(page): + return _scrape_stealthy(url) + return _parse_competitor_page(url, page) + except Exception: + return _scrape_stealthy(url) + + +def _scrape_stealthy(url: str) -> Optional[dict]: + """StealthyFetcher for JS-rendered or Cloudflare-protected sites.""" + try: + from scrapling import StealthyFetcher + page = StealthyFetcher(auto_match=False).get(url, timeout=20, network_idle=True) + if _is_empty(page): + return None + return _parse_competitor_page(url, page) + except Exception: + return None + + +def _is_empty(page) -> bool: + if page is None: + return True + try: + return len(page.get_all_text()) < 100 + except Exception: + return True + + +def _parse_competitor_page(url: str, page) -> dict: + return { + "url": url, + "title": _get_title(page), + "tagline": _get_tagline(page), + "description": _get_meta_description(page), + "pricing": _get_pricing(page), + "features": _get_features(page), + "tech_stack": _get_tech_signals(page), + } + + +def _get_title(page) -> str: + try: + return page.css("title").first.text.strip()[:120] + except Exception: + return "" + + +def _get_tagline(page) -> str: + """Extract the main hero headline.""" + selectors = ["h1", "[class*='hero'] h1", "header h1", "[class*='headline']", "[class*='tagline']"] + for sel in selectors: + try: + el = page.css(sel).first + if el: + text = el.text.strip() + if 5 < len(text) < 200: + return text + except Exception: + pass + return "" + + +def _get_meta_description(page) -> str: + try: + el = page.css('meta[name="description"]').first + return (el.attrs.get("content") or "")[:300] + except Exception: + return "" + + +def _get_pricing(page) -> dict: + """Extract pricing signals from page text.""" + try: + text = page.get_all_text() + except Exception: + return {} + + prices = re.findall( + r'[\$€£]\s*(\d+(?:[.,]\d+)?)\s*(?:/\s*(?:mo|month|year|yr|user|seat))?', + text, + re.IGNORECASE, + ) + model_keywords = [ + "free", "freemium", "free trial", "per user", "per month", "per year", + "enterprise", "custom pricing", "contact us", "flat rate", + ] + detected_model = [kw for kw in model_keywords if kw.lower() in text.lower()] + + return { + "prices": prices[:6], + "model_signals": detected_model[:4], + "has_free_tier": any(kw in ["free", "freemium", "free trial"] for kw in detected_model), + } + + +def _get_features(page) -> list[str]: + """Extract feature descriptions from bullets and feature sections.""" + features = [] + try: + for el in page.css("li, [class*='feature'], [class*='benefit']")[:30]: + try: + text = el.text.strip() + if 10 < len(text) < 150 and not text.startswith(("©", "Terms", "Privacy")): + features.append(text) + except Exception: + pass + except Exception: + pass + return features[:12] + + +def _get_tech_signals(page) -> list[str]: + """Detect tech stack from script URLs and meta tags.""" + signals = [] + TECH_PATTERNS = { + "React": r"react", + "Vue": r"vue\.js|vuejs", + "Angular": r"angular", + "Next.js": r"_next/", + "Stripe": r"js\.stripe\.com", + "Intercom": r"intercom", + "Segment": r"segment\.com", + "HubSpot": r"hubspot", + "Webflow": r"webflow", + "Shopify": r"shopify", + } + try: + html = str(page) + for tech, pattern in TECH_PATTERNS.items(): + if re.search(pattern, html, re.IGNORECASE): + signals.append(tech) + except Exception: + pass + return signals + + +def scrape_g2_reviews(product_url: str, max_pages: int = 2) -> list[dict]: + """ + Scrape G2 reviews. Requires StealthyFetcher + Playwright. + G2 is Cloudflare-protected — basic Fetcher will fail. + """ + reviews = [] + try: + from scrapling import StealthyFetcher + for page_num in range(1, max_pages + 1): + url = f"{product_url}?page={page_num}" + page = StealthyFetcher(auto_match=False).get(url, timeout=25, network_idle=True) + if _is_empty(page): + break + for review in page.css("[itemprop='review'], .paper--white, [class*='review-card']"): + try: + reviews.append({ + "source": "g2", + "pros": _safe_text(review, "[class*='pros']"), + "cons": _safe_text(review, "[class*='cons']"), + "rating": _safe_attr(review, "[itemprop='ratingValue']", "content"), + "title": _safe_text(review, "h3, [class*='title']"), + }) + except Exception: + pass + except Exception: + pass + return reviews + + +def _safe_text(parent, selector: str) -> str: + try: + return parent.css(selector).first.text.strip()[:300] + except Exception: + return "" + + +def _safe_attr(parent, selector: str, attr: str) -> str: + try: + return parent.css(selector).first.attrs.get(attr, "") + except Exception: + return "" diff --git a/venture-analyst/scripts/sources.py b/venture-analyst/scripts/sources.py new file mode 100644 index 0000000000000000000000000000000000000000..9d103bfe602056d73ee886b2ff9e0d889a6d3c47 --- /dev/null +++ b/venture-analyst/scripts/sources.py @@ -0,0 +1,288 @@ +""" +Level 1 data sources for venture-analyst. +Zero API keys required. Works immediately after install. +""" +import time +import requests +from typing import Optional + +REDDIT_UA = "venture-analyst/1.0 (open-source research tool; github.com/veyralabsgroup/veyraskills)" + + +# ── HN Algolia (no auth, 10k req/hour) ──────────────────────────────────────── + +def search_hn(query: str, limit: int = 20) -> list[dict]: + """Search Hacker News via Algolia API. Best zero-key source.""" + url = "https://hn.algolia.com/api/v1/search" + params = { + "query": query, + "hitsPerPage": limit, + "tags": "(story,ask_hn,show_hn)", + } + try: + r = requests.get(url, params=params, timeout=10) + r.raise_for_status() + hits = r.json().get("hits", []) + return [ + { + "source": "hackernews", + "title": h.get("title") or (h.get("story_text", "")[:80] + "..."), + "url": h.get("url") or f"https://news.ycombinator.com/item?id={h.get('objectID')}", + "points": h.get("points", 0), + "comments": h.get("num_comments", 0), + "text": (h.get("story_text") or "")[:400], + "author": h.get("author", ""), + "date": h.get("created_at", "")[:10], + } + for h in hits + ] + except Exception: + return [] + + +def search_hn_comments(query: str, min_points: int = 5, limit: int = 30) -> list[dict]: + """Search HN comments — great for finding raw pain and opinions.""" + url = "https://hn.algolia.com/api/v1/search" + params = { + "query": query, + "hitsPerPage": limit, + "tags": "comment", + "numericFilters": f"points>{min_points}", + } + try: + r = requests.get(url, params=params, timeout=10) + r.raise_for_status() + hits = r.json().get("hits", []) + return [ + { + "source": "hackernews_comment", + "text": (h.get("comment_text") or "")[:500], + "url": f"https://news.ycombinator.com/item?id={h.get('objectID')}", + "points": h.get("points", 0), + "author": h.get("author", ""), + } + for h in hits if h.get("comment_text") + ] + except Exception: + return [] + + +# ── Reddit (no auth, custom UA required) ────────────────────────────────────── + +def search_reddit( + query: str, + subreddits: Optional[list[str]] = None, + limit: int = 25, + timeframe: str = "year", +) -> list[dict]: + """Search Reddit via public .json endpoint. Custom UA avoids 429s.""" + results = [] + headers = {"User-Agent": REDDIT_UA} + + if subreddits: + per_sub = max(5, limit // len(subreddits[:4])) + for sub in subreddits[:4]: + url = f"https://www.reddit.com/r/{sub}/search.json" + params = {"q": query, "sort": "top", "limit": per_sub, "t": timeframe, "restrict_sr": 1} + _reddit_fetch(url, params, headers, results) + time.sleep(1.2) + else: + url = "https://www.reddit.com/search.json" + params = {"q": query, "sort": "relevance", "limit": limit, "t": timeframe} + _reddit_fetch(url, params, headers, results) + + return results + + +def _reddit_fetch(url: str, params: dict, headers: dict, results: list) -> None: + try: + r = requests.get(url, params=params, headers=headers, timeout=12) + if r.status_code == 200: + for post in r.json().get("data", {}).get("children", []): + d = post.get("data", {}) + results.append({ + "source": "reddit", + "title": d.get("title", ""), + "url": f"https://reddit.com{d.get('permalink', '')}", + "upvotes": d.get("score", 0), + "comments": d.get("num_comments", 0), + "text": (d.get("selftext") or "")[:500], + "subreddit": d.get("subreddit", ""), + }) + except Exception: + pass + + +# ── GitHub Issues (no auth, 60 req/hour) ────────────────────────────────────── + +def search_github_issues( + query: str, + limit: int = 20, + token: Optional[str] = None, +) -> list[dict]: + """ + Search GitHub issues for pain points and feature requests. + Unauthenticated: 60 req/hour (enough for a single analysis). + With GITHUB_TOKEN: 5,000 req/hour. + """ + url = "https://api.github.com/search/issues" + params = { + "q": f"{query} type:issue", + "sort": "reactions", + "order": "desc", + "per_page": min(limit, 30), + } + headers = {"Accept": "application/vnd.github.v3+json"} + if token: + headers["Authorization"] = f"token {token}" + + try: + r = requests.get(url, params=params, headers=headers, timeout=12) + if r.status_code != 200: + return [] + return [ + { + "source": "github", + "title": i.get("title", ""), + "url": i.get("html_url", ""), + "reactions": i.get("reactions", {}).get("total_count", 0), + "comments": i.get("comments", 0), + "text": (i.get("body") or "")[:400], + "repo": i.get("repository_url", "").split("/")[-1], + "state": i.get("state", ""), + } + for i in r.json().get("items", []) + ] + except Exception: + return [] + + +def search_github_repos( + query: str, + limit: int = 10, + token: Optional[str] = None, +) -> list[dict]: + """Find existing repos/tools in the space.""" + url = "https://api.github.com/search/repositories" + params = { + "q": query, + "sort": "stars", + "order": "desc", + "per_page": min(limit, 20), + } + headers = {"Accept": "application/vnd.github.v3+json"} + if token: + headers["Authorization"] = f"token {token}" + + try: + r = requests.get(url, params=params, headers=headers, timeout=12) + if r.status_code != 200: + return [] + return [ + { + "source": "github_repo", + "name": repo.get("full_name", ""), + "url": repo.get("html_url", ""), + "stars": repo.get("stargazers_count", 0), + "description": repo.get("description", ""), + "language": repo.get("language", ""), + "updated": repo.get("updated_at", "")[:10], + } + for repo in r.json().get("items", []) + ] + except Exception: + return [] + + +# ── Web search (ddgs, no key) ────────────────────────────────────────────────── + +def search_web(query: str, limit: int = 10) -> list[dict]: + """Web search via ddgs. No API key. May rate-limit on heavy use.""" + try: + from ddgs import DDGS + results = [] + with DDGS() as ddgs: + for r in ddgs.text(query, max_results=limit): + results.append({ + "source": "web", + "title": r.get("title", ""), + "url": r.get("href", ""), + "text": r.get("body", ""), + }) + return results + except Exception: + return [] + + +# ── Google Trends (no key) ───────────────────────────────────────────────────── + +def get_trends(keyword: str) -> dict: + """Google Trends via trendspyg. Fragile but free.""" + try: + from trendspyg import TrendReq + pytrends = TrendReq(hl="en-US", tz=360) + pytrends.build_payload([keyword], timeframe="today 12-m") + data = pytrends.interest_over_time() + if data.empty: + return {"trend": "no_data", "avg_interest": 0} + + avg = float(data[keyword].mean()) + recent = float(data[keyword].iloc[-8:].mean()) + if recent > avg * 1.25: + trend = "rising" + elif recent < avg * 0.75: + trend = "declining" + else: + trend = "stable" + + related = {} + try: + related_data = pytrends.related_queries() + top = related_data.get(keyword, {}).get("top") + if top is not None and not top.empty: + related = {row["query"]: row["value"] for _, row in top.head(5).iterrows()} + except Exception: + pass + + return { + "trend": trend, + "avg_interest": round(avg, 1), + "recent_interest": round(recent, 1), + "related_queries": related, + } + except Exception: + return {"trend": "unavailable", "avg_interest": 0} + + +# ── Evidence Score ───────────────────────────────────────────────────────────── + +def calculate_evidence_score(results: dict) -> dict: + """ + Score the evidence quality collected across all sources. + Returns score 0-100 + breakdown per source. + """ + reddit = results.get("reddit", []) + hn = results.get("hackernews", []) + results.get("hackernews_comment", []) + github = results.get("github", []) + competitors = results.get("competitors", []) + trends = results.get("trends", {}) + + # Weighted scoring + reddit_pts = min(len(reddit) * 1.5, 25) + hn_pts = min(len(hn) * 2.5, 25) + github_pts = min(len(github) * 2, 20) + comp_pts = min(len(competitors) * 3, 20) + trend_pts = 10 if trends.get("trend") not in ("unavailable", "no_data") else 0 + + score = int(reddit_pts + hn_pts + github_pts + comp_pts + trend_pts) + + return { + "evidence_score": min(score, 100), + "breakdown": { + "reddit_mentions": len(reddit), + "hn_discussions": len(hn), + "github_issues": len(github), + "competitors_found": len(competitors), + "trend_data": trends.get("trend", "unavailable"), + }, + } diff --git a/venture-analyst/templates/experiment-spec.md b/venture-analyst/templates/experiment-spec.md new file mode 100644 index 0000000000000000000000000000000000000000..3bc7989dffddfeecb5829b9c3a120abbe6f9987e --- /dev/null +++ b/venture-analyst/templates/experiment-spec.md @@ -0,0 +1,119 @@ +# Experiment Spec + +Use this template when designing a specific validation experiment. Fill every section — vague experiments produce vague results. + +--- + +## Experiment: [name] + +**Idea being validated:** [one sentence] +**Hypothesis:** If [target customer] experiences [problem], then [% of them] will [take specific action] when shown [this offering]. +**Type:** discovery / demand_signal / value_validation / willingness_to_pay + +--- + +### Setup + +**Duration:** [X days/weeks] +**Budget:** [€0 / €X] +**Effort:** [hours estimated] + +**Who:** [exact target customer - be specific. "founders" is not specific. "B2B SaaS founders with 1-10 person team, pre-Series A" is specific.] + +**Channel:** [where you'll find them] +- Primary: [Reddit / LinkedIn / Cold email / specific community] +- Backup: [if primary fails] + +**What you're showing them:** +[Landing page URL / message template / demo link / mockup] + +--- + +### Metrics + +**Primary metric:** [one number] +**Target:** [specific threshold that determines pass/fail] + +| Metric | How to measure | Pass threshold | Fail threshold | +|--------|---------------|----------------|----------------| +| [primary] | [tool/method] | [number] | [number] | +| [secondary] | [tool/method] | [number] | [number] | + +**Data collection method:** [Google Analytics / Tally form / manual tracking / Stripe] + +--- + +### Scripts and materials + +**Outreach message (cold):** +``` +Subject: [subject line - no spam words] + +[message body - short, no pitch, curiosity-based] +``` + +**Interview opener:** +"I'm researching how [people like you] handle [problem area]. Not selling anything — want 15 minutes to understand your current process." + +**Landing page headline:** [outcome they want] without [current pain] +**CTA text:** [Join waitlist / Get early access / Book a call] + +--- + +### Mom Test checklist + +Before running interviews, verify all questions pass the Mom Test: + +- [ ] Questions ask about past behavior, not future intentions +- [ ] No hypotheticals ("would you use X?" is banned) +- [ ] No leading questions ("don't you find X frustrating?") +- [ ] No pitching during problem interviews +- [ ] Success criteria defined before starting (not after seeing results) + +--- + +### Week-by-week plan + +**Week 1:** +- [ ] [specific task] +- [ ] [specific task] + +**Week 2:** +- [ ] [specific task] +- [ ] Review data against thresholds + +--- + +### Results tracking + +| Date | Channel | Contacts/Views | Actions | Conversion | +|------|---------|----------------|---------|------------| +| | | | | | + +**Running total:** +- Primary metric: [X / target] +- Secondary: [X / target] + +--- + +### Decision rules + +**If primary metric >= target by [date]:** [proceed to next experiment / begin building] + +**If primary metric < 50% of target by [date]:** [pivot message / pivot target customer / kill experiment] + +**If qualitative signals contradict quantitative:** [investigate further — don't average them out] + +--- + +### Post-experiment notes + +**What worked:** + +**What didn't:** + +**Biggest surprise:** + +**Quote from a customer that changed how I think:** + +**Next experiment:** [or "begin building" if validated] diff --git a/venture-analyst/templates/verdict.md b/venture-analyst/templates/verdict.md new file mode 100644 index 0000000000000000000000000000000000000000..ec48294a89373667eb91763a55af0e80e0a719ae --- /dev/null +++ b/venture-analyst/templates/verdict.md @@ -0,0 +1,372 @@ +# Verdict Template + +Final output of venture-analyst. Complete all sections. No section is optional. +The goal is a decision, not a report. + +--- + +## [Idea Name] - Venture Verdict + +**Date:** [YYYY-MM-DD] +**Evidence Score:** [0-100] +**Evidence Quality:** [A/B/C/D/F] +**Recommendation:** BUILD / VALIDATE FIRST / AVOID + +--- + +## Evidence Summary + +### Source breakdown + +| Source | Count | Quality | Strongest signal | +|--------|-------|---------|-----------------| +| HN discussions | [n] | [H/M/L] | "[quote or title]" | +| Reddit posts | [n] | [H/M/L] | "[quote or title]" | +| GitHub issues | [n] | [H/M/L] | "[repo - issue title - reactions]" | +| Market trend | [rising/stable/declining] | - | [avg interest + period] | + +**Spending signals found:** [Yes - describe / No] +**Willingness-to-pay evidence:** [Yes - describe / No] + +### Competitor landscape + +| Name | Pricing | Users/Stars | Main weakness | +|------|---------|-------------|---------------| +| [A] | [price] | [n] | [gap] | +| [B] | [price] | [n] | [gap] | + +**Market gap identified:** [Yes - describe / No] +**Dominant player (>50% share):** [Yes - name / No] + +**Opportunity Score:** [0-100] - [how strong is the market?] +**Startup Score:** [0-100] - [how viable as a startup to execute?] + +--- + +## Confidence Engine + +``` +Confidence: [0-100] + +High confidence because: ++ [specific signal - e.g. "312 Reddit mentions across 8 subreddits"] ++ [specific signal - e.g. "84 GitHub issues with 50+ reactions"] ++ [specific signal - e.g. "14 paying competitors validated"] ++ [specific signal - e.g. "trend rising 40% over 12 months"] + +Confidence reduced because: +- [specific gap - e.g. "no direct willingness-to-pay signals found"] +- [specific gap - e.g. "search volume low, problem may not be searched"] +- [specific gap - e.g. "fragmented ICP - different pain in different segments"] +``` + +--- + +## Bull Case + +*Arguments for building this.* + +**Strongest evidence:** +[2-3 specific data points with sources. Quotes, numbers, URLs.] + +**Market timing:** +[Why now? What changed recently?] + +**Competitive angle:** +[What can this do that incumbents can't or won't?] + +**Best-case scenario:** +In 18 months: [specific outcome] if [specific condition] holds. + +--- + +## Bear Case + +*Steelman the opposition. Give it everything.* + +**Strongest evidence against:** +[2-3 specific counterarguments with data.] + +**Why existing solutions might be good enough:** +[What do incumbents have that would be hard to beat?] + +**Key risks:** +- Market: [specific] +- Timing: [specific] +- Execution: [specific] + +--- + +## Judge Verdict + +### Score + +| Signal | Present | Points | +|--------|---------|--------| +| Evidence score > 60 | [Y/N] | +2 / 0 | +| Evidence quality A or B | [Y/N] | +1 / 0 | +| Trend rising | [Y/N] | +1 / 0 | +| Clear competitor weakness | [Y/N] | +1 / 0 | +| No dominant player >50% | [Y/N] | +1 / 0 | +| B2B willingness-to-pay signals | [Y/N] | +1 / 0 | +| Price ceiling gap | [Y/N] | +1 / 0 | +| Evidence score < 30 | [Y/N] | 0 / -3 | +| Evidence quality D or F | [Y/N] | 0 / -2 | +| Trend declining | [Y/N] | 0 / -2 | +| Dominant player + free tier | [Y/N] | 0 / -2 | +| Niche < 10k users | [Y/N] | 0 / -1 | +| High-priority founder trap | [Y/N] | 0 / -2 | +| Reality check failed | [Y/N] | 0 / -2 | +| **Total** | | **[sum]** | + +### Recommendation + +**[BUILD / VALIDATE FIRST / AVOID]** +**Confidence:** [0-100] + +*Reasoning (2-3 sentences. Direct. Reference specific evidence):* +[What tipped the scale. What the judge weighed most heavily.] + +--- + +## Decision Intelligence + +### Contradictions + +[If none: "No significant contradictions detected."] + +[If found:] +``` +Contradiction: [name] + +[source A] shows: [signal] +[source B] shows: [signal] + +Interpretation: [explanation] +What to verify: [specific test that resolves this] +``` + +### Founder Traps + +[If none detected: "No founder traps detected."] + +[If found:] +``` +Trap: [trap name] +Evidence: [which signals matched] +Risk level: [high/medium] +``` + +### Reality Check + +[If execution path is clear: "No significant execution barriers identified."] + +[If barriers found:] +``` +Execution risk: [high/medium] + +Requires: [list constraints] + +Alternative path: [reduced scope or different entry point] +``` + +### Distribution Plan + +**Top 3 acquisition channels for this idea:** + +1. **[channel]** - [why, based on where the evidence was actually found] + - Test: [how to test this in 2 weeks with minimal budget] + +2. **[channel]** - [why] + - Test: [how to test] + +3. **[channel]** - [why] + - Test: [how to test] + +**Avoid:** +- [channel] - [specific reason for this market] + +### Time-To-First-Dollar + +**Estimate:** [X-Y weeks] + +| Week | Action | +|------|--------| +| 1-2 | [specific action] | +| 3-4 | [specific action] | +| 5-8 | [specific action] | + +**What would accelerate this:** [factor] +**What would delay this:** [risk] + +### Assumption Map + +``` +A1 (most dangerous): [core belief about problem/customer] +Confidence: [0-100] +Based on: [evidence or lack of it] +Validates if: [observable outcome] +Kills startup if: [falsified by X] + +A2: [monetization or switching assumption] +Confidence: [0-100] +Based on: [evidence or lack of it] +Validates if: [observable outcome] +Kills startup if: [falsified by X] + +A3: [distribution or growth assumption] +Confidence: [0-100] +Based on: [evidence or lack of it] +Validates if: [observable outcome] +Kills startup if: [falsified by X] +``` + +**Know:** [what the evidence clearly supports] +**Believe:** [what is inferred but not proven] +**Imagining:** [what has no data behind it] + +--- + +## Phase 6 - Execution & Moat + +### Moat Intelligence + +| Moat type | Score (1-10) | Reason | +|-----------|-------------|--------| +| Distribution | [n] | [one line] | +| Data | [n] | [one line] | +| Community | [n] | [one line] | +| Switching cost | [n] | [one line] | +| Execution | [n] | [one line] | + +**Overall Moat Score:** [weighted avg of top 2] +**Most realistic moat:** [type] +**How to build it:** [specific action, not generic advice] + +### PMF Simulation + +**Persona A:** [job title / situation] +- Current workflow: [specific] +- Main pain: [from research quotes] +- Adoption trigger: [what makes them switch] +- Adoption blocker: [what stops them] +- Simulated arc: discovers (Week 1) -> [Week 2] -> [Week 3: converts/churns] -> [Week 4] + +**Persona B:** [job title / situation] +- [same structure] + +**Persona C:** [job title / situation] +- [same structure] + +**PMF friction report:** +- Main blockers: [list, note how many personas affected] +- Easiest persona to convert first: [A/B/C] - because [reason] + +### Market Timing + +``` +Timing: [Too Early / Good Window / Late / Saturated] + +Signals: ++ [positive] +- [negative] + +Window: [closes in X months / open for X years / unclear] +Verdict: [one sentence] +``` + +### Execution Roadmap + +**Path:** [BUILD / VALIDATE FIRST / AVOID] +**Critical constraint:** [the one thing that must be true] + +| Week | Actions | Decision gate | +|------|---------|--------------| +| 1 | [specific tasks] | [pass/fail condition] | +| 2 | [specific tasks] | [pass/fail condition] | +| 3 | [specific tasks] | [pass/fail condition] | +| 4 | [goal] | [commit or reassess] | + +**Do NOT build yet:** +- [feature/system] - [reason] + +**Reassess if:** [specific trigger condition] + +### Kill Criteria + +``` +If after [n] days from starting: + +< [n] real customer interviews completed +< [n] people willing to schedule a call + Zero payment intent (pre-order, deposit, or signed LOI) + [idea-specific negative signal] + +-> Stop. Do not pivot yet. + First answer: was the problem real or did we imagine it? + +Pivot trigger (instead of stop): +If problem is validated but [specific step] fails +-> [specific narrower pivot] +``` + +--- + +## Next Steps + +### If BUILD + +**Start here (this week):** +[One specific action. Not "do customer research." Something like: "Post in r/[subreddit] describing the problem and ask how people handle it - aim for 10 DMs this week."] + +**MVP type:** [Concierge / Wizard of Oz / Fake door / Single feature] +**v0.1 scope:** [What it does. What it explicitly does NOT do.] +**First 10 customers:** [Where exactly. Who exactly.] + +--- + +### If VALIDATE FIRST + +**Critical unknown:** +[The one thing that must be proven before building. One sentence.] + +**Run this experiment:** +[Name from experiments.py] - [duration] - success = [metric >= threshold] + +**Decision point:** +If [metric] >= [threshold] in [X weeks]: proceed to build. +If not: [specific pivot or kill condition]. + +--- + +### If AVOID + +**Core problem:** +[One clear reason. Not a list.] + +**What would have to change:** +[Specific signal that would make this worth revisiting. Or: "nothing changes this - the structural problem is permanent."] + +**Adjacent opportunity (if found):** +[Related problem the research surfaced that IS worth pursuing.] + +### Pivot Engine + +[If no evidence-backed pivot exists: "No evidence-backed pivot found. Consider a different problem space entirely."] + +[If pivots found:] +``` +Pivot Option 1: [segment / problem / channel / scope] +From: [current idea] +To: [specific pivot] +Evidence: [signal from research that supports this] +Risk: [what could still be wrong] + +Pivot Option 2: [segment / problem / channel / scope] +From: [current idea] +To: [specific pivot] +Evidence: [signal from research that supports this] +Risk: [what could still be wrong] + +Recommended: Option [1/2] - [one sentence reason] +``` diff --git a/worker/heartbeat.ts b/worker/heartbeat.ts new file mode 100644 index 0000000000000000000000000000000000000000..a0b7d72de64ffc67b349b03a0746bd6a7e5caf8e --- /dev/null +++ b/worker/heartbeat.ts @@ -0,0 +1,7 @@ +import { FileNodepadStore } from "../lib/persistence/file-store" +import { runHeartbeat } from "../lib/heartbeat" +import { configuredBackupRemote } from "../lib/github-sync/backup" +const store=new FileNodepadStore(),workspace=process.argv[2]||process.env.NODEPAD_WORKSPACE_ID||"default",once=process.argv.includes("--once"),interval=Math.max(10,Number(process.env.NODEPAD_HEARTBEAT_INTERVAL_SECONDS||900))*1000 +async function tick(){try{const summary=await runHeartbeat(store,workspace,{backup:process.env.NODEPAD_GITHUB_SYNC==="true"?configuredBackupRemote():undefined});console.log(JSON.stringify(summary))}catch(e){console.error(e);process.exitCode=1}} +async function main(){await tick();if(!once)setInterval(tick,interval)} +void main()