diff --git a/.archon/commands/defaults/scout-consolidate-perf-plan.md b/.archon/commands/defaults/scout-consolidate-perf-plan.md new file mode 100644 index 0000000000..7d5f12e9b3 --- /dev/null +++ b/.archon/commands/defaults/scout-consolidate-perf-plan.md @@ -0,0 +1,96 @@ +--- +description: Merge per-route Scout profiles into a single implementation plan (plan.md) +argument-hint: (no arguments) +--- + +# Consolidate Scout performance plan + +**Workflow ID**: $WORKFLOW_ID +**Artifacts**: $ARTIFACTS_DIR + +--- + +## Mission + +Read: + +- `$ARTIFACTS_DIR/routes.json` +- `$ARTIFACTS_DIR/routes-summary.md` +- `$ARTIFACTS_DIR/profile-00.md` … `$ARTIFACTS_DIR/profile-09.md` (include only files that exist; skipped indices may say SKIPPED) + +Produce **one** implementation plan at **`$ARTIFACTS_DIR/plan.md`** that `archon-plan-setup` / `archon-confirm-plan` / `archon-implement-tasks` can consume. + +--- + +## Plan template (required sections) + +Use this structure (fill with real content from profiles): + +```markdown +# Performance: Scout hot-route optimizations + +## Summary +{1–2 sentences} + +## Mission +{Single goal statement} + +## NOT Building (Scope Limits) +- {Explicit non-goals — e.g. unrelated refactors, new features} +- Do not change behavior except latency/resource usage unless noted. + +## Success Criteria +- [ ] Each targeted route has measurable improvement or documented tradeoff +- [ ] Project validation suite passes (see CLAUDE.md) +- [ ] Scout shows no new regressions for these endpoints after deploy (verification note) + +## Files to Change + +| File | Action | +|------|--------| +| `{path}` | UPDATE | + +## Patterns to Mirror + +| Pattern | Source File | Lines | +|---------|-------------|-------| +| {name} | `{path}` | {lines} | + +## Task List + +### Task 1: {title} +**Action**: UPDATE +**Details**: {specific changes} +**Route**: `{METHOD} {path}` +**Validate**: {command} + +### Task 2: ... + +(Add one or more tasks per route or grouped fix.) + +## Validation Commands +1. Commands from CLAUDE.md / package.json (typecheck, lint, test). + +## Risks + +| Risk | Impact | Mitigation | +|------|--------|------------| +| {risk} | {H/M/L} | {mitigation} | +``` + +--- + +## Rules + +1. **Deduplicate** overlapping tasks if multiple profiles touch the same file. +2. **Order** tasks by dependency (models before handlers, shared utils first). +3. **Reference** actual symbols/files from the profile markdown files. +4. If profiles disagree, prefer the most evidence-backed recommendation and note the conflict in **Risks**. +5. Ignore profiles that are SKIPPED or empty. + +--- + +## Output + +- Write **`$ARTIFACTS_DIR/plan.md`** only (plan-setup will create `plan-context.md`). +- Stdout: `Plan written to $ARTIFACTS_DIR/plan.md with {N} tasks.` diff --git a/.archon/commands/defaults/scout-discover-routes.md b/.archon/commands/defaults/scout-discover-routes.md new file mode 100644 index 0000000000..d6a11ee320 --- /dev/null +++ b/.archon/commands/defaults/scout-discover-routes.md @@ -0,0 +1,67 @@ +--- +description: Query Scout APM for top slow + high-traffic routes and write routes.json +argument-hint: "[optional app name or app id — else first active app]" +--- + +# Scout route discovery + +**User message**: $ARGUMENTS +**Artifacts**: $ARTIFACTS_DIR + +--- + +## Mission + +Use **Scout MCP** tools to identify **up to 10 HTTP routes** to optimize. Combine: + +- **Slowest** endpoints (p95 / mean response time), and +- **Most hit** (throughput / request volume) + +Dedupe by route identity (method + path). If you have fewer than 10 distinct hot/slow routes, include the next candidates by severity. If Scout returns fewer than 10, use all available. + +--- + +## Prerequisites + +1. **MCP**: Scout tools should be available (`list_apps`, `get_app_endpoints`, `get_endpoint_metrics`, etc.). If MCP is unavailable, ask the user to set `SCOUT_API_KEY`, ensure Docker can run `scoutapp/scout-mcp-local`, or paste a Scout endpoints export into `$ARTIFACTS_DIR/scout-endpoints-export.json` (array of endpoint objects) and continue from that file. + +2. **App selection**: If `$ARGUMENTS` names an app or numeric id, use that. Otherwise call `list_apps` and pick the production app that matches this repo (name/hostname) or the most recently active app. State which app you chose. + +--- + +## Steps + +1. Call Scout MCP to list endpoints with metrics for the chosen app (`get_app_endpoints` or equivalent). + +2. Rank and select up to **10** routes using a clear rule, e.g.: + - Take top **5** by p95 latency (or worst mean response time if p95 missing). + - Take top **5** by throughput. + - Union, dedupe, then fill remaining slots by composite score: `latency × log(throughput)` or similar. + +3. Write **`$ARTIFACTS_DIR/routes.json`** — JSON array of exactly the chosen routes, each object including at least: + + - `rank` (1–10) + - `method` (e.g. `GET`) + - `path` (e.g. `/api/foo`) + - `scout_name` or endpoint id if the API exposes one + - `p95_ms`, `mean_ms` (numbers or null) + - `rpm` or throughput (number or null) + - `error_rate` if available + +4. Write **`$ARTIFACTS_DIR/routes-summary.md`** — human-readable table: rank, method, path, p95, throughput, notes. + +5. Print a one-line stdout summary: `Discovered N routes for app {name} (id {id}).` + +--- + +## Error handling + +- If no endpoints are returned: write `routes.json` as `[]`, explain in summary, and STOP with a clear error in stdout so the workflow can fail visibly. + +--- + +## Success criteria + +- `routes.json` exists and is valid JSON. +- `routes-summary.md` exists. +- At most 10 routes; each profile step can rely on fixed indices `0..N-1`. diff --git a/.archon/config.yaml b/.archon/config.yaml index beadf10287..fc7d70da71 100644 --- a/.archon/config.yaml +++ b/.archon/config.yaml @@ -1,5 +1,8 @@ worktree: baseBranch: dev + # Copy local .env into isolated worktrees so Scout MCP sees SCOUT_API_KEY when cwd is ~/.archon/workspaces/... + copyFiles: + - .env docs: path: packages/docs-web/src/content/docs diff --git a/.archon/scripts/ci-wait.js b/.archon/scripts/ci-wait.js new file mode 100644 index 0000000000..51907c19cb --- /dev/null +++ b/.archon/scripts/ci-wait.js @@ -0,0 +1,70 @@ +#!/usr/bin/env bun +/** + * Wait for GitHub CI on a PR to finish, with a hard wall-clock timeout. + * + * Usage: bun .archon/scripts/ci-wait.js [timeout-ms] + * + * Exit codes: + * 0 — all required checks passed + * 1 — at least one required check failed + * 3 — timeout reached before CI finished + * 2 — bad args / missing gh + * + * Used by archon-slack-feature-to-review-app to gate review-app deploy. + */ +import { spawn } from 'node:child_process'; + +const DEFAULT_TIMEOUT_MS = 60 * 60 * 1000; + +function main() { + const [pr, timeoutArg] = process.argv.slice(2); + + if (!pr) { + console.error('Usage: ci-wait.js [timeout-ms]'); + process.exit(2); + } + + const timeoutMs = timeoutArg ? Number(timeoutArg) : DEFAULT_TIMEOUT_MS; + if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { + console.error(`Invalid timeout-ms: ${timeoutArg}`); + process.exit(2); + } + + console.log( + `Waiting for CI on PR ${pr} (timeout: ${Math.round(timeoutMs / 1000)}s)...` + ); + + const child = spawn( + 'gh', + ['pr', 'checks', pr, '--watch', '--fail-fast', '--interval', '30'], + { stdio: 'inherit' } + ); + + let timedOut = false; + const timer = setTimeout(() => { + timedOut = true; + console.error(`\nCI wait timed out after ${Math.round(timeoutMs / 1000)}s`); + child.kill('SIGTERM'); + setTimeout(() => process.exit(3), 2000).unref(); + }, timeoutMs); + timer.unref(); + + child.on('exit', (code, _signal) => { + clearTimeout(timer); + if (timedOut) return; + if (code === 0) { + console.log('CI passed.'); + process.exit(0); + } + console.error(`CI failed (gh exit code ${code ?? 'null'})`); + process.exit(1); + }); + + child.on('error', err => { + clearTimeout(timer); + console.error(`Failed to spawn gh: ${err.message}`); + process.exit(2); + }); +} + +main(); diff --git a/.archon/scripts/dispatch-review-app.js b/.archon/scripts/dispatch-review-app.js new file mode 100644 index 0000000000..b7abfbccc0 --- /dev/null +++ b/.archon/scripts/dispatch-review-app.js @@ -0,0 +1,47 @@ +#!/usr/bin/env bun +/** + * Dispatch a GitHub Actions workflow_dispatch event on the given ref. + * + * Usage: bun .archon/scripts/dispatch-review-app.js + * + * Exits 0 on successful dispatch. Exits non-zero with a human-readable stderr + * message on any failure (missing args, gh not installed, gh call failed). + * + * Used by the archon-slack-feature-to-review-app workflow after CI passes + * to deploy a review app for the PR branch. + */ +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); + +async function main() { + const [workflowFile, ref] = process.argv.slice(2); + + if (!workflowFile || !ref) { + console.error('Usage: dispatch-review-app.js '); + process.exit(2); + } + + try { + const { stdout, stderr } = await execFileAsync('gh', [ + 'workflow', + 'run', + workflowFile, + '--ref', + ref, + ]); + if (stdout.trim()) console.log(stdout.trim()); + if (stderr.trim()) console.log(stderr.trim()); + console.log( + JSON.stringify({ dispatched: true, workflow: workflowFile, ref }) + ); + } catch (err) { + console.error( + `Failed to dispatch ${workflowFile} on ref ${ref}: ${err.stderr ?? err.message}` + ); + process.exit(1); + } +} + +void main(); diff --git a/.archon/scripts/fetch-review-app-url.js b/.archon/scripts/fetch-review-app-url.js new file mode 100644 index 0000000000..24b2157c21 --- /dev/null +++ b/.archon/scripts/fetch-review-app-url.js @@ -0,0 +1,107 @@ +#!/usr/bin/env bun +/** + * Poll a GitHub PR's comments for a review-app URL matching a regex. + * + * Usage: + * bun .archon/scripts/fetch-review-app-url.js [timeout-ms] [interval-ms] + * + * Exit codes: + * 0 — URL found; printed to stdout as the only stdout line + * 3 — timeout reached without a match + * 2 — bad args / gh failure / invalid regex / bad comments JSON + * + * The workflow consumes the trimmed stdout via $.output. + * All log lines go to stderr so the URL is the only stdout content. + */ +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); + +const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000; +const DEFAULT_INTERVAL_MS = 20 * 1000; + +async function pollOnce(pr, regex) { + const { stdout } = await execFileAsync('gh', [ + 'pr', + 'view', + pr, + '--json', + 'comments', + ]); + let parsed; + try { + parsed = JSON.parse(stdout); + } catch { + throw new Error(`gh returned non-JSON stdout: ${stdout.slice(0, 200)}`); + } + const comments = parsed.comments ?? []; + for (const c of comments) { + const match = typeof c.body === 'string' ? c.body.match(regex) : null; + if (match) return match[0]; + } + return null; +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function main() { + const [pr, regexStr, timeoutArg, intervalArg] = process.argv.slice(2); + + if (!pr || !regexStr) { + console.error( + 'Usage: fetch-review-app-url.js [timeout-ms] [interval-ms]' + ); + process.exit(2); + } + + let regex; + try { + regex = new RegExp(regexStr); + } catch (err) { + console.error( + `Invalid regex ${JSON.stringify(regexStr)}: ${err.message}` + ); + process.exit(2); + } + + const timeoutMs = timeoutArg ? Number(timeoutArg) : DEFAULT_TIMEOUT_MS; + const intervalMs = intervalArg ? Number(intervalArg) : DEFAULT_INTERVAL_MS; + if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { + console.error(`Invalid timeout-ms: ${timeoutArg}`); + process.exit(2); + } + if (!Number.isFinite(intervalMs) || intervalMs <= 0) { + console.error(`Invalid interval-ms: ${intervalArg}`); + process.exit(2); + } + + const deadline = Date.now() + timeoutMs; + console.error( + `Polling PR ${pr} for pattern ${regex} every ${Math.round(intervalMs / 1000)}s, up to ${Math.round(timeoutMs / 1000)}s total...` + ); + + while (Date.now() < deadline) { + try { + const match = await pollOnce(pr, regex); + if (match) { + console.log(match); + return; + } + } catch (err) { + console.error(`Poll error (will retry): ${err.message}`); + } + const remaining = deadline - Date.now(); + if (remaining <= 0) break; + await sleep(Math.min(intervalMs, remaining)); + } + + console.error( + `No matching comment found on PR ${pr} within ${Math.round(timeoutMs / 1000)}s.` + ); + process.exit(3); +} + +void main(); diff --git a/.archon/workflows/defaults/archon-scout-perf-roadmap.yaml b/.archon/workflows/defaults/archon-scout-perf-roadmap.yaml new file mode 100644 index 0000000000..e5b561ea81 --- /dev/null +++ b/.archon/workflows/defaults/archon-scout-perf-roadmap.yaml @@ -0,0 +1,488 @@ +name: archon-scout-perf-roadmap +description: | + Use when: Optimizing app performance using Scout APM. + Does: + 1. Scout MCP — top 10 slow + high-traffic HTTP routes + 2. Parallel analysis (10 nodes) — profile each route in the codebase + traces + 3. Consolidated optimization plan + 4. Interactive plan review until user approves + 5. Implement via plan pipeline (draft PR) + 6. Multi-agent code review + self-fix + 7. Mark PR ready when review satisfied + + Requires: SCOUT_API_KEY (self-hosted Docker MCP) or Scout endpoints export; Docker for scout-mcp-local. + Input: Optional app name/id after workflow message; e.g. "My App Name" or leave default. + + NOT for: Performance work without Scout (use archon-assist) or non-HTTP routes only. + +provider: claude +model: sonnet +interactive: true + +nodes: + # ═══════════════════════════════════════════════════════════════ + # PHASE 1 — Scout discovery + # ═══════════════════════════════════════════════════════════════ + + - id: discover-routes + command: scout-discover-routes + context: fresh + mcp: .archon/mcp/scout-apm.json + + - id: profile-00 + prompt: | + You are **one of ten parallel profilers** for the Scout performance roadmap. + + **Assigned index**: 0 (0-based). **Profile file**: `$ARTIFACTS_DIR/profile-00.md` + + ## Steps + + 1. Read `$ARTIFACTS_DIR/routes.json`. If there is **no** element at index 0, write to `$ARTIFACTS_DIR/profile-00.md` a single line `SKIPPED: no route at index 0` and stop. + 2. Otherwise take the route object at index 0. Map it to this codebase (routers, handlers, middleware, DB). + 3. Use **Scout MCP** when available: `get_endpoint_metrics`, `get_app_endpoint_traces`, `get_app_insights` for this app/endpoint to explain **where** time is spent (N+1, slow queries, external I/O, etc.). + 4. Read CLAUDE.md and search the codebase for the implementation path. + 5. Write **`$ARTIFACTS_DIR/profile-00.md`** with: + - Route identity (method + path) + - Scout metrics summary + - Code path (files + symbols) + - Root causes of slowness + - Concrete optimization ideas (caching, batching, indexes, async, etc.) + + Keep stdout to a short summary line for logs. + depends_on: [discover-routes] + context: fresh + mcp: .archon/mcp/scout-apm.json + + - id: profile-01 + prompt: | + You are **one of ten parallel profilers** for the Scout performance roadmap. + + **Assigned index**: 1 (0-based). **Profile file**: `$ARTIFACTS_DIR/profile-01.md` + + ## Steps + + 1. Read `$ARTIFACTS_DIR/routes.json`. If there is **no** element at index 1, write to `$ARTIFACTS_DIR/profile-01.md` a single line `SKIPPED: no route at index 1` and stop. + 2. Otherwise take the route object at index 1. Map it to this codebase (routers, handlers, middleware, DB). + 3. Use **Scout MCP** when available: `get_endpoint_metrics`, `get_app_endpoint_traces`, `get_app_insights` for this app/endpoint to explain **where** time is spent (N+1, slow queries, external I/O, etc.). + 4. Read CLAUDE.md and search the codebase for the implementation path. + 5. Write **`$ARTIFACTS_DIR/profile-01.md`** with: + - Route identity (method + path) + - Scout metrics summary + - Code path (files + symbols) + - Root causes of slowness + - Concrete optimization ideas (caching, batching, indexes, async, etc.) + + Keep stdout to a short summary line for logs. + depends_on: [discover-routes] + context: fresh + mcp: .archon/mcp/scout-apm.json + + - id: profile-02 + prompt: | + You are **one of ten parallel profilers** for the Scout performance roadmap. + + **Assigned index**: 2 (0-based). **Profile file**: `$ARTIFACTS_DIR/profile-02.md` + + ## Steps + + 1. Read `$ARTIFACTS_DIR/routes.json`. If there is **no** element at index 2, write to `$ARTIFACTS_DIR/profile-02.md` a single line `SKIPPED: no route at index 2` and stop. + 2. Otherwise take the route object at index 2. Map it to this codebase (routers, handlers, middleware, DB). + 3. Use **Scout MCP** when available: `get_endpoint_metrics`, `get_app_endpoint_traces`, `get_app_insights` for this app/endpoint to explain **where** time is spent (N+1, slow queries, external I/O, etc.). + 4. Read CLAUDE.md and search the codebase for the implementation path. + 5. Write **`$ARTIFACTS_DIR/profile-02.md`** with: + - Route identity (method + path) + - Scout metrics summary + - Code path (files + symbols) + - Root causes of slowness + - Concrete optimization ideas (caching, batching, indexes, async, etc.) + + Keep stdout to a short summary line for logs. + depends_on: [discover-routes] + context: fresh + mcp: .archon/mcp/scout-apm.json + + - id: profile-03 + prompt: | + You are **one of ten parallel profilers** for the Scout performance roadmap. + + **Assigned index**: 3 (0-based). **Profile file**: `$ARTIFACTS_DIR/profile-03.md` + + ## Steps + + 1. Read `$ARTIFACTS_DIR/routes.json`. If there is **no** element at index 3, write to `$ARTIFACTS_DIR/profile-03.md` a single line `SKIPPED: no route at index 3` and stop. + 2. Otherwise take the route object at index 3. Map it to this codebase (routers, handlers, middleware, DB). + 3. Use **Scout MCP** when available: `get_endpoint_metrics`, `get_app_endpoint_traces`, `get_app_insights` for this app/endpoint to explain **where** time is spent (N+1, slow queries, external I/O, etc.). + 4. Read CLAUDE.md and search the codebase for the implementation path. + 5. Write **`$ARTIFACTS_DIR/profile-03.md`** with: + - Route identity (method + path) + - Scout metrics summary + - Code path (files + symbols) + - Root causes of slowness + - Concrete optimization ideas (caching, batching, indexes, async, etc.) + + Keep stdout to a short summary line for logs. + depends_on: [discover-routes] + context: fresh + mcp: .archon/mcp/scout-apm.json + + - id: profile-04 + prompt: | + You are **one of ten parallel profilers** for the Scout performance roadmap. + + **Assigned index**: 4 (0-based). **Profile file**: `$ARTIFACTS_DIR/profile-04.md` + + ## Steps + + 1. Read `$ARTIFACTS_DIR/routes.json`. If there is **no** element at index 4, write to `$ARTIFACTS_DIR/profile-04.md` a single line `SKIPPED: no route at index 4` and stop. + 2. Otherwise take the route object at index 4. Map it to this codebase (routers, handlers, middleware, DB). + 3. Use **Scout MCP** when available: `get_endpoint_metrics`, `get_app_endpoint_traces`, `get_app_insights` for this app/endpoint to explain **where** time is spent (N+1, slow queries, external I/O, etc.). + 4. Read CLAUDE.md and search the codebase for the implementation path. + 5. Write **`$ARTIFACTS_DIR/profile-04.md`** with: + - Route identity (method + path) + - Scout metrics summary + - Code path (files + symbols) + - Root causes of slowness + - Concrete optimization ideas (caching, batching, indexes, async, etc.) + + Keep stdout to a short summary line for logs. + depends_on: [discover-routes] + context: fresh + mcp: .archon/mcp/scout-apm.json + + - id: profile-05 + prompt: | + You are **one of ten parallel profilers** for the Scout performance roadmap. + + **Assigned index**: 5 (0-based). **Profile file**: `$ARTIFACTS_DIR/profile-05.md` + + ## Steps + + 1. Read `$ARTIFACTS_DIR/routes.json`. If there is **no** element at index 5, write to `$ARTIFACTS_DIR/profile-05.md` a single line `SKIPPED: no route at index 5` and stop. + 2. Otherwise take the route object at index 5. Map it to this codebase (routers, handlers, middleware, DB). + 3. Use **Scout MCP** when available: `get_endpoint_metrics`, `get_app_endpoint_traces`, `get_app_insights` for this app/endpoint to explain **where** time is spent (N+1, slow queries, external I/O, etc.). + 4. Read CLAUDE.md and search the codebase for the implementation path. + 5. Write **`$ARTIFACTS_DIR/profile-05.md`** with: + - Route identity (method + path) + - Scout metrics summary + - Code path (files + symbols) + - Root causes of slowness + - Concrete optimization ideas (caching, batching, indexes, async, etc.) + + Keep stdout to a short summary line for logs. + depends_on: [discover-routes] + context: fresh + mcp: .archon/mcp/scout-apm.json + + - id: profile-06 + prompt: | + You are **one of ten parallel profilers** for the Scout performance roadmap. + + **Assigned index**: 6 (0-based). **Profile file**: `$ARTIFACTS_DIR/profile-06.md` + + ## Steps + + 1. Read `$ARTIFACTS_DIR/routes.json`. If there is **no** element at index 6, write to `$ARTIFACTS_DIR/profile-06.md` a single line `SKIPPED: no route at index 6` and stop. + 2. Otherwise take the route object at index 6. Map it to this codebase (routers, handlers, middleware, DB). + 3. Use **Scout MCP** when available: `get_endpoint_metrics`, `get_app_endpoint_traces`, `get_app_insights` for this app/endpoint to explain **where** time is spent (N+1, slow queries, external I/O, etc.). + 4. Read CLAUDE.md and search the codebase for the implementation path. + 5. Write **`$ARTIFACTS_DIR/profile-06.md`** with: + - Route identity (method + path) + - Scout metrics summary + - Code path (files + symbols) + - Root causes of slowness + - Concrete optimization ideas (caching, batching, indexes, async, etc.) + + Keep stdout to a short summary line for logs. + depends_on: [discover-routes] + context: fresh + mcp: .archon/mcp/scout-apm.json + + - id: profile-07 + prompt: | + You are **one of ten parallel profilers** for the Scout performance roadmap. + + **Assigned index**: 7 (0-based). **Profile file**: `$ARTIFACTS_DIR/profile-07.md` + + ## Steps + + 1. Read `$ARTIFACTS_DIR/routes.json`. If there is **no** element at index 7, write to `$ARTIFACTS_DIR/profile-07.md` a single line `SKIPPED: no route at index 7` and stop. + 2. Otherwise take the route object at index 7. Map it to this codebase (routers, handlers, middleware, DB). + 3. Use **Scout MCP** when available: `get_endpoint_metrics`, `get_app_endpoint_traces`, `get_app_insights` for this app/endpoint to explain **where** time is spent (N+1, slow queries, external I/O, etc.). + 4. Read CLAUDE.md and search the codebase for the implementation path. + 5. Write **`$ARTIFACTS_DIR/profile-07.md`** with: + - Route identity (method + path) + - Scout metrics summary + - Code path (files + symbols) + - Root causes of slowness + - Concrete optimization ideas (caching, batching, indexes, async, etc.) + + Keep stdout to a short summary line for logs. + depends_on: [discover-routes] + context: fresh + mcp: .archon/mcp/scout-apm.json + + - id: profile-08 + prompt: | + You are **one of ten parallel profilers** for the Scout performance roadmap. + + **Assigned index**: 8 (0-based). **Profile file**: `$ARTIFACTS_DIR/profile-08.md` + + ## Steps + + 1. Read `$ARTIFACTS_DIR/routes.json`. If there is **no** element at index 8, write to `$ARTIFACTS_DIR/profile-08.md` a single line `SKIPPED: no route at index 8` and stop. + 2. Otherwise take the route object at index 8. Map it to this codebase (routers, handlers, middleware, DB). + 3. Use **Scout MCP** when available: `get_endpoint_metrics`, `get_app_endpoint_traces`, `get_app_insights` for this app/endpoint to explain **where** time is spent (N+1, slow queries, external I/O, etc.). + 4. Read CLAUDE.md and search the codebase for the implementation path. + 5. Write **`$ARTIFACTS_DIR/profile-08.md`** with: + - Route identity (method + path) + - Scout metrics summary + - Code path (files + symbols) + - Root causes of slowness + - Concrete optimization ideas (caching, batching, indexes, async, etc.) + + Keep stdout to a short summary line for logs. + depends_on: [discover-routes] + context: fresh + mcp: .archon/mcp/scout-apm.json + + - id: profile-09 + prompt: | + You are **one of ten parallel profilers** for the Scout performance roadmap. + + **Assigned index**: 9 (0-based). **Profile file**: `$ARTIFACTS_DIR/profile-09.md` + + ## Steps + + 1. Read `$ARTIFACTS_DIR/routes.json`. If there is **no** element at index 9, write to `$ARTIFACTS_DIR/profile-09.md` a single line `SKIPPED: no route at index 9` and stop. + 2. Otherwise take the route object at index 9. Map it to this codebase (routers, handlers, middleware, DB). + 3. Use **Scout MCP** when available: `get_endpoint_metrics`, `get_app_endpoint_traces`, `get_app_insights` for this app/endpoint to explain **where** time is spent (N+1, slow queries, external I/O, etc.). + 4. Read CLAUDE.md and search the codebase for the implementation path. + 5. Write **`$ARTIFACTS_DIR/profile-09.md`** with: + - Route identity (method + path) + - Scout metrics summary + - Code path (files + symbols) + - Root causes of slowness + - Concrete optimization ideas (caching, batching, indexes, async, etc.) + + Keep stdout to a short summary line for logs. + depends_on: [discover-routes] + context: fresh + mcp: .archon/mcp/scout-apm.json + + # ═══════════════════════════════════════════════════════════════ + # PHASE 2 — Single plan from profiles + # ═══════════════════════════════════════════════════════════════ + + - id: consolidate-plan + command: scout-consolidate-perf-plan + depends_on: [profile-00, profile-01, profile-02, profile-03, profile-04, profile-05, profile-06, profile-07, profile-08, profile-09] + context: fresh + + # ═══════════════════════════════════════════════════════════════ + # PHASE 3 — Interactive plan review (human edits) + # ═══════════════════════════════════════════════════════════════ + + - id: refine-plan + depends_on: [consolidate-plan] + loop: + prompt: | + # Scout perf roadmap — Plan review + + The consolidated plan lives at `$ARTIFACTS_DIR/plan.md`. + + **User's latest message**: $LOOP_USER_INPUT + + --- + + ## If the first iteration (no user input yet): + + 1. Read `$ARTIFACTS_DIR/plan.md` and `$ARTIFACTS_DIR/routes-summary.md`. + 2. Present a clear summary: goals, tasks, risks, and what will change. + 3. Ask for feedback (scope, ordering, risk tradeoffs). + + **Do not** emit `PLAN_APPROVED` on the first iteration. + + ## If the user gave feedback: + + 1. Edit `$ARTIFACTS_DIR/plan.md` to incorporate changes (tasks, scope, exclusions). + 2. Show what changed. + 3. Ask for more feedback or approval. + + ## If the user gave **explicit approval** (e.g. "approved", "looks good", "implement", "ship it"): + + Output: "Plan approved. Proceeding to implementation setup." + Then emit: `PLAN_APPROVED` + + **CRITICAL**: Never emit `PLAN_APPROVED` unless the latest user message clearly approves. Questions are not approval. + until: PLAN_APPROVED + max_iterations: 15 + interactive: true + gate_message: | + Review the plan. Request changes, or say "approved" to implement (draft PR next). + + # ═══════════════════════════════════════════════════════════════ + # PHASE 4 — Plan execution pipeline (same as plan-to-pr) + # ═══════════════════════════════════════════════════════════════ + + - id: plan-setup + command: archon-plan-setup + depends_on: [refine-plan] + context: fresh + + - id: confirm-plan + command: archon-confirm-plan + depends_on: [plan-setup] + context: fresh + + - id: implement-tasks + command: archon-implement-tasks + depends_on: [confirm-plan] + context: fresh + model: claude-opus-4-6[1m] + + - id: validate + command: archon-validate + depends_on: [implement-tasks] + context: fresh + + # ═══════════════════════════════════════════════════════════════ + # PHASE 5 — Draft PR (not ready for review yet) + # ═══════════════════════════════════════════════════════════════ + + - id: create-draft-pr + prompt: | + Create a **draft** pull request for the current branch (Scout perf optimizations). + + 1. Ensure all changes are committed; push `git push -u origin HEAD`. + 2. Read `$ARTIFACTS_DIR/plan.md` and `$ARTIFACTS_DIR/routes-summary.md` for context. + 3. If a PR already exists: `gh pr list --head $(git branch --show-current)` — report URL and skip creation. + 4. Else use the repo PR template if present; fill every section. + 5. Create with: `gh pr create --draft --base $BASE_BRANCH` — title like `perf: Scout hot-route optimizations`. + 6. Save `gh pr view --json number,url` to `$ARTIFACTS_DIR/.pr-url` if helpful. + + Summarize PR URL and draft status. + depends_on: [validate] + context: fresh + + # ═══════════════════════════════════════════════════════════════ + # PHASE 6 — Code review (same pattern as archon-fix-github-issue) + # ═══════════════════════════════════════════════════════════════ + + - id: review-scope + command: archon-pr-review-scope + depends_on: [create-draft-pr] + context: fresh + + - id: review-classify + prompt: | + You are a PR review classifier for a Scout performance PR. + + ## PR scope + $review-scope.output + + Rules: + - **Code review**: ALWAYS run (`true`). + - **Error handling**: run if diff touches try/catch, async error paths, or new failure modes. + - **Test coverage**: run if diff touches non-test source. + - **Comment quality**: run if diff changes comments/docstrings meaningfully. + - **Docs impact**: run if public APIs, env vars, or user-facing behavior docs change. + + Provide brief reasoning. + depends_on: [review-scope] + model: haiku + allowed_tools: [] + context: fresh + output_format: + type: object + properties: + run_code_review: + type: string + enum: ["true", "false"] + run_error_handling: + type: string + enum: ["true", "false"] + run_test_coverage: + type: string + enum: ["true", "false"] + run_comment_quality: + type: string + enum: ["true", "false"] + run_docs_impact: + type: string + enum: ["true", "false"] + reasoning: + type: string + required: + - run_code_review + - run_error_handling + - run_test_coverage + - run_comment_quality + - run_docs_impact + - reasoning + + - id: code-review + command: archon-code-review-agent + depends_on: [review-classify] + context: fresh + + - id: error-handling + command: archon-error-handling-agent + depends_on: [review-classify] + when: "$review-classify.output.run_error_handling == 'true'" + context: fresh + + - id: test-coverage + command: archon-test-coverage-agent + depends_on: [review-classify] + when: "$review-classify.output.run_test_coverage == 'true'" + context: fresh + + - id: comment-quality + command: archon-comment-quality-agent + depends_on: [review-classify] + when: "$review-classify.output.run_comment_quality == 'true'" + context: fresh + + - id: docs-impact + command: archon-docs-impact-agent + depends_on: [review-classify] + when: "$review-classify.output.run_docs_impact == 'true'" + context: fresh + + - id: synthesize + command: archon-synthesize-review + depends_on: [code-review, error-handling, test-coverage, comment-quality, docs-impact] + trigger_rule: one_success + context: fresh + + - id: self-fix + command: archon-self-fix-all + depends_on: [synthesize] + context: fresh + + - id: simplify + command: archon-simplify-changes + depends_on: [self-fix] + context: fresh + + # ═══════════════════════════════════════════════════════════════ + # PHASE 7 — Mark PR ready (review concerns addressed) + # ═══════════════════════════════════════════════════════════════ + + - id: mark-pr-ready + prompt: | + Scout perf workflow — finalize the PR. + + 1. Run validation: `bun run validate` or the repo's full check from CLAUDE.md. + 2. Push any fixes: `git push origin HEAD`. + 3. **Mark the PR ready for review** (exit draft): `gh pr ready` (or `gh pr ready ` if needed). + 4. Post a short comment on the PR summarizing: Scout routes targeted, key changes, validation status. + + If `gh pr ready` is not appropriate (already ready, or permission), report current PR state from `gh pr view`. + + Output: + - PR URL + - Draft vs open (ready) state + - Next steps for the human (optional) + depends_on: [simplify] + context: fresh diff --git a/.archon/workflows/defaults/archon-slack-feature-to-review-app.yaml b/.archon/workflows/defaults/archon-slack-feature-to-review-app.yaml new file mode 100644 index 0000000000..502ab5eb04 --- /dev/null +++ b/.archon/workflows/defaults/archon-slack-feature-to-review-app.yaml @@ -0,0 +1,405 @@ +name: archon-slack-feature-to-review-app +description: | + Use when: User on Slack/chat asks @archie to build, add, or implement a + feature end-to-end and wants a working review app at the end. Matches + phrases like "build X", "add feature Y", "implement Z", "ship a feature + that...". + Input: Feature description in natural language. + Output: PR ready for review + review-app URL posted back to the thread. + NOT for: Spec/PRD only (use archon-interactive-prd), code-only changes + without a spec (use archon-idea-to-pr), or bug fixes + (use archon-fix-github-issue). + +provider: claude +interactive: true + +nodes: + # ═══════════════════════════════════════════════════════════════ + # PHASE A — SPEC CREATION (bounded 3-iteration revision loop) + # ═══════════════════════════════════════════════════════════════ + + - id: spec + model: sonnet + loop: + prompt: | + # Feature request → spec + + You are turning a Slack-submitted feature request into a focused + implementation spec, through iterative dialogue. + + **Original request**: $ARGUMENTS + **User's latest reply**: $LOOP_USER_INPUT + + --- + + ## If this is the first iteration ($LOOP_USER_INPUT is empty): + + 1. Restate your understanding of the request in 1-2 sentences. + 2. Explore the codebase briefly (CLAUDE.md, directory structure, + files obviously related to the feature). + 3. Ask a tight set of 3-5 clarifying questions focused on DECISIONS + (scope boundaries, which existing code to extend, test + expectations, explicit out-of-scope items). + 4. End with: "Answer the questions and I'll draft a spec." + 5. Do NOT emit the approval signal yet. + + ## If the user has replied: + + 1. Process their answers. + 2. If you now have enough to draft a spec, write it to + `.claude/archon/specs/.spec.md` with these sections: + - Problem + - Proposed change (which files, functions, interfaces) + - Out of scope + - Acceptance criteria (specific, testable bullets) + - Testing plan + 3. Present a condensed summary of the spec in-thread (not the full + file), end with: "Reply `approved` to implement, or tell me what + to change." + 4. If the user's latest reply EXPLICITLY approves (contains + "approved", "looks good", "ship it", "go"), emit + SPEC_APPROVED and stop. Otherwise, revise the + spec file based on their feedback and re-summarize. + + **CRITICAL**: Never emit SPEC_APPROVED unless the + user's LATEST message explicitly approves. Questions, feedback, and + change requests are NOT approval. + until: SPEC_APPROVED + max_iterations: 3 + interactive: true + gate_message: | + Answer the questions above, or reply "approved" once the spec looks right. + + - id: announce-spec-approved + depends_on: [spec] + bash: 'echo "🧠 Spec approved. Creating implementation plan..."' + + # ═══════════════════════════════════════════════════════════════ + # PHASE B — PLAN + # ═══════════════════════════════════════════════════════════════ + + - id: create-plan + command: archon-create-plan + depends_on: [announce-spec-approved] + context: fresh + + - id: refine-plan + depends_on: [create-plan] + model: sonnet + loop: + prompt: | + # Feature-to-review-app — Plan review + + The implementation plan lives at `$ARTIFACTS_DIR/plan.md`. + + **User's latest reply**: $LOOP_USER_INPUT + + --- + + ## If this is the first iteration ($LOOP_USER_INPUT is empty): + + 1. Read `$ARTIFACTS_DIR/plan.md`. + 2. Post a condensed in-thread summary (NOT the full file): goal, + ordered task list (one line each), files that will change, risks + or tradeoffs worth flagging, and anything you explicitly left + out of scope. + 3. End with: "Reply `approved` to implement, or tell me what to + change (scope, ordering, tasks to add/drop)." + + **Do not** emit `PLAN_APPROVED` on the first + iteration. + + ## If the user gave feedback: + + 1. Edit `$ARTIFACTS_DIR/plan.md` directly to incorporate the + changes (add/remove/modify tasks, tighten scope, adjust + ordering, etc.). + 2. Post a short "Changes made" summary of what you edited. + 3. Ask for more feedback or approval. + + ## If the user's latest reply EXPLICITLY approves + (contains "approved", "looks good", "ship it", "go"): + + Output: "Plan approved. Setting up the implementation worktree." + Then emit: `PLAN_APPROVED` + + **CRITICAL**: Never emit `PLAN_APPROVED` unless + the user's LATEST message explicitly approves. Questions, feedback, + and change requests are NOT approval. If the user rejects outright + (e.g. "no", "cancel", "stop"), acknowledge and wait for further + instructions — do NOT emit the approval signal. + until: PLAN_APPROVED + max_iterations: 5 + interactive: true + gate_message: | + Review the plan summary. Request changes, or reply "approved" to start implementation. + + - id: plan-setup + command: archon-plan-setup + depends_on: [refine-plan] + context: fresh + + - id: announce-plan-ready + depends_on: [plan-setup] + bash: 'echo "🏗️ Plan approved. Implementing in a fresh worktree..."' + + # ═══════════════════════════════════════════════════════════════ + # PHASE C — IMPLEMENT + VALIDATE + # ═══════════════════════════════════════════════════════════════ + + - id: implement-tasks + command: archon-implement-tasks + depends_on: [announce-plan-ready] + context: fresh + model: claude-opus-4-6[1m] + + - id: validate + command: archon-validate + depends_on: [implement-tasks] + context: fresh + + - id: announce-validated + depends_on: [validate] + bash: 'echo "✅ Implementation passed local validation. Opening PR..."' + + # ═══════════════════════════════════════════════════════════════ + # PHASE D — PR + # ═══════════════════════════════════════════════════════════════ + + - id: finalize-pr + command: archon-finalize-pr + depends_on: [announce-validated] + context: fresh + + - id: announce-pr-open + depends_on: [finalize-pr] + bash: 'echo "🔍 PR opened. Running code review (round 1 of 2)..."' + + # ═══════════════════════════════════════════════════════════════ + # PHASE E — CODE REVIEW: ROUND 1 + # (five parallel agents → synthesize → conditional fix) + # ═══════════════════════════════════════════════════════════════ + + - id: review-scope-1 + command: archon-pr-review-scope + depends_on: [announce-pr-open] + context: fresh + + - id: sync-1 + command: archon-sync-pr-with-main + depends_on: [review-scope-1] + context: fresh + + - id: code-review-1 + command: archon-code-review-agent + depends_on: [sync-1] + context: fresh + + - id: error-handling-1 + command: archon-error-handling-agent + depends_on: [sync-1] + context: fresh + + - id: test-coverage-1 + command: archon-test-coverage-agent + depends_on: [sync-1] + context: fresh + + - id: comment-quality-1 + command: archon-comment-quality-agent + depends_on: [sync-1] + context: fresh + + - id: docs-impact-1 + command: archon-docs-impact-agent + depends_on: [sync-1] + context: fresh + + - id: synthesize-1 + command: archon-synthesize-review + depends_on: + - code-review-1 + - error-handling-1 + - test-coverage-1 + - comment-quality-1 + - docs-impact-1 + trigger_rule: none_failed_min_one_success + context: fresh + output_format: + type: object + properties: + blocking_findings_count: + type: number + summary: + type: string + required: [blocking_findings_count] + + - id: announce-round-1-result + depends_on: [synthesize-1] + bash: | + count="$synthesize-1.output.blocking_findings_count" + if [ "$count" = "0" ]; then + echo "✅ Review round 1 clean. Waiting on CI..." + else + echo "🔧 Review round 1 found $count blocking issue(s). Applying fixes..." + fi + + - id: implement-fixes-1 + command: archon-implement-review-fixes + depends_on: [announce-round-1-result] + context: fresh + when: '$synthesize-1.output.blocking_findings_count > 0' + + # ═══════════════════════════════════════════════════════════════ + # PHASE E — CODE REVIEW: ROUND 2 (only if round 1 had findings) + # ═══════════════════════════════════════════════════════════════ + + - id: announce-round-2-start + depends_on: [implement-fixes-1] + bash: 'echo "🔍 Re-reviewing after fixes..."' + when: '$synthesize-1.output.blocking_findings_count > 0' + + - id: review-scope-2 + command: archon-pr-review-scope + depends_on: [announce-round-2-start] + context: fresh + when: '$synthesize-1.output.blocking_findings_count > 0' + + - id: code-review-2 + command: archon-code-review-agent + depends_on: [review-scope-2] + context: fresh + + - id: error-handling-2 + command: archon-error-handling-agent + depends_on: [review-scope-2] + context: fresh + + - id: test-coverage-2 + command: archon-test-coverage-agent + depends_on: [review-scope-2] + context: fresh + + - id: comment-quality-2 + command: archon-comment-quality-agent + depends_on: [review-scope-2] + context: fresh + + - id: docs-impact-2 + command: archon-docs-impact-agent + depends_on: [review-scope-2] + context: fresh + + - id: synthesize-2 + command: archon-synthesize-review + depends_on: + - code-review-2 + - error-handling-2 + - test-coverage-2 + - comment-quality-2 + - docs-impact-2 + trigger_rule: none_failed_min_one_success + context: fresh + output_format: + type: object + properties: + blocking_findings_count: + type: number + summary: + type: string + required: [blocking_findings_count] + + - id: review-gate + depends_on: [synthesize-1, synthesize-2] + trigger_rule: none_failed_min_one_success + bash: | + r1="$synthesize-1.output.blocking_findings_count" + r2="$synthesize-2.output.blocking_findings_count" + if [ "$r1" = "0" ]; then + echo "✅ Review clean (round 1). Waiting on CI..." + exit 0 + fi + if [ -n "$r2" ] && [ "$r2" = "0" ]; then + echo "✅ Review clean (round 2). Waiting on CI..." + exit 0 + fi + echo "⛔ Code review did not converge after 2 rounds." + echo "Round 1 summary: $synthesize-1.output.summary" + echo "Round 2 summary: $synthesize-2.output.summary" + echo "PR is open; stopping before CI and review-app deploy." + exit 1 + + # ═══════════════════════════════════════════════════════════════ + # PHASE F — WAIT FOR CI + # ═══════════════════════════════════════════════════════════════ + + - id: extract-pr-number + depends_on: [review-gate] + bash: | + set -e + number=$(gh pr view --json number --jq '.number') + if [ -z "$number" ] || [ "$number" = "null" ]; then + echo "ERROR: could not resolve PR number for current branch" >&2 + exit 1 + fi + printf '%s\n' "$number" + + - id: ci-wait + depends_on: [extract-pr-number] + timeout: 3900000 + bash: | + set -e + bun .archon/scripts/ci-wait.js "$extract-pr-number.output" 3600000 + + - id: announce-ci-pass + depends_on: [ci-wait] + bash: 'echo "🚀 CI green. Deploying review app..."' + + # ═══════════════════════════════════════════════════════════════ + # PHASE G — DISPATCH REVIEW-APP DEPLOY + # ═══════════════════════════════════════════════════════════════ + + - id: deploy-review-app + depends_on: [announce-ci-pass] + bash: | + set -e + branch=$(gh pr view --json headRefName --jq '.headRefName') + bun .archon/scripts/dispatch-review-app.js deploy-to-review-app.yml "$branch" + + # ═══════════════════════════════════════════════════════════════ + # PHASE H — FETCH REVIEW-APP URL FROM PR COMMENTS + # ═══════════════════════════════════════════════════════════════ + + - id: fetch-review-url + depends_on: [deploy-review-app] + timeout: 1000000 + bash: | + set -e + bun .archon/scripts/fetch-review-app-url.js \ + "$extract-pr-number.output" \ + 'https://[^[:space:])]+\.review\.instrumentl\.com[^[:space:])]*' \ + 900000 20000 + + # ═══════════════════════════════════════════════════════════════ + # PHASE I — FINAL POST + # ═══════════════════════════════════════════════════════════════ + + - id: announce-done + depends_on: [fetch-review-url] + model: haiku + prompt: | + Output ONLY the final status message below, with placeholders filled. + No preamble, no code fences, no commentary. + + First, resolve the PR URL: + `gh pr view --json url --jq .url` + + The review-app URL is: $fetch-review-url.output + + Message format: + + 🎉 Done! + • PR: + • Review app: $fetch-review-url.output + + Open the review app to try it out; review the PR when you're ready to merge. diff --git a/.claude/skills/archon/guides/setup.md b/.claude/skills/archon/guides/setup.md index c12ba1649d..30c651d70c 100644 --- a/.claude/skills/archon/guides/setup.md +++ b/.claude/skills/archon/guides/setup.md @@ -119,8 +119,6 @@ If Bun was just installed in Prerequisites (macOS/Linux), use `~/.bun/bin/bun` i 3. Verify: `archon version` 4. Check Claude is installed: `which claude`, then `claude /login` if needed -> **Note — Claude Code binary path.** Archon does not bundle Claude Code. In compiled Archon binaries (quick install, Homebrew), the Claude Code SDK needs `CLAUDE_BIN_PATH` set to the absolute path of its `cli.js`. The `archon setup` wizard in Step 4 auto-detects this via `npm root -g` and writes it to `~/.archon/.env` — no manual action needed in the typical case. Source installs (`bun run`) don't need this; the SDK finds `cli.js` via `node_modules` automatically. - ## Step 4: Configure Credentials The CLI loads infrastructure config (database, tokens) from `~/.archon/.env` only. This prevents conflicts with project `.env` files that may contain different database URLs. @@ -160,7 +158,7 @@ Both paths are normal — the manual path is not an error. Wait for the user to confirm they've completed the setup wizard before proceeding. -### 4c: Verify Configuration +### 5c: Verify Configuration After the user confirms setup is complete: @@ -172,7 +170,7 @@ Should show: - `Database: sqlite` (default, zero setup) or `Database: postgresql` (if DATABASE_URL was configured) - No errors about missing configuration -### 4d: Run Database Migrations (PostgreSQL only) +### 5d: Run Database Migrations (PostgreSQL only) **SQLite users: skip this step.** SQLite is auto-initialized on first run with zero setup. diff --git a/.claude/skills/archon/guides/slack.md b/.claude/skills/archon/guides/slack.md index 42011ee980..14a3293434 100644 --- a/.claude/skills/archon/guides/slack.md +++ b/.claude/skills/archon/guides/slack.md @@ -10,7 +10,7 @@ Follow the step-by-step instructions in **[docs/slack-setup.md](../../../../../d 1. Create a Slack app at [api.slack.com/apps](https://api.slack.com/apps) (from scratch) 2. Enable **Socket Mode** — generates an App-Level Token (`xapp-...`) for `SLACK_APP_TOKEN` -3. Add **Bot Token Scopes**: `app_mentions:read`, `chat:write`, `channels:history`, `channels:join`, `im:history`, `im:write`, `im:read` +3. Add **Bot Token Scopes**: `app_mentions:read`, `chat:write`, `reactions:write`, `channels:history`, `channels:join`, `im:history`, `im:write`, `im:read` 4. Subscribe to **Bot Events**: `app_mention`, `message.im` 5. **Install to Workspace** — generates a Bot User OAuth Token (`xoxb-...`) for `SLACK_BOT_TOKEN` 6. Invite the bot to your channel: `/invite @YourBotName` diff --git a/.cursor/hooks/state/continual-learning-index.json b/.cursor/hooks/state/continual-learning-index.json new file mode 100644 index 0000000000..9d9eb2e7f6 --- /dev/null +++ b/.cursor/hooks/state/continual-learning-index.json @@ -0,0 +1,11 @@ +{ + "280d685f-a48a-46c6-833f-323dd405d054/280d685f-a48a-46c6-833f-323dd405d054.jsonl": { + "mtime": 1776270712 + }, + "d2ae91bc-63ac-4028-88d4-97ce68f614a8/d2ae91bc-63ac-4028-88d4-97ce68f614a8.jsonl": { + "mtime": 1776268070 + }, + "caf60f72-6726-464d-a2cf-d3e482e5d1a9/caf60f72-6726-464d-a2cf-d3e482e5d1a9.jsonl": { + "mtime": 1776259093 + } +} diff --git a/.cursor/hooks/state/continual-learning.json b/.cursor/hooks/state/continual-learning.json new file mode 100644 index 0000000000..5a75f06918 --- /dev/null +++ b/.cursor/hooks/state/continual-learning.json @@ -0,0 +1,8 @@ +{ + "version": 1, + "lastRunAtMs": 1776270702201, + "turnsSinceLastRun": 0, + "lastTranscriptMtimeMs": 1776270702140.7751, + "lastProcessedGenerationId": "10c60213-c8e3-462b-a77e-202f0bb589a2", + "trialStartedAtMs": null +} diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 0000000000..641de8700b --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "scout-apm": { + "command": "docker", + "args": ["run", "--rm", "-i", "--env", "SCOUT_API_KEY", "scoutapp/scout-mcp-local:latest"], + "env": { + "SCOUT_API_KEY": "${env:SCOUT_API_KEY}" + }, + "envFile": "${workspaceFolder}/.env" + } + } +} diff --git a/.gitignore b/.gitignore index a2f33c5d5c..4427ec5979 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ coverage/ npm-debug.log* yarn-debug.log* yarn-error.log* +package-lock.json # ESLint cache .eslintcache diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..4277831adb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,6 @@ +## Learned User Preferences + +## Learned Workspace Facts + +- Run `bun install` from the repo root before `bun run dev` or per-package dev scripts; without a linked root `node_modules`, workspace packages are not resolved and common failures include missing `vite`/`astro` and failed `@archon/paths` subpath imports (for example `@archon/paths/strip-cwd-env-boot` from `@archon/server`). +- In the Web UI, Settings → Platform Connections hardcodes Slack, Telegram, Discord, and GitHub as not connected; only Web reflects live adapter state, so a working Slack bot can still show as not configured there until the UI is wired to real platform status. diff --git a/bun.lock b/bun.lock index cf5b5efd7d..0d6b1c9db0 100644 --- a/bun.lock +++ b/bun.lock @@ -225,7 +225,14 @@ }, }, "overrides": { + "axios": "^1.15.0", + "flatted": "^3.4.2", + "follow-redirects": "^1.16.0", + "lodash": "^4.18.0", + "path-to-regexp": "^8.4.0", + "picomatch": "^4.0.4", "test-exclude": "^7.0.1", + "undici": "^6.24.0", }, "packages": { "@antfu/ni": ["@antfu/ni@25.0.0", "", { "dependencies": { "ansis": "^4.0.0", "fzf": "^0.5.2", "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" }, "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs", "nup": "bin/nup.mjs" } }, "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA=="], @@ -1040,7 +1047,7 @@ "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], - "axios": ["axios@1.13.6", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ=="], + "axios": ["axios@1.15.1", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-WOG+Jj8ZOvR0a3rAn+Tuf1UQJRxw5venr6DgdbJzngJE3qG7X0kL83CZGpdHMxEm+ZK3seAbvFsw4FfOfP9vxg=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -1388,11 +1395,11 @@ "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - "flatted": ["flatted@3.4.1", "", {}, "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ=="], + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], "flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="], - "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], "fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="], @@ -1674,7 +1681,7 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], @@ -1958,7 +1965,7 @@ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], + "path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], "pg": ["pg@8.20.0", "", { "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA=="], @@ -1980,7 +1987,7 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], @@ -2028,7 +2035,7 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], @@ -2320,7 +2327,7 @@ "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], - "undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="], + "undici": ["undici@6.25.0", "", {}, "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], @@ -2548,8 +2555,6 @@ "ajv-formats/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], - "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "astro/sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "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" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], "astro/unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], @@ -2608,10 +2613,6 @@ "micromark-extension-directive/parse-entities": ["parse-entities@4.0.2", "", { "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" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "msw/path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], - "msw/type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], diff --git a/docs/plans/2026-04-17-slack-archie-feature-to-review-app-plan.md b/docs/plans/2026-04-17-slack-archie-feature-to-review-app-plan.md new file mode 100644 index 0000000000..fd89b6e4dc --- /dev/null +++ b/docs/plans/2026-04-17-slack-archie-feature-to-review-app-plan.md @@ -0,0 +1,973 @@ +# @archie Slack feature-to-review-app Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Ship one bundled Archon workflow (`archon-slack-feature-to-review-app`) that takes a natural-language feature request in Slack and drives it end-to-end to a PR + deployed review app, with progress posted to the thread. + +**Architecture:** One new YAML workflow composing existing commands (spec questions from `archon-interactive-prd`, plan/implement/PR/review agents from `archon-idea-to-pr`) plus three small `.archon/scripts/` helpers (dispatch a GH Actions workflow, wait for CI, poll PR comments for the review-app URL). No adapter or orchestrator changes. Registered as a bundled default so it's available in binary builds. + +**Tech Stack:** Bun + TypeScript, Archon workflow engine (DAG + loop nodes), `gh` CLI for GitHub interactions, existing Slack adapter (no changes). + +**Related design doc:** `docs/specs/2026-04-17-slack-archie-feature-to-review-app-design.md`. + +--- + +## File Structure + +New files: +- `.archon/scripts/dispatch-review-app.ts` — shell-safe wrapper around `gh workflow run`. +- `.archon/scripts/ci-wait.ts` — polls `gh pr checks --watch` with a hard timeout; exits 0 on green, non-zero on red/timeout. +- `.archon/scripts/fetch-review-app-url.ts` — polls `gh pr view --json comments` every 20s up to 15 min, regex-extracts the first URL match. +- `.archon/workflows/defaults/archon-slack-feature-to-review-app.yaml` — the workflow. + +Modified files: +- `packages/workflows/src/defaults/bundled-defaults.ts` — register the new workflow YAML. +- `packages/workflows/src/defaults/bundled-defaults.test.ts` — extend existing parse assertion to cover it (only if a count assertion exists). + +No changes to: Slack adapter, orchestrator, DB schema, Zod config schemas. + +--- + +### Task 1: Script — dispatch-review-app.ts + +**Files:** +- Create: `.archon/scripts/dispatch-review-app.ts` + +Small shell wrapper. The workflow passes two CLI args: `` (e.g. `deploy-to-review-app.yml`) and `` (the PR branch). Exits 0 on dispatch success, non-zero with a clear message otherwise. + +- [ ] **Step 1: Write the script** + +Create `.archon/scripts/dispatch-review-app.ts` with the following contents: + +```typescript +#!/usr/bin/env bun +/** + * Dispatch a GitHub Actions workflow_dispatch event on the given ref. + * + * Usage: bun .archon/scripts/dispatch-review-app.ts + * + * Exits 0 on successful dispatch. Exits non-zero with a human-readable stderr + * message on any failure (missing args, gh not installed, gh call failed). + * + * Used by the archon-slack-feature-to-review-app workflow after CI passes + * to deploy a review app for the PR branch. + */ +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); + +async function main(): Promise { + const [workflowFile, ref] = process.argv.slice(2); + + if (!workflowFile || !ref) { + console.error('Usage: dispatch-review-app.ts '); + process.exit(2); + } + + try { + const { stdout, stderr } = await execFileAsync('gh', [ + 'workflow', + 'run', + workflowFile, + '--ref', + ref, + ]); + if (stdout.trim()) console.log(stdout.trim()); + if (stderr.trim()) console.log(stderr.trim()); + console.log( + JSON.stringify({ dispatched: true, workflow: workflowFile, ref }) + ); + } catch (err) { + const e = err as Error & { stderr?: string }; + console.error( + `Failed to dispatch ${workflowFile} on ref ${ref}: ${e.stderr ?? e.message}` + ); + process.exit(1); + } +} + +void main(); +``` + +- [ ] **Step 2: Verify it runs with a missing-arg check** + +Run: `bun .archon/scripts/dispatch-review-app.ts; echo "exit=$?"` + +Expected: usage line on stderr, line `exit=2` on stdout. + +- [ ] **Step 3: Commit** + +```bash +git add .archon/scripts/dispatch-review-app.ts +git commit -m "feat(scripts): dispatch-review-app helper for slack feature workflow + +Wraps gh workflow run for review-app deployment; exits non-zero with a +clear message on dispatch failure. Used by archon-slack-feature-to-review-app." +``` + +--- + +### Task 2: Script — ci-wait.ts + +**Files:** +- Create: `.archon/scripts/ci-wait.ts` + +Polls `gh pr checks --watch --fail-fast` with an outer wall-clock timeout. `gh pr checks --watch` already exits 0 on all-green and 1 on any failure; we add a parent process timeout so we never hang. + +- [ ] **Step 1: Write the script** + +Create `.archon/scripts/ci-wait.ts` with the following contents: + +```typescript +#!/usr/bin/env bun +/** + * Wait for GitHub CI on a PR to finish, with a hard wall-clock timeout. + * + * Usage: bun .archon/scripts/ci-wait.ts [timeout-ms] + * + * Exit codes: + * 0 — all required checks passed + * 1 — at least one required check failed + * 3 — timeout reached before CI finished + * 2 — bad args / missing gh + * + * Used by archon-slack-feature-to-review-app to gate review-app deploy. + */ +import { spawn } from 'node:child_process'; + +const DEFAULT_TIMEOUT_MS = 60 * 60 * 1000; // 60 minutes + +function main(): void { + const [pr, timeoutArg] = process.argv.slice(2); + + if (!pr) { + console.error('Usage: ci-wait.ts [timeout-ms]'); + process.exit(2); + } + + const timeoutMs = timeoutArg ? Number(timeoutArg) : DEFAULT_TIMEOUT_MS; + if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { + console.error(`Invalid timeout-ms: ${timeoutArg}`); + process.exit(2); + } + + console.log( + `Waiting for CI on PR ${pr} (timeout: ${Math.round(timeoutMs / 1000)}s)...` + ); + + const child = spawn( + 'gh', + ['pr', 'checks', pr, '--watch', '--fail-fast', '--interval', '30'], + { stdio: 'inherit' } + ); + + let timedOut = false; + const timer = setTimeout(() => { + timedOut = true; + console.error(`\nCI wait timed out after ${Math.round(timeoutMs / 1000)}s`); + child.kill('SIGTERM'); + setTimeout(() => process.exit(3), 2000).unref(); + }, timeoutMs); + timer.unref(); + + child.on('exit', (code, _signal) => { + clearTimeout(timer); + if (timedOut) return; + if (code === 0) { + console.log('CI passed.'); + process.exit(0); + } + console.error(`CI failed (gh exit code ${code ?? 'null'})`); + process.exit(1); + }); + + child.on('error', err => { + clearTimeout(timer); + console.error(`Failed to spawn gh: ${err.message}`); + process.exit(2); + }); +} + +main(); +``` + +- [ ] **Step 2: Verify arg validation** + +Run: `bun .archon/scripts/ci-wait.ts; echo "exit=$?"` + +Expected: usage line on stderr, `exit=2`. + +Run: `bun .archon/scripts/ci-wait.ts 99999 abc; echo "exit=$?"` + +Expected: `Invalid timeout-ms: abc` on stderr, `exit=2`. + +- [ ] **Step 3: Commit** + +```bash +git add .archon/scripts/ci-wait.ts +git commit -m "feat(scripts): ci-wait helper with hard timeout + +Wraps gh pr checks --watch --fail-fast with a wall-clock timeout so the +workflow can't hang indefinitely. Exit codes distinguish pass/fail/timeout." +``` + +--- + +### Task 3: Script — fetch-review-app-url.ts + +**Files:** +- Create: `.archon/scripts/fetch-review-app-url.ts` + +Polls the PR's comments via `gh pr view --json comments` every 20 seconds for up to 15 minutes, looking for a URL matching a caller-supplied regex. Prints the URL on stdout and exits 0 on match; non-zero on timeout. Log lines go to stderr so `$nodeId.output` captures only the URL. + +- [ ] **Step 1: Write the script** + +Create `.archon/scripts/fetch-review-app-url.ts` with the following contents: + +```typescript +#!/usr/bin/env bun +/** + * Poll a GitHub PR's comments for a review-app URL matching a regex. + * + * Usage: + * bun .archon/scripts/fetch-review-app-url.ts [timeout-ms] [interval-ms] + * + * Exit codes: + * 0 — URL found; printed to stdout as the only stdout line + * 3 — timeout reached without a match + * 2 — bad args / gh failure / invalid regex / bad comments JSON + * + * The workflow consumes the trimmed stdout via $.output. + * All log lines go to stderr so the URL is the only stdout content. + */ +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); + +const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000; +const DEFAULT_INTERVAL_MS = 20 * 1000; + +interface CommentShape { + body?: string; +} + +async function pollOnce( + pr: string, + regex: RegExp +): Promise { + const { stdout } = await execFileAsync('gh', [ + 'pr', + 'view', + pr, + '--json', + 'comments', + ]); + let parsed: { comments?: CommentShape[] }; + try { + parsed = JSON.parse(stdout); + } catch { + throw new Error(`gh returned non-JSON stdout: ${stdout.slice(0, 200)}`); + } + const comments = parsed.comments ?? []; + for (const c of comments) { + const match = typeof c.body === 'string' ? c.body.match(regex) : null; + if (match) return match[0]; + } + return null; +} + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function main(): Promise { + const [pr, regexStr, timeoutArg, intervalArg] = process.argv.slice(2); + + if (!pr || !regexStr) { + console.error( + 'Usage: fetch-review-app-url.ts [timeout-ms] [interval-ms]' + ); + process.exit(2); + } + + let regex: RegExp; + try { + regex = new RegExp(regexStr); + } catch (err) { + console.error( + `Invalid regex ${JSON.stringify(regexStr)}: ${(err as Error).message}` + ); + process.exit(2); + } + + const timeoutMs = timeoutArg ? Number(timeoutArg) : DEFAULT_TIMEOUT_MS; + const intervalMs = intervalArg ? Number(intervalArg) : DEFAULT_INTERVAL_MS; + if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { + console.error(`Invalid timeout-ms: ${timeoutArg}`); + process.exit(2); + } + if (!Number.isFinite(intervalMs) || intervalMs <= 0) { + console.error(`Invalid interval-ms: ${intervalArg}`); + process.exit(2); + } + + const deadline = Date.now() + timeoutMs; + console.error( + `Polling PR ${pr} for pattern ${regex} every ${Math.round(intervalMs / 1000)}s, up to ${Math.round(timeoutMs / 1000)}s total...` + ); + + while (Date.now() < deadline) { + try { + const match = await pollOnce(pr, regex); + if (match) { + console.log(match); + return; + } + } catch (err) { + console.error(`Poll error (will retry): ${(err as Error).message}`); + } + const remaining = deadline - Date.now(); + if (remaining <= 0) break; + await sleep(Math.min(intervalMs, remaining)); + } + + console.error( + `No matching comment found on PR ${pr} within ${Math.round(timeoutMs / 1000)}s.` + ); + process.exit(3); +} + +void main(); +``` + +- [ ] **Step 2: Verify arg validation** + +Run: `bun .archon/scripts/fetch-review-app-url.ts; echo "exit=$?"` + +Expected: usage line on stderr, `exit=2`. + +Run: `bun .archon/scripts/fetch-review-app-url.ts 1 '[' 5000; echo "exit=$?"` + +Expected: `Invalid regex "["...` on stderr, `exit=2`. + +- [ ] **Step 3: Commit** + +```bash +git add .archon/scripts/fetch-review-app-url.ts +git commit -m "feat(scripts): fetch-review-app-url helper + +Polls gh pr view --json comments for a URL matching a caller-supplied +regex; prints the URL on stdout, errors on stderr so the workflow engine +captures only the URL via \$nodeId.output." +``` + +--- + +### Task 4: Workflow YAML — archon-slack-feature-to-review-app + +**Files:** +- Create: `.archon/workflows/defaults/archon-slack-feature-to-review-app.yaml` + +The main artifact. Implementation decisions: +- Spec revision is a `loop:` node with `interactive: true` matching `archon-piv-loop`'s `refine-plan` pattern. Signal: `SPEC_APPROVED`, `max_iterations: 3`. +- Code review "2 rounds max" is **explicitly unrolled** (not a `loop:` node) because loops are single-prompt-bodied and our review needs a 5-parallel-agents sub-graph. Round 2 uses `when:` to skip itself when round 1 was clean. +- Scripts are invoked via `bash:` wrappers (not `script:` nodes) because `script:` nodes do not accept CLI args. +- Instrumentl-specific review-app parameters (`deploy-to-review-app.yml`, `*.review.instrumentl.com` regex) are hardcoded as literal strings. Per-project overrides are future work. + +- [ ] **Step 1: Write the workflow YAML** + +Create `.archon/workflows/defaults/archon-slack-feature-to-review-app.yaml` with: + +```yaml +name: archon-slack-feature-to-review-app +description: | + Use when: User on Slack/chat asks @archie to build, add, or implement a + feature end-to-end and wants a working review app at the end. Matches + phrases like "build X", "add feature Y", "implement Z", "ship a feature + that...". + Input: Feature description in natural language. + Output: PR ready for review + review-app URL posted back to the thread. + NOT for: Spec/PRD only (use archon-interactive-prd), code-only changes + without a spec (use archon-idea-to-pr), or bug fixes + (use archon-fix-github-issue). + +provider: claude +interactive: true + +nodes: + # ═══════════════════════════════════════════════════════════════ + # PHASE A — SPEC CREATION (bounded 3-iteration revision loop) + # ═══════════════════════════════════════════════════════════════ + + - id: spec + model: sonnet + loop: + prompt: | + # Feature request → spec + + You are turning a Slack-submitted feature request into a focused + implementation spec, through iterative dialogue. + + **Original request**: $ARGUMENTS + **User's latest reply**: $LOOP_USER_INPUT + + --- + + ## If this is the first iteration ($LOOP_USER_INPUT is empty): + + 1. Restate your understanding of the request in 1-2 sentences. + 2. Explore the codebase briefly (CLAUDE.md, directory structure, + files obviously related to the feature). + 3. Ask a tight set of 3-5 clarifying questions focused on DECISIONS + (scope boundaries, which existing code to extend, test + expectations, explicit out-of-scope items). + 4. End with: "Answer the questions and I'll draft a spec." + 5. Do NOT emit the approval signal yet. + + ## If the user has replied: + + 1. Process their answers. + 2. If you now have enough to draft a spec, write it to + `.claude/archon/specs/.spec.md` with these sections: + - Problem + - Proposed change (which files, functions, interfaces) + - Out of scope + - Acceptance criteria (specific, testable bullets) + - Testing plan + 3. Present a condensed summary of the spec in-thread (not the full + file), end with: "Reply `approved` to implement, or tell me what + to change." + 4. If the user's latest reply EXPLICITLY approves (contains + "approved", "looks good", "ship it", "go"), emit + SPEC_APPROVED and stop. Otherwise, revise the + spec file based on their feedback and re-summarize. + + **CRITICAL**: Never emit SPEC_APPROVED unless the + user's LATEST message explicitly approves. Questions, feedback, and + change requests are NOT approval. + until: SPEC_APPROVED + max_iterations: 3 + interactive: true + gate_message: | + Answer the questions above, or reply "approved" once the spec looks right. + + - id: announce-spec-approved + depends_on: [spec] + bash: 'echo "🧠 Spec approved. Creating implementation plan..."' + + # ═══════════════════════════════════════════════════════════════ + # PHASE B — PLAN + # ═══════════════════════════════════════════════════════════════ + + - id: create-plan + command: archon-create-plan + depends_on: [announce-spec-approved] + context: fresh + + - id: plan-setup + command: archon-plan-setup + depends_on: [create-plan] + context: fresh + + - id: announce-plan-ready + depends_on: [plan-setup] + bash: 'echo "🏗️ Plan ready. Implementing in a fresh worktree..."' + + # ═══════════════════════════════════════════════════════════════ + # PHASE C — IMPLEMENT + VALIDATE + # ═══════════════════════════════════════════════════════════════ + + - id: implement-tasks + command: archon-implement-tasks + depends_on: [announce-plan-ready] + context: fresh + model: claude-opus-4-6[1m] + + - id: validate + command: archon-validate + depends_on: [implement-tasks] + context: fresh + + - id: announce-validated + depends_on: [validate] + bash: 'echo "✅ Implementation passed local validation. Opening PR..."' + + # ═══════════════════════════════════════════════════════════════ + # PHASE D — PR + # ═══════════════════════════════════════════════════════════════ + + - id: finalize-pr + command: archon-finalize-pr + depends_on: [announce-validated] + context: fresh + + - id: announce-pr-open + depends_on: [finalize-pr] + bash: 'echo "🔍 PR opened. Running code review (round 1 of 2)..."' + + # ═══════════════════════════════════════════════════════════════ + # PHASE E — CODE REVIEW: ROUND 1 + # (five parallel agents → synthesize → conditional fix) + # ═══════════════════════════════════════════════════════════════ + + - id: review-scope-1 + command: archon-pr-review-scope + depends_on: [announce-pr-open] + context: fresh + + - id: sync-1 + command: archon-sync-pr-with-main + depends_on: [review-scope-1] + context: fresh + + - id: code-review-1 + command: archon-code-review-agent + depends_on: [sync-1] + context: fresh + + - id: error-handling-1 + command: archon-error-handling-agent + depends_on: [sync-1] + context: fresh + + - id: test-coverage-1 + command: archon-test-coverage-agent + depends_on: [sync-1] + context: fresh + + - id: comment-quality-1 + command: archon-comment-quality-agent + depends_on: [sync-1] + context: fresh + + - id: docs-impact-1 + command: archon-docs-impact-agent + depends_on: [sync-1] + context: fresh + + - id: synthesize-1 + command: archon-synthesize-review + depends_on: + - code-review-1 + - error-handling-1 + - test-coverage-1 + - comment-quality-1 + - docs-impact-1 + trigger_rule: none_failed_min_one_success + context: fresh + output_format: + type: object + properties: + blocking_findings_count: + type: number + summary: + type: string + required: [blocking_findings_count] + + - id: announce-round-1-result + depends_on: [synthesize-1] + bash: | + count="$synthesize-1.output.blocking_findings_count" + if [ "$count" = "0" ]; then + echo "✅ Review round 1 clean. Waiting on CI..." + else + echo "🔧 Review round 1 found $count blocking issue(s). Applying fixes..." + fi + + - id: implement-fixes-1 + command: archon-implement-review-fixes + depends_on: [announce-round-1-result] + context: fresh + when: '$synthesize-1.output.blocking_findings_count > 0' + + # ═══════════════════════════════════════════════════════════════ + # PHASE E — CODE REVIEW: ROUND 2 (only if round 1 had findings) + # ═══════════════════════════════════════════════════════════════ + + - id: announce-round-2-start + depends_on: [implement-fixes-1] + bash: 'echo "🔍 Re-reviewing after fixes..."' + when: '$synthesize-1.output.blocking_findings_count > 0' + + - id: review-scope-2 + command: archon-pr-review-scope + depends_on: [announce-round-2-start] + context: fresh + when: '$synthesize-1.output.blocking_findings_count > 0' + + - id: code-review-2 + command: archon-code-review-agent + depends_on: [review-scope-2] + context: fresh + + - id: error-handling-2 + command: archon-error-handling-agent + depends_on: [review-scope-2] + context: fresh + + - id: test-coverage-2 + command: archon-test-coverage-agent + depends_on: [review-scope-2] + context: fresh + + - id: comment-quality-2 + command: archon-comment-quality-agent + depends_on: [review-scope-2] + context: fresh + + - id: docs-impact-2 + command: archon-docs-impact-agent + depends_on: [review-scope-2] + context: fresh + + - id: synthesize-2 + command: archon-synthesize-review + depends_on: + - code-review-2 + - error-handling-2 + - test-coverage-2 + - comment-quality-2 + - docs-impact-2 + trigger_rule: none_failed_min_one_success + context: fresh + output_format: + type: object + properties: + blocking_findings_count: + type: number + summary: + type: string + required: [blocking_findings_count] + + - id: review-gate + depends_on: [synthesize-1, synthesize-2] + trigger_rule: none_failed_min_one_success + bash: | + r1="$synthesize-1.output.blocking_findings_count" + r2="$synthesize-2.output.blocking_findings_count" + if [ "$r1" = "0" ]; then + echo "✅ Review clean (round 1). Waiting on CI..." + exit 0 + fi + if [ -n "$r2" ] && [ "$r2" = "0" ]; then + echo "✅ Review clean (round 2). Waiting on CI..." + exit 0 + fi + echo "⛔ Code review did not converge after 2 rounds." + echo "Round 1 summary: $synthesize-1.output.summary" + echo "Round 2 summary: $synthesize-2.output.summary" + echo "PR is open; stopping before CI and review-app deploy." + exit 1 + + # ═══════════════════════════════════════════════════════════════ + # PHASE F — WAIT FOR CI + # ═══════════════════════════════════════════════════════════════ + + - id: extract-pr-number + depends_on: [review-gate] + bash: | + set -e + number=$(gh pr view --json number --jq '.number') + if [ -z "$number" ] || [ "$number" = "null" ]; then + echo "ERROR: could not resolve PR number for current branch" >&2 + exit 1 + fi + printf '%s\n' "$number" + + - id: ci-wait + depends_on: [extract-pr-number] + timeout: 3900000 + bash: | + set -e + bun .archon/scripts/ci-wait.ts "$extract-pr-number.output" 3600000 + + - id: announce-ci-pass + depends_on: [ci-wait] + bash: 'echo "🚀 CI green. Deploying review app..."' + + # ═══════════════════════════════════════════════════════════════ + # PHASE G — DISPATCH REVIEW-APP DEPLOY + # ═══════════════════════════════════════════════════════════════ + + - id: deploy-review-app + depends_on: [announce-ci-pass] + bash: | + set -e + branch=$(gh pr view --json headRefName --jq '.headRefName') + bun .archon/scripts/dispatch-review-app.ts deploy-to-review-app.yml "$branch" + + # ═══════════════════════════════════════════════════════════════ + # PHASE H — FETCH REVIEW-APP URL FROM PR COMMENTS + # ═══════════════════════════════════════════════════════════════ + + - id: fetch-review-url + depends_on: [deploy-review-app] + timeout: 1000000 + bash: | + set -e + bun .archon/scripts/fetch-review-app-url.ts \ + "$extract-pr-number.output" \ + 'https://[^[:space:])]+\.review\.instrumentl\.com[^[:space:])]*' \ + 900000 20000 + + # ═══════════════════════════════════════════════════════════════ + # PHASE I — FINAL POST + # ═══════════════════════════════════════════════════════════════ + + - id: announce-done + depends_on: [fetch-review-url] + model: haiku + prompt: | + Output ONLY the final status message below, with placeholders filled. + No preamble, no code fences, no commentary. + + First, resolve the PR URL: + `gh pr view --json url --jq .url` + + The review-app URL is: $fetch-review-url.output + + Message format: + + 🎉 Done! + • PR: + • Review app: $fetch-review-url.output + + Open the review app to try it out; review the PR when you're ready to merge. +``` + +- [ ] **Step 2: Validate the workflow parses** + +Run: + +```bash +bun run cli validate workflows archon-slack-feature-to-review-app +``` + +Expected: no errors. All referenced commands (`archon-create-plan`, `archon-plan-setup`, `archon-implement-tasks`, `archon-validate`, `archon-finalize-pr`, `archon-pr-review-scope`, `archon-sync-pr-with-main`, `archon-code-review-agent`, `archon-error-handling-agent`, `archon-test-coverage-agent`, `archon-comment-quality-agent`, `archon-docs-impact-agent`, `archon-synthesize-review`, `archon-implement-review-fixes`) must exist. All referenced scripts (`ci-wait`, `dispatch-review-app`, `fetch-review-app-url`) must exist in `.archon/scripts/`. + +If the validator complains about a missing command, verify it exists under `.archon/commands/defaults/` or in the validator's discovery path. If missing, the design assumed it existed — reopen the design doc and adjust. + +If the validator complains about `when:` expression syntax or `output_format` keys, fix according to the error message. + +- [ ] **Step 3: Commit** + +```bash +git add .archon/workflows/defaults/archon-slack-feature-to-review-app.yaml +git commit -m "feat(workflows): archon-slack-feature-to-review-app + +End-to-end workflow for Slack @archie feature requests: interactive spec +creation (bounded 3-iteration revision loop), plan + implement + PR using +existing commands, two-round code review with conditional second pass, CI +wait, review-app dispatch, URL fetch from PR comments, and final post back +to the Slack thread. Composes existing commands; adds no new adapter or +orchestrator code." +``` + +--- + +### Task 5: Register the workflow in bundled defaults + +**Files:** +- Modify: `packages/workflows/src/defaults/bundled-defaults.ts` +- Modify: `packages/workflows/src/defaults/bundled-defaults.test.ts` (only if a count assertion exists) + +Binary builds read bundled workflows from a compile-time text import map. Add the new workflow to both the import list and the exported map. + +- [ ] **Step 1: Add the import** + +Open `packages/workflows/src/defaults/bundled-defaults.ts`. In the workflow imports section, after the line importing `archonWorkflowBuilderWf`, add: + +```typescript +import archonSlackFeatureToReviewAppWf from '../../../../.archon/workflows/defaults/archon-slack-feature-to-review-app.yaml' with { type: 'text' }; +``` + +- [ ] **Step 2: Add the map entry** + +In the same file, in the `BUNDLED_WORKFLOWS` map, after the line `'archon-workflow-builder': archonWorkflowBuilderWf,` add: + +```typescript + 'archon-slack-feature-to-review-app': archonSlackFeatureToReviewAppWf, +``` + +- [ ] **Step 3: Inspect the existing test for a count assertion** + +Run: + +```bash +grep -n "13\|BUNDLED_WORKFLOWS\|toHaveLength\|Object.keys" packages/workflows/src/defaults/bundled-defaults.test.ts +``` + +If you see an assertion like `expect(Object.keys(BUNDLED_WORKFLOWS)).toHaveLength(13)`, update the `13` to `14`. + +If there is no count assertion (the test just iterates and parses), no test change is needed — the new entry is covered automatically. + +- [ ] **Step 4: Run the bundled-defaults test** + +Run: + +```bash +cd packages/workflows && bun test src/defaults/bundled-defaults.test.ts +``` + +Expected: all tests pass, including the parse check on the new workflow. + +If parsing fails because the workflow references a command not present in `BUNDLED_COMMANDS`, that means binary builds need the command too. Add the missing command's import and map entry to `BUNDLED_COMMANDS` following the same pattern as the other 21 commands in that file. + +- [ ] **Step 5: Type-check** + +From repo root: + +```bash +bun run type-check +``` + +Expected: no new type errors. The `with { type: 'text' }` import syntax is already used 34 times in this file. + +- [ ] **Step 6: Commit** + +```bash +git add packages/workflows/src/defaults/bundled-defaults.ts \ + packages/workflows/src/defaults/bundled-defaults.test.ts +git commit -m "feat(workflows): register archon-slack-feature-to-review-app in bundled defaults + +Make the new end-to-end Slack workflow available in binary builds alongside +the existing bundled workflows." +``` + +--- + +### Task 6: Pre-PR validation + +No new code; run the project's standard validation gate. + +- [ ] **Step 1: Run full validation** + +From repo root: + +```bash +bun run validate +``` + +Expected: type-check, lint, format, and tests all pass. + +- [ ] **Step 2: Fix any flagged issues inline** + +If lint flags anything in the new script files, fix inline. Do not silence warnings with `eslint-disable` — the repo enforces zero warnings per `CLAUDE.md`. + +- [ ] **Step 3: Commit fixups (only if needed)** + +```bash +git status --short +``` + +If anything changed in step 2: + +```bash +git add -A +git commit -m "chore: fix lint/format for new slack feature workflow" +``` + +Otherwise skip. + +--- + +### Task 7: Manual smoke test (one-time, after merge) + +Verification checklist to run ONCE against a real Slack workspace after merge. Not part of CI. Document outcomes in the PR description. + +- [ ] **Step 1: Confirm environment** + +Check `.env` contains: +- `SLACK_BOT_TOKEN` (xoxb-*) +- `SLACK_APP_TOKEN` (xapp-*) +- `SLACK_ALLOWED_USER_IDS` including your Slack user ID +- `ANTHROPIC_API_KEY` +- `GITHUB_TOKEN` with workflow dispatch permissions on the target repo + +Confirm a codebase pointing at the target repo is registered in Archon. + +Start the server: + +```bash +bun run dev +``` + +- [ ] **Step 2: Trigger with a trivial request** + +In the connected Slack channel, post: + +``` +@archie add a README badge linking to the docs site +``` + +- [ ] **Step 3: Verify spec phase** + +Expected in-thread: +1. Bot restates the request and asks 3-5 targeted questions. +2. Reply with answers. +3. Bot drafts a spec summary and asks for approval. +4. Reply `approved`. +5. Announce: `🧠 Spec approved. Creating implementation plan...` appears. + +- [ ] **Step 4: Verify implement + PR** + +Expected announces, in order: +1. `🏗️ Plan ready. Implementing in a fresh worktree...` +2. `✅ Implementation passed local validation. Opening PR...` +3. `🔍 PR opened. Running code review (round 1 of 2)...` + +Confirm a real PR exists in the target repo with the generated branch. + +- [ ] **Step 5: Verify review loop** + +Expected either: +- `✅ Review round 1 clean. Waiting on CI...` (clean path) + +or: +- `🔧 Review round 1 found N blocking issue(s). Applying fixes...` +- `🔍 Re-reviewing after fixes...` +- Then one of: + - `✅ Review clean (round 2). Waiting on CI...` + - `⛔ Code review did not converge after 2 rounds.` (terminal) + +- [ ] **Step 6: Verify CI + deploy** + +Expected: +- `🚀 CI green. Deploying review app...` +- `deploy-to-review-app.yml` workflow run appears in GitHub Actions for the PR branch. + +- [ ] **Step 7: Verify URL fetch + final post** + +Expected within 15 minutes: +- Final message: `🎉 Done! • PR: • Review app: ` +- Clicking the review-app URL loads the deployed app. + +- [ ] **Step 8: Record results in the PR description** + +Add a `## Smoke test` section with pass/fail per step, links to the Slack thread, and any follow-ups discovered. + +--- + +## Self-Review Notes + +**Spec coverage check** — each design doc section maps to a task: +- Trigger + routing: no work needed (existing Slack adapter + router). +- Configuration: deferred; values hardcoded in YAML for v1 (documented divergence below). +- Workflow node graph phases A–I: Task 4. +- Progress announcements: inline in Task 4 (bash echo nodes). +- Authorization: no work needed (existing `SLACK_ALLOWED_USER_IDS`). +- Failure modes: script exit codes in Tasks 1–3; `review-gate` bash node in Task 4 handles the 2-round cap. +- Testing: Task 5 (bundled-defaults parse test) + Task 7 (manual smoke). Unit tests for scripts dropped; justified below. +- Implementation artifacts: Tasks 1–5. + +**Placeholder scan:** No `TBD`, `TODO`, or "implement later" markers. Exact commands and complete code in every code step. + +**Type consistency check:** Script CLI signatures (`process.argv` contracts) match the `bash:` wrapper invocations in Task 4. Script file names (`dispatch-review-app.ts`, `ci-wait.ts`, `fetch-review-app-url.ts`) match across Tasks 1–3 and the workflow invocations in Task 4. + +**Divergence from design doc (noted for reviewers):** + +1. **Code-review "2 rounds" unrolled** into explicit nodes rather than a `loop:` node, because loop bodies are single-prompt and cannot wrap the 5-parallel-agents sub-graph. Same net behavior, more verbose YAML. +2. **`reviewApp` config schema dropped for v1.** Values hardcoded in the YAML (`deploy-to-review-app.yml`, `*.review.instrumentl.com` regex). Per-project overrides become work when the second project opts in. +3. **Unit tests for helper scripts dropped.** No existing test pattern for `.archon/scripts/` (the existing `echo-*.js` files have none), and writing one would require new scaffolding. Workflow-level parse test + manual smoke test + defensive script arg validation provide pragmatic coverage. diff --git a/docs/specs/2026-04-17-slack-archie-feature-to-review-app-design.md b/docs/specs/2026-04-17-slack-archie-feature-to-review-app-design.md new file mode 100644 index 0000000000..9bd6da4f15 --- /dev/null +++ b/docs/specs/2026-04-17-slack-archie-feature-to-review-app-design.md @@ -0,0 +1,320 @@ +# Slack @archie: feature request → review app + +**Status:** Design approved, ready for implementation plan +**Date:** 2026-04-17 +**Workflow name:** `archon-slack-feature-to-review-app` + +## Problem + +When a teammate has a feature idea, the path from "I wish we had X" to "there's a +working review app I can try" takes days and crosses many tools (spec doc, Jira +ticket, branch, PR, review, CI, deploy). Most of those steps are mechanical. + +We want a single Slack interaction — `@archie, build a feature to do X` — to drive +the entire loop: clarify the idea, write a spec, get approval, implement in an +isolated worktree, open a PR, run code review until clean, wait for CI, deploy a +review app, and post the review-app URL back to the thread. + +Primary target repo: **instrumentl/instrumentl**. Designed so a second project can +opt in later by registering its codebase with Archon and setting two config +values; no code changes required per new project. + +## Non-Goals + +- Replacing structured product discovery for large initiatives. This is for + features small enough that a PRD-style spec is overkill — one Slack ask, one PR. +- Bug fixes (use `archon-fix-github-issue`). +- Spec-only / PRD-only workflows (use `archon-interactive-prd`). +- Merging the PR. The final artifact is a review app + a PR ready for human + review and merge. + +## Success Criteria + +- A user in Slack tags `@archie` with a feature request and, without leaving the + thread, is asked 3 clarifying question rounds, receives a spec to approve, + then receives ongoing progress updates and a final review-app URL. +- Works end-to-end against `instrumentl/instrumentl` with no custom code beyond + the workflow YAML and 3 helper scripts. +- Re-targeting at a second project requires only: registering the codebase in + Archon, setting `reviewApp.workflowFile` and `reviewApp.urlCommentPattern` in + that repo's `.archon/config.yaml`. + +## Approach Summary + +One new bundled workflow in `packages/workflows/src/defaults/workflows/` that +composes existing commands (spec questions from `archon-interactive-prd`, plan + +implement + PR + review agents from `archon-idea-to-pr`) and adds three small +new pieces: + +1. A bounded 3-iteration spec revision loop. +2. A bounded 2-round code-review loop with an exit condition on "no blocking + findings". +3. Three new bash/script helpers: wait for CI, dispatch the review-app GitHub + Actions workflow, poll PR comments for the review-app URL. + +Plus ~7 lightweight `prompt:` announce nodes at phase boundaries that stream +status lines to the Slack thread. + +## Trigger + Routing + +No custom Slack adapter work. The flow uses existing infrastructure: + +- `SlackAdapter.start()` fires on `app_mention` — strips the mention, passes + text to the orchestrator. +- The orchestrator's router (`archon-assist`) matches workflow `description` + fields. This workflow's description matches phrases like `build X`, `add + feature Y`, `implement Z`, `ship a feature that...`. +- Conversation ID = `channel:thread_ts` — every message and gate response stays + in the same thread. +- The worktree branch is auto-generated from the feature slug, e.g., + `archie/csv-grant-export-2026-04-17`. + +## Configuration + +New optional `reviewApp` section in `.archon/config.yaml` (per-project): + +```yaml +reviewApp: + workflowFile: deploy-to-review-app.yml + urlCommentPattern: 'https://[^\s)]+\.review\.instrumentl\.com[^\s)]*' +``` + +Defaults target Instrumentl. Missing values fall back to sensible defaults; +`urlCommentPattern` not matching any comment after the polling window fails +loudly with a clear error rather than silently succeeding. + +## Workflow Node Graph + +File: `packages/workflows/src/defaults/workflows/archon-slack-feature-to-review-app.yaml`. + +Header: + +```yaml +name: archon-slack-feature-to-review-app +description: | + Use when: A user on Slack/chat asks @archie to build, add, or implement a + feature end-to-end and wants a working review app at the end. Matches phrases + like "build X", "add feature Y", "implement Z", "ship a feature that...". + Input: Feature description in natural language. + Output: Merged-ready PR + review-app URL posted back to the requesting thread. + NOT for: Spec/PRD only (use archon-interactive-prd), code-only changes without + a spec (use archon-idea-to-pr), or bug fixes (use archon-fix-github-issue). +interactive: true +provider: claude +``` + +### Phases + +**A. Spec creation (interactive, bounded 3-iteration revision loop)** +- Reuse foundation / deep-dive / scope question nodes from + `archon-interactive-prd`, each gated by `approval: capture_response: true`. +- `spec-generate` writes to `$ARTIFACTS_DIR/specs/.spec.md`. +- `spec-approval-loop`: `loop:` node wrapping a revise prompt + an approval + gate. Exit on approve. `$REJECTION_REASON` feeds revision. Max 3 iterations. + On cap-hit: post "Spec revision limit reached..." and fail gracefully. + +**B. Plan** +- `archon-create-plan` with the spec path. +- `archon-plan-setup` creates the worktree + branch. + +**C. Implement + validate** +- `archon-implement-tasks` on `claude-opus-4-6[1m]`. +- `archon-validate` runs `bun run validate`. + +**D. PR creation** +- `archon-finalize-pr` opens the PR and marks it ready. PR URL/number flows + forward via `$finalize-pr.output`. + +**E. Code review loop (bounded 2 rounds)** +Single `loop:` node, exit condition "no blocking findings" from +`archon-synthesize-review`, max 2 iterations. Body: +- `review-scope`, `sync`. +- Five parallel review agents: `archon-code-review-agent`, + `archon-error-handling-agent`, `archon-test-coverage-agent`, + `archon-comment-quality-agent`, `archon-docs-impact-agent`. +- `archon-synthesize-review`. +- `archon-implement-review-fixes`. +- On cap-hit with unresolved blocking findings: post findings to Slack and + stop before Phase F. Don't deploy broken code. + +**F. Wait for CI** +- `ci-wait`: `bash:` / `script:` node wrapping + `gh pr checks --watch --fail-fast --interval 30`. 60-minute timeout + (configurable). +- On red: one additional call to `archon-implement-review-fixes` with CI logs + attached as context (separate node from Phase E; does NOT re-enter the review + loop — this is a CI-failure-specific fix pass, not a code-review pass), + followed by one retry of `ci-wait`. Still red → stop with logs posted to + Slack. + +**G. Trigger review app** +- `deploy-review-app`: `bash:` node — `gh workflow run ${reviewApp.workflowFile} + --ref `. + +**H. Fetch review-app URL** +- `fetch-review-url`: `bash:` / `script:` node polling + `gh pr view --json comments` every 20s for up to 15 min, grepping for + `reviewApp.urlCommentPattern`. Extracts the first match. Fails loudly with a + clear message if not found in window. + +**I. Final post to Slack** +- `announce-done`: `prompt:` node emits the final message with PR URL, + review-app URL, review-loop iterations used, total time. Because the + workflow is `interactive: true`, output streams to the Slack thread. + +### Progress Announcements (Option A — inline) + +Short `prompt:` announce nodes at phase boundaries, each directed to print +exactly one status line. Uses `haiku` / cheapest model available. Streams to +Slack via the `interactive: true` mechanism. + +Fixed announces (always fire on happy path): 6 — after spec approval, after +plan, after implementation+validation, after PR creation, after review passes, +after CI passes. The final "done" message is `announce-done` in Phase I. + +Additional announces inside loops (fire variably): review-round-start, +fixes-applied, re-reviewing, CI-failed-retrying. Expect 6–10 total on the +happy path depending on how many review/CI loop iterations run. + +Example sequence: + +``` +🧠 Spec approved. Creating implementation plan... +🏗️ Plan ready. Spinning up worktree and implementing... +✅ Implementation passed local validation. Opening PR... +🔍 PR # opened. Running code review (round 1 of 2)... +🔧 Review found blocking issues. Applying fixes... +🔍 Re-reviewing... +✅ Review clean. Waiting on CI... +🚀 CI green. Deploying review app... +🎉 Done. PR: Review app: +``` + +Intermediate announces between review rounds are emitted inside the loop body; +the sequence shown is the happy-path flow. + +### Dependency Graph + +Strictly linear A → B → C → D → E → F → G → H → I. Parallelism lives inside +phase E's review-loop body only. + +## Authorization + +- `SLACK_ALLOWED_USER_IDS` (existing) gates who can talk to the bot at all. +- Any authorized user in the thread can approve/reject/provide feedback at + any gate. Matches team norms; no second-layer approver list. + +## Failure Modes + +Each case posts a single explanatory message to the thread. + +**Spec phase** +- User abandons mid-questionnaire → 24h approval-gate timeout → "No response in + 24h — cancelling. Tag @archie again when ready." +- Spec revision cap hit → "Spec revision limit reached. Your last feedback: + . Please re-tag @archie with a tighter description." + +**Plan / implement / validate** +- Plan step errors → existing error propagation posts to thread. +- Validation still red after internal retries → "Implementation didn't pass + local validation. Last error: . PR not created." +- Worktree issues → existing `classifyIsolationError` mapping. + +**PR / review** +- `gh pr create` errors → raw `gh` error posted. +- Review loop cap hit with unresolved blockers → "Code review didn't converge + after 2 rounds. PR open at . Remaining blocking findings: ..." + +**CI** +- CI goes red → one fix retry, then "CI still failing after 1 fix attempt. + PR: . Latest CI logs: ..." +- CI timeout (60 min) → "CI hasn't completed in 60 min. PR: ." + +**Review app** +- `gh workflow run` dispatch fails → "Couldn't trigger . PR is ready at ; deploy manually." +- URL not found in 15-min window → "Review app dispatched but no matching URL + appeared in PR comments. Pattern: . PR: ." + +**Cross-cutting** +- `/workflow abandon` → standard engine behavior. +- Archon server restart → `/workflow resume ` works; interactive workflow + resumes from last completed node. +- Slack thread archived mid-run → platform `sendMessage` errors logged, run + completes in DB; user sees results in Archon web UI. + +## Testing + +**Static validation** +- `bun run cli validate workflows archon-slack-feature-to-review-app` — YAML + schema, command refs, `depends_on` edges, `$nodeId.output` refs. +- Added to `bundled-defaults.test.ts`'s "all bundled workflows parse" assertion. + +**Unit tests (new scripts in `.archon/scripts/`)** +- `ci-wait.ts` — mocked `execFileAsync` for green / red / timeout cases. +- `fetch-review-app-url.ts` — first-poll match, eventual-poll match, no-match + timeout, invalid JSON. +- `dispatch-review-app.ts` — invoked with expected args. + +**Integration test (one)** +Run the workflow through the executor with: +- `MockAgentProvider` returning canned AI responses. +- `execFileAsync` mocked for all `gh` calls. +- In-memory platform adapter capturing `sendMessage`. + +Assert: the expected happy-path announce messages land in order on the +captured platform, workflow reaches `done`, final message contains both PR URL +and review-app URL. Exact count asserted is the fixed happy-path set (6 + +final = 7); variable review/CI announces are not count-asserted to keep the +test non-brittle. + +**Manual validation checklist (first real run)** +1. Tag `@archie` with a trivial feature request. +2. Verify 3 question gates ask, answers feed spec. +3. Verify approve path; trigger reject-with-feedback once to confirm revision + loop. +4. Verify PR created with correct branch name. +5. Verify review loop runs; synthesize output sane. +6. Verify `gh workflow run deploy-to-review-app.yml` fires after CI. +7. Verify review-app URL parsed from PR comment and posted to thread. + +**Explicitly NOT testing:** Slack adapter (already covered), the 5 review +agents (already covered), `gh` CLI behavior. + +## Implementation Artifacts + +New files: +- `packages/workflows/src/defaults/workflows/archon-slack-feature-to-review-app.yaml` +- `.archon/scripts/ci-wait.ts` +- `.archon/scripts/fetch-review-app-url.ts` +- `.archon/scripts/dispatch-review-app.ts` +- Corresponding `*.test.ts` next to each script. + +Modified files: +- `packages/workflows/src/defaults/bundled-defaults.ts` — register the new + workflow YAML import. +- `packages/workflows/src/defaults/bundled-defaults.test.ts` — ensure the new + workflow parses. + +No changes to: +- `packages/adapters/src/chat/slack/` — Slack adapter as-is. +- `packages/core/src/orchestrator/` — routing as-is. +- Database schema. + +## Open Decisions for the Implementation Plan + +- Exact `.archon/config.yaml` schema location for `reviewApp` (top-level key + vs. nested under `codebase`). Leaning top-level `reviewApp:`. +- Whether announce nodes should use `sonnet` or `haiku` — leaning cheapest + option that reliably emits exact text, TBD during implementation. +- Naming: `announce-*` node prefix vs. emoji-first in-node IDs. Cosmetic; pick + during implementation. + +## Follow-Up Work (Not in This Design) + +- Option B for progress (workflow-event-driven Slack updates) — revisit if more + workflows need identical announce patterns. +- Auto-merge of the PR once review-app is validated by a human "ship it" reply + — separate workflow. +- Support for non-GitHub review-app deploy mechanisms (e.g., direct HTTP + webhook) — only if a second project needs it. diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 0d2ee9e213..0000000000 --- a/package-lock.json +++ /dev/null @@ -1,4614 +0,0 @@ -{ - "name": "remote-coding-agent", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "remote-coding-agent", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.1.57", - "@octokit/rest": "^22.0.0", - "@openai/codex-sdk": "^0.64.0", - "@slack/bolt": "^4.6.0", - "discord.js": "^14.16.0", - "dotenv": "^17.2.3", - "express": "^5.2.1", - "pg": "^8.11.0", - "telegraf": "^4.16.0", - "telegramify-markdown": "^1.3.0" - }, - "devDependencies": { - "@eslint/js": "^9.39.1", - "@types/bun": "latest", - "@types/express": "^5.0.5", - "@types/node": "^22.0.0", - "@types/pg": "^8.11.0", - "eslint": "^9.39.1", - "eslint-config-prettier": "10.1.8", - "prettier": "^3.7.4", - "typescript": "^5.3.0", - "typescript-eslint": "^8.48.0" - }, - "engines": { - "bun": ">=1.0.0" - } - }, - "node_modules/@anthropic-ai/claude-agent-sdk": { - "version": "0.1.71", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.1.71.tgz", - "integrity": "sha512-O34SQCEsdU11Z2uy30GaJGRLdRbEwEvaQs8APywHVOW/EdIGE0rS/AE4V6p9j45/j5AFwh2USZWlbz5NTlLnrw==", - "license": "SEE LICENSE IN README.md", - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "^0.33.5", - "@img/sharp-darwin-x64": "^0.33.5", - "@img/sharp-linux-arm": "^0.33.5", - "@img/sharp-linux-arm64": "^0.33.5", - "@img/sharp-linux-x64": "^0.33.5", - "@img/sharp-linuxmusl-arm64": "^0.33.5", - "@img/sharp-linuxmusl-x64": "^0.33.5", - "@img/sharp-win32-x64": "^0.33.5" - }, - "peerDependencies": { - "zod": "^3.24.1 || ^4.0.0" - } - }, - "node_modules/@discordjs/builders": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", - "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/formatters": "^0.6.2", - "@discordjs/util": "^1.2.0", - "@sapphire/shapeshift": "^4.0.0", - "discord-api-types": "^0.38.33", - "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.4", - "tslib": "^2.6.3" - }, - "engines": { - "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/collection": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", - "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.11.0" - } - }, - "node_modules/@discordjs/formatters": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", - "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", - "license": "Apache-2.0", - "dependencies": { - "discord-api-types": "^0.38.33" - }, - "engines": { - "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/rest": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", - "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/collection": "^2.1.1", - "@discordjs/util": "^1.1.1", - "@sapphire/async-queue": "^1.5.3", - "@sapphire/snowflake": "^3.5.3", - "@vladfrangu/async_event_emitter": "^2.4.6", - "discord-api-types": "^0.38.16", - "magic-bytes.js": "^1.10.0", - "tslib": "^2.6.3", - "undici": "6.21.3" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", - "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/util": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", - "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", - "license": "Apache-2.0", - "dependencies": { - "discord-api-types": "^0.38.33" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/ws": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", - "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/collection": "^2.1.0", - "@discordjs/rest": "^2.5.1", - "@discordjs/util": "^1.1.0", - "@sapphire/async-queue": "^1.5.2", - "@types/ws": "^8.5.10", - "@vladfrangu/async_event_emitter": "^2.2.4", - "discord-api-types": "^0.38.1", - "tslib": "^2.6.2", - "ws": "^8.17.0" - }, - "engines": { - "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", - "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "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.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "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/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.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/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", - "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.0.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "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.0.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", - "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.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "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.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "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.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "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.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "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.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "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.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "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.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "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.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "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.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "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.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "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.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "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.0.4" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "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/@octokit/auth-token": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", - "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/core": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", - "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.3", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "before-after-hook": "^4.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/endpoint": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", - "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/graphql": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", - "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", - "license": "MIT", - "dependencies": { - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", - "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", - "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", - "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/request": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", - "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.2", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "fast-content-type-parse": "^3.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/rest": { - "version": "22.0.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", - "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", - "license": "MIT", - "dependencies": { - "@octokit/core": "^7.0.6", - "@octokit/plugin-paginate-rest": "^14.0.0", - "@octokit/plugin-request-log": "^6.0.0", - "@octokit/plugin-rest-endpoint-methods": "^17.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@openai/codex-sdk": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@openai/codex-sdk/-/codex-sdk-0.64.0.tgz", - "integrity": "sha512-C675h9+5iL7v3w3XKlMTCmejGyEY4qOygro8pRAoBAQbbCS504SKxosJu5Mb/drhV0GkjbKbKfcjxHtjZJfNDQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@sapphire/async-queue": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", - "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/shapeshift": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", - "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=v16" - } - }, - "node_modules/@sapphire/snowflake": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", - "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@slack/bolt": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@slack/bolt/-/bolt-4.6.0.tgz", - "integrity": "sha512-xPgfUs2+OXSugz54Ky07pA890+Qydk22SYToi8uGpXeHSt1JWwFJkRyd/9Vlg5I1AdfdpGXExDpwnbuN9Q/2dQ==", - "license": "MIT", - "dependencies": { - "@slack/logger": "^4.0.0", - "@slack/oauth": "^3.0.4", - "@slack/socket-mode": "^2.0.5", - "@slack/types": "^2.18.0", - "@slack/web-api": "^7.12.0", - "axios": "^1.12.0", - "express": "^5.0.0", - "path-to-regexp": "^8.1.0", - "raw-body": "^3", - "tsscmp": "^1.0.6" - }, - "engines": { - "node": ">=18", - "npm": ">=8.6.0" - }, - "peerDependencies": { - "@types/express": "^5.0.0" - } - }, - "node_modules/@slack/logger": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", - "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", - "license": "MIT", - "dependencies": { - "@types/node": ">=18.0.0" - }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" - } - }, - "node_modules/@slack/oauth": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@slack/oauth/-/oauth-3.0.4.tgz", - "integrity": "sha512-+8H0g7mbrHndEUbYCP7uYyBCbwqmm3E6Mo3nfsDvZZW74zKk1ochfH/fWSvGInYNCVvaBUbg3RZBbTp0j8yJCg==", - "license": "MIT", - "dependencies": { - "@slack/logger": "^4", - "@slack/web-api": "^7.10.0", - "@types/jsonwebtoken": "^9", - "@types/node": ">=18", - "jsonwebtoken": "^9" - }, - "engines": { - "node": ">=18", - "npm": ">=8.6.0" - } - }, - "node_modules/@slack/socket-mode": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-2.0.5.tgz", - "integrity": "sha512-VaapvmrAifeFLAFaDPfGhEwwunTKsI6bQhYzxRXw7BSujZUae5sANO76WqlVsLXuhVtCVrBWPiS2snAQR2RHJQ==", - "license": "MIT", - "dependencies": { - "@slack/logger": "^4", - "@slack/web-api": "^7.10.0", - "@types/node": ">=18", - "@types/ws": "^8", - "eventemitter3": "^5", - "ws": "^8" - }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" - } - }, - "node_modules/@slack/types": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.19.0.tgz", - "integrity": "sha512-7+QZ38HGcNh/b/7MpvPG6jnw7mliV6UmrquJLqgdxkzJgQEYUcEztvFWRU49z0x4vthF0ixL5lTK601AXrS8IA==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0", - "npm": ">= 6.12.0" - } - }, - "node_modules/@slack/web-api": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.13.0.tgz", - "integrity": "sha512-ERcExbWrnkDN8ovoWWe6Wgt/usanj1dWUd18dJLpctUI4mlPS0nKt81Joh8VI+OPbNnY1lIilVt9gdMBD9U2ig==", - "license": "MIT", - "dependencies": { - "@slack/logger": "^4.0.0", - "@slack/types": "^2.18.0", - "@types/node": ">=18.0.0", - "@types/retry": "0.12.0", - "axios": "^1.11.0", - "eventemitter3": "^5.0.1", - "form-data": "^4.0.4", - "is-electron": "2.2.2", - "is-stream": "^2", - "p-queue": "^6", - "p-retry": "^4", - "retry": "^0.13.1" - }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" - } - }, - "node_modules/@telegraf/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@telegraf/types/-/types-7.1.0.tgz", - "integrity": "sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==", - "license": "MIT" - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bun": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.4.tgz", - "integrity": "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bun-types": "1.3.4" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", - "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "license": "MIT" - }, - "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/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "node_modules/@types/mdast": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", - "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2" - } - }, - "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.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", - "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "license": "MIT" - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" - } - }, - "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/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz", - "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/type-utils": "8.50.0", - "@typescript-eslint/utils": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.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.50.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz", - "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", - "debug": "^4.3.4" - }, - "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", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", - "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.50.0", - "@typescript-eslint/types": "^8.50.0", - "debug": "^4.3.4" - }, - "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.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz", - "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.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.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", - "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", - "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.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz", - "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/utils": "8.50.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.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", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz", - "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", - "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.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", - "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.50.0", - "@typescript-eslint/tsconfig-utils": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.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.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz", - "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.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", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", - "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.50.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vladfrangu/async_event_emitter": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", - "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-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/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/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/before-after-hook": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", - "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", - "license": "Apache-2.0" - }, - "node_modules/body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", - "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.0", - "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": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "license": "MIT", - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "license": "MIT" - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", - "license": "MIT" - }, - "node_modules/bun-types": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.4.tgz", - "integrity": "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "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==", - "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/ccount": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", - "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "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/character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "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==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "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/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/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/discord-api-types": { - "version": "0.38.37", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz", - "integrity": "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w==", - "license": "MIT", - "workspaces": [ - "scripts/actions/documentation" - ] - }, - "node_modules/discord.js": { - "version": "14.25.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz", - "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/builders": "^1.13.0", - "@discordjs/collection": "1.5.3", - "@discordjs/formatters": "^0.6.2", - "@discordjs/rest": "^2.6.0", - "@discordjs/util": "^1.2.0", - "@discordjs/ws": "^1.2.3", - "@sapphire/snowflake": "3.5.3", - "discord-api-types": "^0.38.33", - "fast-deep-equal": "3.1.3", - "lodash.snakecase": "4.1.1", - "magic-bytes.js": "^1.10.0", - "tslib": "^2.6.3", - "undici": "6.21.3" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", - "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==", - "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/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "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==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "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==", - "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/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==", - "license": "MIT" - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@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.12.4", - "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.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "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/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "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/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==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "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/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-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "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==", - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/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/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/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "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.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "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-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "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/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/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "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-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "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==", - "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.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", - "license": "MIT" - }, - "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==", - "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/iconv-lite": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", - "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", - "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==", - "license": "ISC" - }, - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "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", - "engines": { - "node": ">=4" - } - }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-electron": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", - "license": "MIT" - }, - "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-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": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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==", - "license": "MIT" - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "license": "MIT", - "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/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.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "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/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "license": "MIT" - }, - "node_modules/longest-streak": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", - "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/magic-bytes.js": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", - "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", - "license": "MIT" - }, - "node_modules/markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "license": "MIT", - "dependencies": { - "repeat-string": "^1.0.0" - }, - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", - "integrity": "sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^4.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", - "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-string": "^2.0.0", - "micromark": "~2.11.0", - "parse-entities": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz", - "integrity": "sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==", - "license": "MIT", - "dependencies": { - "mdast-util-gfm-autolink-literal": "^0.1.0", - "mdast-util-gfm-strikethrough": "^0.2.0", - "mdast-util-gfm-table": "^0.1.0", - "mdast-util-gfm-task-list-item": "^0.1.0", - "mdast-util-to-markdown": "^0.6.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz", - "integrity": "sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==", - "license": "MIT", - "dependencies": { - "ccount": "^1.0.0", - "mdast-util-find-and-replace": "^1.1.0", - "micromark": "^2.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz", - "integrity": "sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==", - "license": "MIT", - "dependencies": { - "mdast-util-to-markdown": "^0.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz", - "integrity": "sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==", - "license": "MIT", - "dependencies": { - "markdown-table": "^2.0.0", - "mdast-util-to-markdown": "~0.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", - "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", - "license": "MIT", - "dependencies": { - "mdast-util-to-markdown": "~0.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", - "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "longest-streak": "^2.0.0", - "mdast-util-to-string": "^2.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.0.0", - "zwitch": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", - "license": "MIT", - "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==", - "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==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/micromark": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", - "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "debug": "^4.0.0", - "parse-entities": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", - "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.0", - "micromark-extension-gfm-autolink-literal": "~0.5.0", - "micromark-extension-gfm-strikethrough": "~0.6.5", - "micromark-extension-gfm-table": "~0.4.0", - "micromark-extension-gfm-tagfilter": "~0.3.0", - "micromark-extension-gfm-task-list-item": "~0.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", - "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", - "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", - "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", - "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", - "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", - "license": "MIT", - "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "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==", - "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==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "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/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==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "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==", - "license": "MIT", - "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==", - "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==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "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/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue/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/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "license": "MIT", - "dependencies": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/pg": { - "version": "8.16.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", - "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.9.1", - "pg-pool": "^3.10.1", - "pg-protocol": "^1.10.3", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.2.7" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", - "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", - "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", - "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", - "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "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.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "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==", - "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/remark-gfm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz", - "integrity": "sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==", - "license": "MIT", - "dependencies": { - "mdast-util-gfm": "^0.1.0", - "micromark-extension-gfm": "^0.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", - "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^0.8.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-remove-comments": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/remark-remove-comments/-/remark-remove-comments-0.2.0.tgz", - "integrity": "sha512-faGaTeqp0bvDgE0uTofa90EBHD4HXeHN+EUaPDR9datgFvaPkYcLxakaJud9LnomFPkOhx0A9NMYFk/lln/etQ==", - "license": "MIT", - "dependencies": { - "html-comment-regex": "^1.1.2", - "unist-util-visit": "^2.0.3" - } - }, - "node_modules/remark-stringify": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", - "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", - "license": "MIT", - "dependencies": { - "mdast-util-to-markdown": "^0.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "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/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "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/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-compare": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/safe-compare/-/safe-compare-1.1.4.tgz", - "integrity": "sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==", - "license": "MIT", - "dependencies": { - "buffer-alloc": "^1.2.0" - } - }, - "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/sandwich-stream": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sandwich-stream/-/sandwich-stream-2.0.2.tgz", - "integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "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==", - "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/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "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.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "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.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "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==", - "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==", - "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/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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/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/telegraf": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-4.16.3.tgz", - "integrity": "sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==", - "license": "MIT", - "dependencies": { - "@telegraf/types": "^7.1.0", - "abort-controller": "^3.0.0", - "debug": "^4.3.4", - "mri": "^1.2.0", - "node-fetch": "^2.7.0", - "p-timeout": "^4.1.0", - "safe-compare": "^1.1.4", - "sandwich-stream": "^2.0.2" - }, - "bin": { - "telegraf": "lib/cli.mjs" - }, - "engines": { - "node": "^12.20.0 || >=14.13.1" - } - }, - "node_modules/telegraf/node_modules/p-timeout": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", - "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/telegramify-markdown": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/telegramify-markdown/-/telegramify-markdown-1.3.0.tgz", - "integrity": "sha512-Qz2YCEc/1DBt2123bn4OSLANUchpGeTJ/3EyFzIeooGFUL4n+DcTP/4u1DZ0oBpgzQX5NdRHys20T8AJcEEGIA==", - "license": "MIT", - "dependencies": { - "mdast-util-gfm-table": "^0.1.6", - "mdast-util-to-markdown": "^0.6.2", - "remark-gfm": "^1.0.0", - "remark-parse": "^9.0.0", - "remark-remove-comments": "^0.2.0", - "remark-stringify": "^9.0.1", - "unified": "^9.0.0", - "unist-util-remove": "^2.0.1", - "unist-util-visit": "^2.0.3" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-mixer": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", - "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "license": "MIT" - }, - "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/tsscmp": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", - "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", - "license": "MIT", - "engines": { - "node": ">=0.6.x" - } - }, - "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-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz", - "integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.50.0", - "@typescript-eslint/parser": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/utils": "8.50.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", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/undici": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", - "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "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==", - "license": "MIT" - }, - "node_modules/unified": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", - "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", - "license": "MIT", - "dependencies": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.1.0.tgz", - "integrity": "sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==", - "license": "MIT", - "dependencies": { - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "license": "ISC" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vfile": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", - "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "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/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/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "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/zod": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", - "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zwitch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", - "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/package.json b/package.json index b296d638ca..386689c73d 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,14 @@ "bun": "^1.3.0" }, "overrides": { - "test-exclude": "^7.0.1" + "test-exclude": "^7.0.1", + "flatted": "^3.4.2", + "axios": "^1.15.0", + "follow-redirects": "^1.16.0", + "lodash": "^4.18.0", + "path-to-regexp": "^8.4.0", + "picomatch": "^4.0.4", + "undici": "^6.24.0" }, "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.74" diff --git a/packages/adapters/src/chat/slack/adapter.test.ts b/packages/adapters/src/chat/slack/adapter.test.ts index 283353dee1..a1845cd1db 100644 --- a/packages/adapters/src/chat/slack/adapter.test.ts +++ b/packages/adapters/src/chat/slack/adapter.test.ts @@ -26,6 +26,7 @@ mock.module('@archon/paths', () => ({ // Create mock functions const mockPostMessage = mock(() => Promise.resolve(undefined)); const mockReplies = mock(() => Promise.resolve({ messages: [] })); +const mockReactionsAdd = mock(() => Promise.resolve({ ok: true })); const mockEvent = mock(() => {}); const mockStart = mock(() => Promise.resolve(undefined)); const mockStop = mock(() => Promise.resolve(undefined)); @@ -38,6 +39,9 @@ const mockApp = { conversations: { replies: mockReplies, }, + reactions: { + add: mockReactionsAdd, + }, }, event: mockEvent, start: mockStart, @@ -366,4 +370,122 @@ describe('SlackAdapter', () => { ); }); }); + + describe('interactive-loop gate rendering', () => { + let adapter: SlackAdapter; + + beforeEach(() => { + adapter = new SlackAdapter('xoxb-fake', 'xapp-fake'); + mockPostMessage.mockClear(); + }); + + test('renders Approve + Request changes buttons when interactiveGate is set', async () => { + await adapter.sendMessage('C123:1234.5678', 'Review the plan summary.', { + interactiveGate: { runId: 'run-abc', nodeId: 'refine-plan' }, + }); + + expect(mockPostMessage).toHaveBeenCalledTimes(1); + const call = (mockPostMessage as Mock).mock.calls[0][0] as { + blocks: unknown[]; + }; + expect(call.blocks).toHaveLength(2); + const actionsBlock = call.blocks[1] as { + type: string; + block_id: string; + elements: Array<{ action_id: string; style?: string; text: { text: string } }>; + }; + expect(actionsBlock.type).toBe('actions'); + expect(actionsBlock.block_id).toBe('gate_block|run-abc|refine-plan'); + expect(actionsBlock.elements).toHaveLength(2); + expect(actionsBlock.elements[0].action_id).toBe('gate_approve|run-abc|refine-plan'); + expect(actionsBlock.elements[0].style).toBe('primary'); + expect(actionsBlock.elements[0].text.text).toBe('Approve'); + expect(actionsBlock.elements[1].action_id).toBe('gate_request_changes|run-abc|refine-plan'); + expect(actionsBlock.elements[1].text.text).toBe('Request changes'); + }); + + test('omits gate buttons when no interactiveGate metadata is present', async () => { + await adapter.sendMessage('C123:1234.5678', 'plain message'); + + const call = (mockPostMessage as Mock).mock.calls[0][0] as { + blocks: unknown[]; + }; + expect(call.blocks).toHaveLength(1); + expect((call.blocks[0] as { type: string }).type).toBe('markdown'); + }); + + test('attaches buttons only to the final chunk of a long message', async () => { + const paragraph1 = 'a'.repeat(10000); + const paragraph2 = 'b'.repeat(10000); + const message = `${paragraph1}\n\n${paragraph2}`; + + await adapter.sendMessage('C123', message, { + interactiveGate: { runId: 'run-1', nodeId: 'gate-n' }, + }); + + expect(mockPostMessage).toHaveBeenCalledTimes(2); + const first = (mockPostMessage as Mock).mock.calls[0][0] as { + blocks: unknown[]; + }; + const second = (mockPostMessage as Mock).mock.calls[1][0] as { + blocks: unknown[]; + }; + // First chunk has only markdown; second chunk has markdown + actions. + expect(first.blocks).toHaveLength(1); + expect(second.blocks).toHaveLength(2); + expect((second.blocks[1] as { type: string }).type).toBe('actions'); + }); + }); + + describe('acknowledgeReceipt', () => { + const event: SlackMessageEvent = { + text: 'hello', + user: 'U123', + channel: 'C456', + ts: '1234567890.000001', + }; + + beforeEach(() => { + mockReactionsAdd.mockClear(); + }); + + test('posts :eyes: reaction on the incoming message', async () => { + const adapter = new SlackAdapter('xoxb-fake', 'xapp-fake'); + await adapter.acknowledgeReceipt(event); + + expect(mockReactionsAdd).toHaveBeenCalledTimes(1); + const args = (mockReactionsAdd as Mock).mock.calls[0][0] as { + channel: string; + timestamp: string; + name: string; + }; + expect(args.channel).toBe('C456'); + expect(args.timestamp).toBe('1234567890.000001'); + expect(args.name).toBe('eyes'); + }); + + test('does not throw when reactions:write scope is missing', async () => { + // Simulate Slack's `missing_scope` error shape. + const scopeError = Object.assign(new Error('missing_scope'), { + data: { error: 'missing_scope', needed: 'reactions:write' }, + }); + mockReactionsAdd.mockImplementationOnce(() => Promise.reject(scopeError)); + + const adapter = new SlackAdapter('xoxb-fake', 'xapp-fake'); + // If this rejected, the test runner would surface it — proving graceful handling. + await adapter.acknowledgeReceipt(event); + expect(mockReactionsAdd).toHaveBeenCalledTimes(1); + }); + + test('silently skips when message already has the reaction', async () => { + const alreadyReacted = Object.assign(new Error('already_reacted'), { + data: { error: 'already_reacted' }, + }); + mockReactionsAdd.mockImplementationOnce(() => Promise.reject(alreadyReacted)); + + const adapter = new SlackAdapter('xoxb-fake', 'xapp-fake'); + await adapter.acknowledgeReceipt(event); + expect(mockReactionsAdd).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/packages/adapters/src/chat/slack/adapter.ts b/packages/adapters/src/chat/slack/adapter.ts index e74a069356..4f697a615e 100644 --- a/packages/adapters/src/chat/slack/adapter.ts +++ b/packages/adapters/src/chat/slack/adapter.ts @@ -10,6 +10,40 @@ import { parseAllowedUserIds } from './auth'; import { splitIntoParagraphChunks } from '../../utils/message-splitting'; import type { SlackMessageEvent } from './types'; +/** + * Gate action-id + modal callback-id encoding. runId and nodeId are packed + * with a non-colliding separator so the Slack callback can recover them + * without depending on out-of-band state. Separator `|` is safe: workflow + * runIds are UUIDs and node ids are YAML keys (no pipes). + */ +const GATE_SEP = '|'; +const GATE_ACTION_APPROVE = 'gate_approve'; +const GATE_ACTION_REQUEST_CHANGES = 'gate_request_changes'; +const GATE_MODAL_CALLBACK = 'gate_changes_modal'; + +function encodeGateActionId(prefix: string, runId: string, nodeId: string): string { + return `${prefix}${GATE_SEP}${runId}${GATE_SEP}${nodeId}`; +} + +function decodeGateActionId( + actionId: string | undefined +): { runId: string; nodeId: string } | null { + if (!actionId) return null; + const parts = actionId.split(GATE_SEP); + if (parts.length !== 3) return null; + const [, runId, nodeId] = parts; + if (!runId || !nodeId) return null; + return { runId, nodeId }; +} + +/** + * Block type used for Slack message blocks. We don't import @slack/types + * directly — the adapter package only declares @slack/bolt, and the exact + * block shape is validated at runtime by Slack's API. An opaque record keeps + * the compile boundary narrow while still allowing typed construction. + */ +type SlackBlock = Record; + /** Lazy-initialized logger (deferred so test mocks can intercept createLogger) */ let cachedLog: ReturnType | undefined; function getLog(): ReturnType { @@ -49,12 +83,13 @@ export class SlackAdapter implements IPlatformAdapter { * Send a message to a Slack channel/thread * Uses markdown block for proper formatting of AI responses * Automatically splits messages longer than 12000 characters + * + * When `metadata.interactiveGate` is set, the final chunk is followed by + * an Approve / Request changes action block. All other adapters ignore the + * field, so the text body already includes the `/workflow approve` fallback + * and remains complete on every platform. */ - async sendMessage( - channelId: string, - message: string, - _metadata?: MessageMetadata - ): Promise { + async sendMessage(channelId: string, message: string, metadata?: MessageMetadata): Promise { getLog().debug({ channelId, messageLength: message.length }, 'slack.send_message'); // Parse channelId - may include thread_ts as "channel:thread_ts" @@ -62,45 +97,61 @@ export class SlackAdapter implements IPlatformAdapter { ? channelId.split(':') : [channelId, undefined]; + const gate = metadata?.interactiveGate; + if (message.length <= MAX_MARKDOWN_BLOCK_LENGTH) { - // Use markdown block for proper formatting - await this.sendWithMarkdownBlock(channel, message, threadTs); + await this.sendWithMarkdownBlock(channel, message, threadTs, gate); } else { - // Long message: split by paragraphs getLog().debug({ messageLength: message.length }, 'slack.message_splitting'); const chunks = splitIntoParagraphChunks(message, MAX_MARKDOWN_BLOCK_LENGTH - 500); - for (const chunk of chunks) { - await this.sendWithMarkdownBlock(channel, chunk, threadTs); + // Attach gate buttons only to the LAST chunk so a long gate prompt still + // ends with a single actionable row. + for (let i = 0; i < chunks.length; i++) { + const isLast = i === chunks.length - 1; + await this.sendWithMarkdownBlock(channel, chunks[i], threadTs, isLast ? gate : undefined); } } } /** - * Send a message using Slack's markdown block for proper formatting - * Falls back to plain text if block fails + * Send a message using Slack's markdown block for proper formatting. + * Falls back to plain text if block fails. + * + * When `gate` is provided, append an Actions block with Approve / Request + * changes buttons whose action_ids carry the runId + nodeId. This lets the + * user resolve interactive-loop gates with one click instead of typing + * `/workflow approve `. */ private async sendWithMarkdownBlock( channel: string, message: string, - threadTs?: string + threadTs?: string, + gate?: { runId: string; nodeId: string } ): Promise { + const blocks: SlackBlock[] = [{ type: 'markdown', text: message }]; + if (gate) { + blocks.push(this.buildGateActionsBlock(gate)); + } try { + // Cast through `unknown`: SlackBlock is an opaque record by design + // (see type definition). Slack's runtime validates the exact shape, and + // the pre-refactor markdown block was already being cast implicitly. await this.app.client.chat.postMessage({ channel, thread_ts: threadTs, - blocks: [ - { - type: 'markdown', - text: message, - }, - ], + blocks, // Fallback text for notifications/accessibility text: message.substring(0, 150) + (message.length > 150 ? '...' : ''), - }); - getLog().debug({ messageLength: message.length }, 'slack.markdown_block_sent'); + } as unknown as Parameters[0]); + getLog().debug( + { messageLength: message.length, gate: Boolean(gate) }, + 'slack.markdown_block_sent' + ); } catch (error) { - // Fallback to plain text + // Fallback to plain text. Gate buttons are sacrificed in this fallback + // path; the message body still contains the `/workflow approve ...` + // instructions so the user retains a way to resolve the gate. const err = error as Error; getLog().warn({ err, channel, threadTs }, 'slack.markdown_block_failed'); await this.app.client.chat.postMessage({ @@ -111,6 +162,33 @@ export class SlackAdapter implements IPlatformAdapter { } } + /** + * Build the Actions block with Approve (primary) and Request changes + * (neutral) buttons. Action ids pack the workflow run + node so the click + * callback can resolve the target without consulting DB state. + */ + private buildGateActionsBlock(gate: { runId: string; nodeId: string }): SlackBlock { + return { + type: 'actions', + block_id: encodeGateActionId('gate_block', gate.runId, gate.nodeId), + elements: [ + { + type: 'button', + action_id: encodeGateActionId(GATE_ACTION_APPROVE, gate.runId, gate.nodeId), + style: 'primary', + text: { type: 'plain_text', text: 'Approve', emoji: true }, + value: 'approve', + }, + { + type: 'button', + action_id: encodeGateActionId(GATE_ACTION_REQUEST_CHANGES, gate.runId, gate.nodeId), + text: { type: 'plain_text', text: 'Request changes', emoji: true }, + value: 'request_changes', + }, + ], + }; + } + /** * Get the Bolt App instance */ @@ -238,10 +316,44 @@ export class SlackAdapter implements IPlatformAdapter { this.messageHandler = handler; } + /** + * Post an :eyes: reaction to the incoming message so the user knows the + * bot received the request immediately — before thread-history fetch, + * orchestration, lock acquisition, or first LLM token. + * + * Intentionally silent on failure: + * - `reactions:write` scope is optional; missing-scope workspaces still + * get a working bot, just without the visual receipt. + * - We never want a reaction error to block message processing. + */ + async acknowledgeReceipt(event: SlackMessageEvent): Promise { + try { + await this.app.client.reactions.add({ + channel: event.channel, + timestamp: event.ts, + name: 'eyes', + }); + getLog().debug({ channel: event.channel, ts: event.ts }, 'slack.receipt_ack_sent'); + } catch (error) { + const err = error as Error & { data?: { error?: string } }; + // `already_reacted` just means we're re-processing; not worth a warn. + if (err.data?.error === 'already_reacted') { + getLog().debug({ channel: event.channel }, 'slack.receipt_ack_already_reacted'); + return; + } + getLog().warn( + { err, slackError: err.data?.error, channel: event.channel }, + 'slack.receipt_ack_failed' + ); + } + } + /** * Start the bot (connects via Socket Mode) */ async start(): Promise { + this.registerGateHandlers(); + // Register app_mention event handler (when bot is @mentioned) this.app.event('app_mention', async ({ event }) => { // Authorization check @@ -311,4 +423,271 @@ export class SlackAdapter implements IPlatformAdapter { void this.app.stop(); getLog().info('slack.bot_stopped'); } + + /** + * Register Bolt handlers for gate buttons + the "Request changes" modal. + * + * Behavior: + * - Approve click → synthesize a message event with text "approved" in the + * gate's thread; the natural-language approval path in handleMessage + * resumes the paused workflow run. + * - Request changes click → open a modal; text entered on submit is + * synthesized as a thread message (treated as feedback by the loop). + * + * Action IDs are matched with the `gate_approve`/`gate_request_changes` + * prefixes (exact action_ids are per-run and per-node). We register pattern + * matchers so every live gate uses the same handler without per-run + * subscriptions. + */ + private registerGateHandlers(): void { + // Approve button. + this.app.action( + { type: 'block_actions', action_id: new RegExp(`^${GATE_ACTION_APPROVE}\\|`) }, + async ({ ack, body, action, client }) => { + await ack(); + await this.handleGateClick({ + body, + action, + client, + verb: 'approve', + }); + } + ); + + // Request changes button — opens a modal to collect feedback text. + this.app.action( + { type: 'block_actions', action_id: new RegExp(`^${GATE_ACTION_REQUEST_CHANGES}\\|`) }, + async ({ ack, body, action, client }) => { + await ack(); + await this.handleRequestChangesClick({ body, action, client }); + } + ); + + // Modal submission — feedback text is synthesized as a thread message. + this.app.view(GATE_MODAL_CALLBACK, async ({ ack, view, body }) => { + await ack(); + await this.handleGateModalSubmit({ view, body }); + }); + } + + /** + * Handle Approve click: synthesize an "approved" message in the original + * thread so the natural-language resume path fires. + */ + private async handleGateClick(params: { + body: unknown; + action: unknown; + // Typed as `unknown` because Bolt's client union is verbose; we only + // call two well-known methods on it. Actual runtime value is a WebClient. + client: unknown; + verb: 'approve'; + }): Promise { + const { body, action, client } = params; + const ctx = this.extractClickContext(body, action); + const ids = decodeGateActionId((action as { action_id?: string }).action_id); + if (!ctx) { + getLog().warn({ ids }, 'slack.gate_click_missing_context'); + return; + } + getLog().info( + { runId: ids?.runId, nodeId: ids?.nodeId, userId: ctx.userId }, + 'slack.gate_approve_clicked' + ); + + // Best-effort: replace the actions row with a status context so the + // buttons can't be clicked twice. Failure here is non-fatal. + const webClient = client as { + chat: { update: (args: Record) => Promise }; + }; + try { + await webClient.chat.update({ + channel: ctx.channel, + ts: ctx.messageTs, + text: `Approved by <@${ctx.userId}>`, + blocks: [ + { + type: 'context', + elements: [ + { + type: 'mrkdwn', + text: `:white_check_mark: Approved by <@${ctx.userId}>`, + }, + ], + }, + ], + }); + } catch (error) { + getLog().warn({ err: error }, 'slack.gate_update_message_failed'); + } + + await this.dispatchSyntheticMessage({ + channel: ctx.channel, + threadTs: ctx.threadTs, + userId: ctx.userId, + text: 'approved', + }); + } + + /** + * Handle Request changes click: open a modal with a multiline textarea. + * channel + thread + user are packed into `private_metadata` so the modal + * submission handler can post a synthetic reply in the correct thread. + */ + private async handleRequestChangesClick(params: { + body: unknown; + action: unknown; + // See handleGateClick for rationale. + client: unknown; + }): Promise { + const { body, action, client } = params; + const ctx = this.extractClickContext(body, action); + const triggerId = this.extractTriggerId(body); + const ids = decodeGateActionId((action as { action_id?: string }).action_id); + if (!ctx || !triggerId) { + getLog().warn({ ids }, 'slack.gate_changes_click_missing_context'); + return; + } + getLog().info( + { runId: ids?.runId, nodeId: ids?.nodeId, userId: ctx.userId }, + 'slack.gate_request_changes_clicked' + ); + + const privateMetadata = JSON.stringify({ + channel: ctx.channel, + threadTs: ctx.threadTs, + userId: ctx.userId, + }); + + const webClient = client as { + views: { open: (args: Record) => Promise }; + }; + try { + await webClient.views.open({ + trigger_id: triggerId, + view: { + type: 'modal', + callback_id: GATE_MODAL_CALLBACK, + private_metadata: privateMetadata, + title: { type: 'plain_text', text: 'Request changes' }, + submit: { type: 'plain_text', text: 'Send' }, + close: { type: 'plain_text', text: 'Cancel' }, + blocks: [ + { + type: 'input', + block_id: 'feedback_block', + label: { + type: 'plain_text', + text: 'What should change?', + }, + element: { + type: 'plain_text_input', + action_id: 'feedback_input', + multiline: true, + placeholder: { + type: 'plain_text', + text: 'e.g., drop the telemetry task, reorder DB migration first, tighten scope to auth only', + }, + }, + }, + ], + }, + }); + } catch (error) { + getLog().error({ err: error }, 'slack.gate_modal_open_failed'); + } + } + + /** + * Handle modal submission: post the user's feedback text as a synthetic + * thread message so the workflow's interactive loop receives it. + */ + private async handleGateModalSubmit(params: { view: unknown; body: unknown }): Promise { + const { view, body } = params; + const v = view as { + private_metadata?: string; + state?: { values?: Record> }; + }; + + let meta: { channel?: string; threadTs?: string; userId?: string } = {}; + try { + meta = v.private_metadata ? (JSON.parse(v.private_metadata) as typeof meta) : {}; + } catch { + getLog().warn('slack.gate_modal_bad_private_metadata'); + return; + } + const feedback = v.state?.values?.feedback_block?.feedback_input?.value?.trim(); + if (!meta.channel || !meta.threadTs || !feedback) { + getLog().warn('slack.gate_modal_missing_fields'); + return; + } + + // Prefer user id from body (authoritative) over private_metadata. + const userId = (body as { user?: { id?: string } }).user?.id ?? meta.userId ?? 'unknown'; + + await this.dispatchSyntheticMessage({ + channel: meta.channel, + threadTs: meta.threadTs, + userId, + text: feedback, + }); + } + + /** + * Extract channel, message ts, thread, and user from a block_actions body. + * Returns null if any required field is missing (shouldn't happen in + * practice; a defensive nullcheck keeps the handler resilient to future + * Bolt payload changes). + */ + private extractClickContext( + body: unknown, + _action: unknown + ): { channel: string; messageTs: string; threadTs: string; userId: string } | null { + const b = body as { + channel?: { id?: string }; + message?: { ts?: string; thread_ts?: string }; + container?: { thread_ts?: string }; + user?: { id?: string }; + }; + const channel = b.channel?.id; + const messageTs = b.message?.ts; + const threadTs = b.message?.thread_ts ?? b.container?.thread_ts ?? messageTs; + const userId = b.user?.id; + if (!channel || !messageTs || !threadTs || !userId) return null; + return { channel, messageTs, threadTs, userId }; + } + + private extractTriggerId(body: unknown): string | undefined { + return (body as { trigger_id?: string }).trigger_id; + } + + /** + * Invoke the registered message handler with a synthetic event so button + * clicks and modal submissions reuse the normal handleMessage pipeline + * (including conversation-lock serialization and isolation context). + */ + private async dispatchSyntheticMessage(params: { + channel: string; + threadTs: string; + userId: string; + text: string; + }): Promise { + if (!this.messageHandler) { + getLog().warn('slack.gate_synthetic_no_handler'); + return; + } + const event: SlackMessageEvent = { + text: params.text, + user: params.userId, + channel: params.channel, + // `ts` of a synthetic reply: reuse thread_ts; the orchestrator does not + // persist this field, and no Slack API call depends on it. + ts: params.threadTs, + thread_ts: params.threadTs, + }; + try { + await this.messageHandler(event); + } catch (error) { + getLog().error({ err: error }, 'slack.gate_synthetic_dispatch_failed'); + } + } } diff --git a/packages/cli/src/commands/setup.ts b/packages/cli/src/commands/setup.ts index 0235ceec3e..6bb7d74797 100644 --- a/packages/cli/src/commands/setup.ts +++ b/packages/cli/src/commands/setup.ts @@ -1030,7 +1030,7 @@ async function collectSlackConfig(): Promise { ' - Settings -> Socket Mode -> Enable\n' + ' - Generate an App-Level Token (xapp-...)\n' + '3. Add Bot Token Scopes (OAuth & Permissions):\n' + - ' - app_mentions:read, chat:write, channels:history\n' + + ' - app_mentions:read, chat:write, reactions:write, channels:history\n' + ' - channels:join, im:history, im:write, im:read\n' + '4. Subscribe to Bot Events (Event Subscriptions):\n' + ' - app_mention, message.im\n' + diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 74966e3b2c..36120ee3a7 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -113,6 +113,14 @@ export interface MessageMetadata { segment?: 'new' | 'auto'; workflowDispatch?: { workerConversationId: string; workflowName: string }; workflowResult?: { workflowName: string; runId: string }; + /** + * When set, the message is an interactive-loop gate prompt. Adapters that + * support rich input (e.g. Slack Block Kit) may render approve / request- + * changes controls bound to this run + node. Adapters without rich input + * MUST send the plain text body unchanged; the underlying text already + * includes the /workflow approve fallback instructions. + */ + interactiveGate?: { runId: string; nodeId: string }; } export interface IPlatformAdapter { diff --git a/packages/docs-web/src/content/docs/adapters/slack.md b/packages/docs-web/src/content/docs/adapters/slack.md index ce53956793..e414e9aec5 100644 --- a/packages/docs-web/src/content/docs/adapters/slack.md +++ b/packages/docs-web/src/content/docs/adapters/slack.md @@ -55,6 +55,7 @@ Archon uses **Socket Mode** for Slack integration, which means: 3. Add these scopes to bot token scopes: - `app_mentions:read` -- Receive @mention events - `chat:write` -- Send messages + - `reactions:write` -- Post :eyes: receipt reaction on incoming messages (optional; bot works without it) - `channels:history` -- Read messages in public channels (for thread context) - `channels:join` -- Allow bot to join public channels - `groups:history` -- Read messages in private channels (optional) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 3d0d1bdcf5..82ed2ed3bd 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -404,6 +404,10 @@ export async function startServer(opts: ServerOptions = {}): Promise { const content = slackAdapter.stripBotMention(event.text); if (!content) return; // Message was only a mention with no content + // Immediate receipt ack (:eyes:). Fire-and-forget — we don't want to + // delay thread-history fetch or orchestration on a reaction round-trip. + void slackAdapter.acknowledgeReceipt(event); + // Check for thread context let threadContext: string | undefined; let parentConversationId: string | undefined; diff --git a/packages/workflows/src/dag-executor.test.ts b/packages/workflows/src/dag-executor.test.ts index c5822197e5..6345c9e0ec 100644 --- a/packages/workflows/src/dag-executor.test.ts +++ b/packages/workflows/src/dag-executor.test.ts @@ -3404,6 +3404,21 @@ describe('executeDagWorkflow -- resume with priorCompletedNodes', () => { iteration: 1, message: 'Review the plan and provide feedback.', }); + + // Gate-send should carry interactiveGate metadata so adapters like Slack + // can render approve/request-changes UI instead of asking users to type + // `/workflow approve `. + const sendMessageMock = platform.sendMessage as Mock< + (id: string, msg: string, meta?: Record) => Promise + >; + const gateSendCall = sendMessageMock.mock.calls.find(call => { + const body = call[1]; + return typeof body === 'string' && body.includes('Input required'); + }); + expect(gateSendCall).toBeDefined(); + expect(gateSendCall?.[2]).toMatchObject({ + interactiveGate: { runId: 'dag-test-run-id', nodeId: 'refine' }, + }); }); it('interactive loop first iteration always gates even if AI emits signal', async () => { diff --git a/packages/workflows/src/dag-executor.ts b/packages/workflows/src/dag-executor.ts index 3680af28b5..a3a5b7e1b8 100644 --- a/packages/workflows/src/dag-executor.ts +++ b/packages/workflows/src/dag-executor.ts @@ -1869,10 +1869,13 @@ async function executeLoopNode( `\u23f8 **Input required** (loop \`${node.id}\`, iteration ${String(i)}): ${loop.gate_message}\n\n` + `Run ID: \`${workflowRun.id}\`\n` + `Respond: \`/workflow approve ${workflowRun.id} \` | Cancel: \`/workflow reject ${workflowRun.id}\``; - const gateSent = await safeSendMessage(platform, conversationId, gateMsg, { - workflowId: workflowRun.id, - nodeName: node.id, - }); + const gateSent = await safeSendMessage( + platform, + conversationId, + gateMsg, + { workflowId: workflowRun.id, nodeName: node.id }, + { interactiveGate: { runId: workflowRun.id, nodeId: node.id } } + ); if (!gateSent) { // Gate message failed to deliver — do not pause; fail the node so the user // sees a clear error rather than a silently orphaned paused run. diff --git a/packages/workflows/src/defaults/bundled-defaults.test.ts b/packages/workflows/src/defaults/bundled-defaults.test.ts index e1e1cb5a30..5f46f6eff2 100644 --- a/packages/workflows/src/defaults/bundled-defaults.test.ts +++ b/packages/workflows/src/defaults/bundled-defaults.test.ts @@ -91,13 +91,14 @@ describe('bundled-defaults', () => { 'archon-piv-loop', 'archon-adversarial-dev', 'archon-workflow-builder', + 'archon-slack-feature-to-review-app', ]; for (const wf of expectedWorkflows) { expect(BUNDLED_WORKFLOWS).toHaveProperty(wf); } - expect(Object.keys(BUNDLED_WORKFLOWS)).toHaveLength(13); + expect(Object.keys(BUNDLED_WORKFLOWS)).toHaveLength(14); }); it('should have non-empty content for all workflows', () => { diff --git a/packages/workflows/src/defaults/bundled-defaults.ts b/packages/workflows/src/defaults/bundled-defaults.ts index a921171b9e..e6b335d652 100644 --- a/packages/workflows/src/defaults/bundled-defaults.ts +++ b/packages/workflows/src/defaults/bundled-defaults.ts @@ -53,6 +53,7 @@ import archonInteractivePrdWf from '../../../../.archon/workflows/defaults/archo import archonPivLoopWf from '../../../../.archon/workflows/defaults/archon-piv-loop.yaml' with { type: 'text' }; import archonAdversarialDevWf from '../../../../.archon/workflows/defaults/archon-adversarial-dev.yaml' with { type: 'text' }; import archonWorkflowBuilderWf from '../../../../.archon/workflows/defaults/archon-workflow-builder.yaml' with { type: 'text' }; +import archonSlackFeatureToReviewAppWf from '../../../../.archon/workflows/defaults/archon-slack-feature-to-review-app.yaml' with { type: 'text' }; // ============================================================================= // Exports @@ -102,6 +103,7 @@ export const BUNDLED_WORKFLOWS: Record = { 'archon-piv-loop': archonPivLoopWf, 'archon-adversarial-dev': archonAdversarialDevWf, 'archon-workflow-builder': archonWorkflowBuilderWf, + 'archon-slack-feature-to-review-app': archonSlackFeatureToReviewAppWf, }; /** diff --git a/packages/workflows/src/deps.ts b/packages/workflows/src/deps.ts index e8fccfca41..e11988ff00 100644 --- a/packages/workflows/src/deps.ts +++ b/packages/workflows/src/deps.ts @@ -47,6 +47,8 @@ export interface WorkflowMessageMetadata { segment?: 'new' | 'auto'; workflowDispatch?: { workerConversationId: string; workflowName: string }; workflowResult?: { workflowName: string; runId: string }; + /** Mirror of MessageMetadata.interactiveGate — see packages/core/src/types/index.ts. */ + interactiveGate?: { runId: string; nodeId: string }; } // --------------------------------------------------------------------------- diff --git a/packages/workflows/src/workflow-heartbeat.test.ts b/packages/workflows/src/workflow-heartbeat.test.ts new file mode 100644 index 0000000000..e7608efe10 --- /dev/null +++ b/packages/workflows/src/workflow-heartbeat.test.ts @@ -0,0 +1,356 @@ +import { describe, it, expect, mock } from 'bun:test'; + +// --- Mock logger (must come before module-under-test imports) --- +const mockLogFn = mock(() => {}); +const mockLogger = { + info: mockLogFn, + warn: mockLogFn, + error: mockLogFn, + debug: mockLogFn, + trace: mockLogFn, + fatal: mockLogFn, + child: mock(() => mockLogger), + bindings: mock(() => ({ module: 'test' })), + isLevelEnabled: mock(() => true), + level: 'info', +}; +mock.module('@archon/paths', () => ({ + createLogger: mock(() => mockLogger), +})); + +import { + startWorkflowHeartbeat, + DEFAULT_HEARTBEAT_INTERVAL_MS, + type HeartbeatEmitter, +} from './workflow-heartbeat'; +import type { WorkflowEmitterEvent } from './event-emitter'; +import type { IWorkflowPlatform } from './deps'; + +// --------------------------------------------------------------------------- +// In-test emitter — avoids depending on the singleton, which other test +// files in the same Bun process can clobber via `mock.module('./event-emitter')`. +// --------------------------------------------------------------------------- + +type LocalEmitter = HeartbeatEmitter & { emit: (e: WorkflowEmitterEvent) => void }; + +function makeLocalEmitter(): LocalEmitter { + const listeners = new Set<(e: WorkflowEmitterEvent) => void>(); + return { + emit(event) { + for (const l of listeners) l(event); + }, + subscribe(listener) { + listeners.add(listener); + return () => { + listeners.delete(listener); + }; + }, + }; +} + +// --------------------------------------------------------------------------- +// Fakes +// --------------------------------------------------------------------------- + +interface FakeClock { + now: () => number; + advance: (ms: number) => void; +} + +function makeFakeClock(initial = 1_000_000): FakeClock { + let t = initial; + return { + now: () => t, + advance: (ms: number) => { + t += ms; + }, + }; +} + +interface FakeTimer { + cleared: boolean; + intervalMs: number; +} + +interface FakeScheduler { + setInterval: (fn: () => void, ms: number) => FakeTimer; + clearInterval: (handle: FakeTimer) => void; + timer: FakeTimer | undefined; +} + +function makeFakeScheduler(): FakeScheduler { + let timer: FakeTimer | undefined; + return { + get timer() { + return timer; + }, + setInterval: (_fn, ms) => { + const t: FakeTimer = { cleared: false, intervalMs: ms }; + timer = t; + return t; + }, + clearInterval: (handle: FakeTimer) => { + handle.cleared = true; + }, + }; +} + +function makeFakePlatform(platformType: string): { + platform: IWorkflowPlatform; + sent: Array<{ conversationId: string; message: string; metadata?: unknown }>; + failNext: () => void; +} { + const sent: Array<{ conversationId: string; message: string; metadata?: unknown }> = []; + let shouldFail = false; + const platform: IWorkflowPlatform = { + async sendMessage(conversationId, message, metadata) { + if (shouldFail) { + shouldFail = false; + throw new Error('platform unavailable'); + } + sent.push({ conversationId, message, metadata }); + }, + getStreamingMode: () => 'batch', + getPlatformType: () => platformType, + }; + return { + platform, + sent, + failNext: () => { + shouldFail = true; + }, + }; +} + +/** Common fixture builder — returns everything a test usually needs. */ +function setup(platformType: string) { + const clock = makeFakeClock(); + const scheduler = makeFakeScheduler(); + const emitter = makeLocalEmitter(); + const { platform, sent, failNext } = makeFakePlatform(platformType); + + const start = (intervalMs = 60_000) => + startWorkflowHeartbeat({ + runId: 'run-1', + platform, + conversationId: 'conv-1', + intervalMs, + emitter, + now: clock.now, + setInterval: scheduler.setInterval as unknown as typeof setInterval, + clearInterval: scheduler.clearInterval as unknown as typeof clearInterval, + }); + + return { clock, scheduler, emitter, platform, sent, failNext, start }; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('workflow-heartbeat', () => { + it('is a no-op for non-chat platforms', () => { + const { scheduler, sent, start } = setup('web'); + const handle = start(); + expect(scheduler.timer).toBeUndefined(); + expect(sent).toHaveLength(0); + handle.stop(); + }); + + it('skips nodes that are younger than the heartbeat interval', async () => { + const { clock, sent, emitter, start } = setup('slack'); + const handle = start(); + emitter.emit({ + type: 'node_started', + runId: 'run-1', + nodeId: 'implement', + nodeName: 'implement', + }); + clock.advance(30_000); + await handle.tick(); + expect(sent).toHaveLength(0); + handle.stop(); + }); + + it('posts a heartbeat for a node that crosses the interval threshold', async () => { + const { clock, sent, emitter, start } = setup('slack'); + const handle = start(); + emitter.emit({ + type: 'node_started', + runId: 'run-1', + nodeId: 'implement', + nodeName: 'implement', + }); + clock.advance(65_000); + await handle.tick(); + expect(sent).toHaveLength(1); + expect(sent[0]!.conversationId).toBe('conv-1'); + expect(sent[0]!.message).toContain('implement'); + expect(sent[0]!.message).toContain('Still working'); + expect((sent[0]!.metadata as { category?: string }).category).toBe('workflow_status'); + handle.stop(); + }); + + it('includes last tool call when available', async () => { + const { clock, sent, emitter, start } = setup('slack'); + const handle = start(); + emitter.emit({ + type: 'node_started', + runId: 'run-1', + nodeId: 'implement', + nodeName: 'implement', + }); + clock.advance(40_000); + emitter.emit({ type: 'tool_started', runId: 'run-1', toolName: 'Edit', stepName: 'implement' }); + clock.advance(30_000); + await handle.tick(); + expect(sent).toHaveLength(1); + expect(sent[0]!.message).toContain('last tool: Edit'); + handle.stop(); + }); + + it('does not post heartbeats for other runs', async () => { + const { clock, sent, emitter, start } = setup('slack'); + const handle = start(); + emitter.emit({ + type: 'node_started', + runId: 'run-OTHER', + nodeId: 'implement', + nodeName: 'implement', + }); + clock.advance(120_000); + await handle.tick(); + expect(sent).toHaveLength(0); + handle.stop(); + }); + + it('stops posting once a node completes', async () => { + const { clock, sent, emitter, start } = setup('slack'); + const handle = start(); + emitter.emit({ + type: 'node_started', + runId: 'run-1', + nodeId: 'implement', + nodeName: 'implement', + }); + clock.advance(65_000); + await handle.tick(); + expect(sent).toHaveLength(1); + + emitter.emit({ + type: 'node_completed', + runId: 'run-1', + nodeId: 'implement', + nodeName: 'implement', + duration: 65_000, + }); + clock.advance(120_000); + await handle.tick(); + expect(sent).toHaveLength(1); + handle.stop(); + }); + + it('consolidates multiple parallel nodes into one message', async () => { + const { clock, sent, emitter, start } = setup('slack'); + const handle = start(); + emitter.emit({ + type: 'node_started', + runId: 'run-1', + nodeId: 'review-correctness', + nodeName: 'review-correctness', + }); + emitter.emit({ + type: 'node_started', + runId: 'run-1', + nodeId: 'review-security', + nodeName: 'review-security', + }); + clock.advance(90_000); + await handle.tick(); + expect(sent).toHaveLength(1); + expect(sent[0]!.message).toContain('review-correctness'); + expect(sent[0]!.message).toContain('review-security'); + handle.stop(); + }); + + it('re-pings on subsequent ticks for still-running nodes', async () => { + const { clock, sent, emitter, start } = setup('slack'); + const handle = start(); + emitter.emit({ type: 'node_started', runId: 'run-1', nodeId: 'ci-wait', nodeName: 'ci-wait' }); + clock.advance(65_000); + await handle.tick(); + expect(sent).toHaveLength(1); + + clock.advance(65_000); + await handle.tick(); + expect(sent).toHaveLength(2); + handle.stop(); + }); + + it('suppresses re-ping that would occur within the interval window', async () => { + const { clock, sent, emitter, start } = setup('slack'); + const handle = start(); + emitter.emit({ type: 'node_started', runId: 'run-1', nodeId: 'ci-wait', nodeName: 'ci-wait' }); + clock.advance(65_000); + await handle.tick(); + expect(sent).toHaveLength(1); + + clock.advance(10_000); + await handle.tick(); + expect(sent).toHaveLength(1); + handle.stop(); + }); + + it('swallows platform send errors so the workflow is not affected', async () => { + const { clock, sent, emitter, failNext, start } = setup('slack'); + const handle = start(); + emitter.emit({ + type: 'node_started', + runId: 'run-1', + nodeId: 'implement', + nodeName: 'implement', + }); + clock.advance(65_000); + failNext(); + await expect(handle.tick()).resolves.toBeUndefined(); + expect(sent).toHaveLength(0); + handle.stop(); + }); + + it('stop() clears the timer and unsubscribes from events', async () => { + const { clock, scheduler, sent, emitter, start } = setup('slack'); + const handle = start(); + handle.stop(); + expect(scheduler.timer?.cleared).toBe(true); + + emitter.emit({ + type: 'node_started', + runId: 'run-1', + nodeId: 'implement', + nodeName: 'implement', + }); + clock.advance(120_000); + await handle.tick(); + expect(sent).toHaveLength(0); + }); + + it('stop() is idempotent', () => { + const { start } = setup('slack'); + const handle = start(); + handle.stop(); + expect(() => handle.stop()).not.toThrow(); + }); + + it('emits for discord and telegram as well as slack', () => { + for (const platformType of ['discord', 'telegram']) { + const { scheduler, start } = setup(platformType); + const handle = start(); + expect(scheduler.timer).toBeDefined(); + handle.stop(); + } + }); + + it('exposes a sensible default interval', () => { + expect(DEFAULT_HEARTBEAT_INTERVAL_MS).toBe(60_000); + }); +}); diff --git a/packages/workflows/src/workflow-heartbeat.ts b/packages/workflows/src/workflow-heartbeat.ts new file mode 100644 index 0000000000..f93489a6b3 --- /dev/null +++ b/packages/workflows/src/workflow-heartbeat.ts @@ -0,0 +1,242 @@ +/** + * Workflow heartbeat — periodic "still working" notifications for chat platforms. + * + * Long-running workflows (e.g. `archon-slack-feature-to-review-app`) have + * nodes that can run silently for many minutes (batch-mode sends, `ci-wait`, + * parallel review agents, etc.). Without liveness signals, users assume the + * workflow is hung. This module subscribes to the workflow event emitter to + * track currently-running nodes, then posts a compact status line on a timer + * so the thread keeps moving. + * + * Scope (per design selection): + * - Runs only for chat platforms (slack, telegram, discord). Web UI and CLI + * already stream their own progress indicators. + * - Fires every `intervalMs` (default 60s). Nodes must have been running at + * least `intervalMs` before we ping — fast nodes are never surfaced. + * - Each node emits at most one heartbeat per tick; re-emits on subsequent + * ticks only if the node is still running. + * - Messages include last tool call when available for richer progress signal. + * + * Called by `executor.ts` around `executeDagWorkflow`: started after the + * `workflow_started` event, stopped in the outer `finally` block so it + * terminates on completion, failure, pause, or cancellation. + */ +import type { IWorkflowPlatform, WorkflowMessageMetadata } from './deps'; +import { getWorkflowEventEmitter, type WorkflowEmitterEvent } from './event-emitter'; + +/** Minimal subscribe surface used by the heartbeat — matches WorkflowEventEmitter. */ +export interface HeartbeatEmitter { + subscribe(listener: (event: WorkflowEmitterEvent) => void): () => void; +} +import { formatDuration } from './utils/duration'; +import { createLogger } from '@archon/paths'; + +/** Lazy-initialized logger */ +let cachedLog: ReturnType | undefined; +function getLog(): ReturnType { + if (!cachedLog) cachedLog = createLogger('workflow.heartbeat'); + return cachedLog; +} + +/** Default tick interval — also the minimum age a node must reach before pinging. */ +export const DEFAULT_HEARTBEAT_INTERVAL_MS = 60_000; + +/** Chat platforms that benefit from heartbeats (batch mode default + multi-minute silences). */ +const HEARTBEAT_PLATFORMS = new Set(['slack', 'telegram', 'discord']); + +/** + * Tracked state for a currently-executing node. Populated on `node_started` + * and cleared on completion/failure/skip. `lastToolName` is refreshed on each + * `tool_started` so heartbeats can surface the most recent activity. + */ +interface ActiveNodeState { + nodeId: string; + nodeName: string; + startedAt: number; + /** When the last heartbeat for this node fired (ms since epoch). */ + lastHeartbeatAt?: number; + lastToolName?: string; + lastToolStartedAt?: number; +} + +export interface HeartbeatOptions { + runId: string; + platform: IWorkflowPlatform; + conversationId: string; + /** Defaults to DEFAULT_HEARTBEAT_INTERVAL_MS when omitted. */ + intervalMs?: number; + /** Injectable clock for tests. */ + now?: () => number; + /** Injectable scheduler for tests — must return a handle compatible with clearInterval. */ + setInterval?: (fn: () => void, ms: number) => ReturnType; + clearInterval?: (handle: ReturnType) => void; + /** Override platform filter — test injection point. */ + shouldEmitForPlatform?: (platformType: string) => boolean; + /** Override event emitter — test injection point. Defaults to the singleton. */ + emitter?: HeartbeatEmitter; +} + +export interface HeartbeatHandle { + stop: () => void; + /** Exposed for tests — invoked internally by the interval timer. */ + tick: () => Promise; +} + +/** + * Start the heartbeat loop for a workflow run. Returns a handle whose + * `stop()` unsubscribes from events and clears the timer. Calling `stop` + * more than once is safe (idempotent). + * + * Returns a no-op handle when the platform is not a chat adapter, so + * callers can wrap every run unconditionally without branching. + */ +export function startWorkflowHeartbeat(options: HeartbeatOptions): HeartbeatHandle { + const { + runId, + platform, + conversationId, + intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS, + now = Date.now, + setInterval: setIntervalFn = setInterval, + clearInterval: clearIntervalFn = clearInterval, + shouldEmitForPlatform = (t: string): boolean => HEARTBEAT_PLATFORMS.has(t), + } = options; + + const platformType = platform.getPlatformType(); + if (!shouldEmitForPlatform(platformType)) { + return { + stop: (): void => { + /* no-op for non-chat platforms */ + }, + tick: async (): Promise => { + /* no-op for non-chat platforms */ + }, + }; + } + + const workflowStartedAt = now(); + const activeNodes = new Map(); + + const emitter: HeartbeatEmitter = options.emitter ?? getWorkflowEventEmitter(); + const unsubscribe = emitter.subscribe((event: WorkflowEmitterEvent) => { + if (event.runId !== runId) return; + + switch (event.type) { + case 'node_started': { + activeNodes.set(event.nodeId, { + nodeId: event.nodeId, + nodeName: event.nodeName, + startedAt: now(), + }); + break; + } + case 'node_completed': + case 'node_failed': + case 'node_skipped': { + activeNodes.delete(event.nodeId); + break; + } + case 'tool_started': { + // `stepName` === nodeId for DAG nodes (see dag-executor emissions). + const state = activeNodes.get(event.stepName); + if (state) { + state.lastToolName = event.toolName; + state.lastToolStartedAt = now(); + } + break; + } + default: + break; + } + }); + + const tick = async (): Promise => { + if (activeNodes.size === 0) return; + const tickNow = now(); + const linesToPost: string[] = []; + + for (const state of activeNodes.values()) { + const nodeAge = tickNow - state.startedAt; + if (nodeAge < intervalMs) continue; + + // Re-pinging: only emit if it's been at least `intervalMs` since the + // last heartbeat for this node. Without this, an aggressive interval + // override (e.g. tests) could double-post. + if (state.lastHeartbeatAt !== undefined && tickNow - state.lastHeartbeatAt < intervalMs) { + continue; + } + + state.lastHeartbeatAt = tickNow; + linesToPost.push(formatHeartbeatLine(state, tickNow)); + } + + if (linesToPost.length === 0) return; + + const totalElapsed = formatDuration(tickNow - workflowStartedAt); + const body = + linesToPost.length === 1 + ? `${linesToPost[0]} — ${totalElapsed} total` + : `⏳ Still working — ${totalElapsed} total\n${linesToPost.map(l => `• ${l}`).join('\n')}`; + + try { + const metadata: WorkflowMessageMetadata = { + category: 'workflow_status', + segment: 'auto', + }; + await platform.sendMessage(conversationId, body, metadata); + } catch (error) { + // Non-fatal: heartbeats are purely informational. Log and move on so a + // transient platform hiccup never affects the workflow itself. + getLog().warn( + { err: error as Error, runId, conversationId, platformType }, + 'workflow.heartbeat_send_failed' + ); + } + }; + + const handle = setIntervalFn(() => { + void tick(); + }, intervalMs); + + // Don't keep the process alive just for heartbeats — the workflow executor + // owns the lifecycle; if it exits the heartbeat should not block shutdown. + if (typeof (handle as { unref?: () => void }).unref === 'function') { + (handle as { unref: () => void }).unref(); + } + + let stopped = false; + return { + tick, + stop: (): void => { + if (stopped) return; + stopped = true; + try { + clearIntervalFn(handle); + } catch (err) { + getLog().warn({ err: err as Error, runId }, 'workflow.heartbeat_clear_failed'); + } + try { + unsubscribe(); + } catch (err) { + getLog().warn({ err: err as Error, runId }, 'workflow.heartbeat_unsub_failed'); + } + activeNodes.clear(); + }, + }; +} + +/** + * Per-node heartbeat line. Surfaces last tool when known so users see what + * the node is currently doing instead of just "still running". + */ +function formatHeartbeatLine(state: ActiveNodeState, nowMs: number): string { + const nodeElapsed = formatDuration(nowMs - state.startedAt); + let line = `⏳ Still working on \`${state.nodeName}\` — ${nodeElapsed} on this step`; + + if (state.lastToolName && state.lastToolStartedAt !== undefined) { + const toolAgo = formatDuration(nowMs - state.lastToolStartedAt); + line += `, last tool: ${state.lastToolName} (${toolAgo} ago)`; + } + + return line; +}