From b6180d2339c5804fb3523517356e513336592e38 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 13:34:19 +0000 Subject: [PATCH] Remove auto-implement functionality Removes the issue-auto-implement GitHub Actions workflows and the associated composite action since it did not work as intended. --- .../actions/issue-auto-implement/.env.example | 24 - .../actions/issue-auto-implement/.gitignore | 9 - .../actions/issue-auto-implement/AGENTS.md | 93 - .../issue-auto-implement/DIAGNOSIS-PR-261.md | 45 - .../actions/issue-auto-implement/README.md | 155 -- .../actions/issue-auto-implement/action.yml | 359 --- .../assess/package-lock.json | 2252 ----------------- .../issue-auto-implement/assess/package.json | 22 - .../assess/src/implement.ts | 204 -- .../issue-auto-implement/assess/src/index.ts | 298 --- .../assess/src/load-dotenv.ts | 12 - .../assess/src/normalize.ts | 77 - .../assess/src/push-and-open-pr.ts | 102 - .../assess/src/run-local-assess.ts | 229 -- .../assess/test/fixtures/issue-comment.json | 11 - .../assess/test/fixtures/issue-labeled.json | 11 - .../test/fixtures/pull_request_review.json | 14 - .../integration/assess.integration.test.ts | 77 - .../assess/test/unit/index.test.ts | 120 - .../assess/test/unit/normalize.test.ts | 92 - .../issue-auto-implement/assess/tsconfig.json | 14 - .../assess/vitest.config.ts | 8 - .../assess/vitest.integration.config.ts | 11 - .../scripts/push-and-open-pr.sh | 54 - .../scripts/setup-local-env.sh | 52 - .../workflows/issue-auto-implement-setup.yml | 36 - .../workflows/issue-auto-implement-test.yml | 28 - .github/workflows/issue-auto-implement.yml | 46 - .github/workflows/test.yml | 2 +- 29 files changed, 1 insertion(+), 4456 deletions(-) delete mode 100644 .github/actions/issue-auto-implement/.env.example delete mode 100644 .github/actions/issue-auto-implement/.gitignore delete mode 100644 .github/actions/issue-auto-implement/AGENTS.md delete mode 100644 .github/actions/issue-auto-implement/DIAGNOSIS-PR-261.md delete mode 100644 .github/actions/issue-auto-implement/README.md delete mode 100644 .github/actions/issue-auto-implement/action.yml delete mode 100644 .github/actions/issue-auto-implement/assess/package-lock.json delete mode 100644 .github/actions/issue-auto-implement/assess/package.json delete mode 100644 .github/actions/issue-auto-implement/assess/src/implement.ts delete mode 100644 .github/actions/issue-auto-implement/assess/src/index.ts delete mode 100644 .github/actions/issue-auto-implement/assess/src/load-dotenv.ts delete mode 100644 .github/actions/issue-auto-implement/assess/src/normalize.ts delete mode 100644 .github/actions/issue-auto-implement/assess/src/push-and-open-pr.ts delete mode 100644 .github/actions/issue-auto-implement/assess/src/run-local-assess.ts delete mode 100644 .github/actions/issue-auto-implement/assess/test/fixtures/issue-comment.json delete mode 100644 .github/actions/issue-auto-implement/assess/test/fixtures/issue-labeled.json delete mode 100644 .github/actions/issue-auto-implement/assess/test/fixtures/pull_request_review.json delete mode 100644 .github/actions/issue-auto-implement/assess/test/integration/assess.integration.test.ts delete mode 100644 .github/actions/issue-auto-implement/assess/test/unit/index.test.ts delete mode 100644 .github/actions/issue-auto-implement/assess/test/unit/normalize.test.ts delete mode 100644 .github/actions/issue-auto-implement/assess/tsconfig.json delete mode 100644 .github/actions/issue-auto-implement/assess/vitest.config.ts delete mode 100644 .github/actions/issue-auto-implement/assess/vitest.integration.config.ts delete mode 100755 .github/actions/issue-auto-implement/scripts/push-and-open-pr.sh delete mode 100755 .github/actions/issue-auto-implement/scripts/setup-local-env.sh delete mode 100644 .github/workflows/issue-auto-implement-setup.yml delete mode 100644 .github/workflows/issue-auto-implement-test.yml delete mode 100644 .github/workflows/issue-auto-implement.yml diff --git a/.github/actions/issue-auto-implement/.env.example b/.github/actions/issue-auto-implement/.env.example deleted file mode 100644 index 076a2d06..00000000 --- a/.github/actions/issue-auto-implement/.env.example +++ /dev/null @@ -1,24 +0,0 @@ -# Issue auto-implement — local development -# Copy to .env and fill in secrets. Do not commit .env. - -# Required for assess and implement. Get from https://console.anthropic.com/ -# Implement passes this to Claude Code CLI (claude on PATH). -AUTO_IMPLEMENT_ANTHROPIC_API_KEY= - -# Required for implement; optional for assess (redirect-to-PR, fetch comments). Run: gh auth token -GITHUB_TOKEN= - -# Required for implement. Example: hookdeck/hookdeck-cli -GITHUB_REPOSITORY= - -# Required for implement. Issue number to implement. -# ISSUE_NUMBER= - -# Optional. Repo root; default inferred from cwd when run from assess/. -# GITHUB_WORKSPACE= - -# Optional. Comma-separated paths (relative to repo root) for Claude context. -# CONTEXT_FILES=AGENTS.md,REFERENCE.md - -# Optional. Passed from assess step into implement prompt. -# VERIFICATION_NOTES= diff --git a/.github/actions/issue-auto-implement/.gitignore b/.github/actions/issue-auto-implement/.gitignore deleted file mode 100644 index ca662105..00000000 --- a/.github/actions/issue-auto-implement/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Workflow artifacts written by implement script (read by action, never commit) -.commit_msg -.pr_title -.pr_body -# PR comment body when implement makes no code changes (no-change rationale or request for clarification; workflow reads, then discards) -.comment_body - -# Local env (secrets); do not commit -.env diff --git a/.github/actions/issue-auto-implement/AGENTS.md b/.github/actions/issue-auto-implement/AGENTS.md deleted file mode 100644 index 64a0c04c..00000000 --- a/.github/actions/issue-auto-implement/AGENTS.md +++ /dev/null @@ -1,93 +0,0 @@ -# AGENTS.md — Issue auto-implement action - -For agents making changes to this action. This file summarizes flows, design decisions, and implementation details. - -## Flows - -### 1. Issue to first PR - -- **Triggers:** `issues.labeled` (prefixed trigger label), `issue_comment.created` on a labeled issue when **no PR exists yet**. -- **Flow:** Normalize event → assess (enough info?) → if `request_info`: post comment, add needs-info label, exit. If `implement`: implement step (push to branch `auto-implement-issue-`) → verify → on fail retry (cap `max_implement_retries`); on pass create PR with "Closes #N", add pr-created label, optional comment. - -### 2. Issue comment when PR already exists - -- **Trigger:** `issue_comment.created` on an issue that **already has an open PR** for that issue. -- **Flow:** Post a short reply on the issue directing the user to the PR; exit. No assessment or implement. - -### 3. PR review or PR conversation comment → iteration - -- **Triggers:** `pull_request_review.submitted`, `pull_request_review_comment.created`, or `issue_comment.created` **on a PR** (when `issue.pull_request` is set) when the PR is from an automation branch or body contains "Closes #N". -- **Flow:** Resolve issue number from PR (body "Closes #N"/"Fixes #N" or head branch `auto-implement-issue-`) → assess with issue + review/comment content → implement ("address review feedback"), push to same branch → verify → on pass: do **not** create PR; post comment on the PR summarizing the new commit(s). - -## Event normalization - -From the workflow event payload, derive: - -- **Issue number:** For `issues` or `issue_comment`: `event.issue.number`. For `pull_request_review` or `pull_request_review_comment`: parse PR body for "Closes #N" or "Fixes #N", or PR head branch for `auto-implement-issue-`. -- **PR exists for issue (issue_comment only):** Check whether an open PR exists for that issue (e.g. head branch `auto-implement-issue-` or body "Closes #"). - -## Request more info vs comment body from implement - -- **Request more info (assess):** The **assess** step decides there is **not enough information** to implement. It returns `action: request_info` and a `comment_body`. The workflow posts that on the issue (or on the PR when the trigger was a PR review/comment), adds the `needs-info` label, and **exits without running implement**. -- **Comment body from implement (no change or need clarification):** The assess step said **implement**. The **implement** step ran (Claude Code CLI with full repo context). When Claude **chose not to make code changes** — e.g. the feedback is a question, the current approach is preferred, or it needs clarification — it **must** write `.comment_body` with the text to post on the PR (one or two sentences). The workflow posts that on the **PR** (review iteration path). If `.comment_body` is missing, the workflow falls back to a generic message. The implement prompt requires Claude to always write `.comment_body` when making no code changes so reviewers get a useful reply. Use for: (a) no-change scenarios (thank the reviewer, briefly explain), or (b) when more information is requested (e.g. "Could you clarify whether you want X or Y?"). - -If a reviewer's comment is ambiguous, assess might still return **implement** (optimistic). Then implement runs; Claude can either make a best-effort change, or write `.comment_body` asking for clarification. That clarification is posted on the PR. - -## Assess script - -- **Path:** `assess/src/index.ts` (TypeScript), run with `npx tsx src/index.ts` from the assess directory (no build). -- **Input:** Reads event from `GITHUB_EVENT_PATH`; optional context files from input. -- **Output:** JSON with `action` (`implement` | `request_info`), `comment_body` (if request_info), `verification_notes` (optional), `review_feedback` (when trigger is PR review or comment on PR — exact text for implement to address). Written to file or GITHUB_OUTPUT. -- **When triggered by PR review:** Include PR review body and review comments in the payload sent to Claude. Assess also sets `review_feedback` from the review/comment so the implement step receives it. - -## Implement script - -- **Path:** `assess/src/implement.ts`, run with `npx tsx src/implement.ts` from the assess directory. -- **Env:** `ISSUE_NUMBER`, `GITHUB_REPOSITORY`, `GITHUB_TOKEN`, `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` (required); `VERIFICATION_NOTES`, `REVIEW_FEEDBACK`, `GITHUB_WORKSPACE`, `CONTEXT_FILES`, `IMPLEMENT_COMMIT_MSG_FILE`, `PREVIOUS_VERIFY_OUTPUT` (optional). -- **Flow:** Fetches issue title/body from GitHub API, loads context files, then runs **Claude Code CLI** (`claude` on PATH) in the repo root with a prompt; the script passes `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` to the CLI as `ANTHROPIC_API_KEY`. When `REVIEW_FEEDBACK` is set (PR review or comment iteration), the prompt includes a "Review feedback to address (you must implement this)" section so the CLI addresses the reviewer's ask (e.g. add tests). The CLI implements in-repo (Read/Edit/Bash). When Claude makes code changes it writes `.commit_msg`, `.pr_title`, `.pr_body`; when it makes no code changes it writes `.comment_body` (no-change rationale or request for clarification). The script ensures commit/PR meta files exist only when Claude did not write `.comment_body`. When `PREVIOUS_VERIFY_OUTPUT` is set (e.g. after a failed verify run), it is included in the prompt so the CLI can fix the implementation. In CI, the action installs the CLI (`npm install -g @anthropic-ai/claude-code`) before the implement step when the assess outcome is `implement`. - -## Implement–verify loop - -- **Single step** `implement_verify_loop`: for each attempt from 1 to `max_implement_retries`, run implement (with `PREVIOUS_VERIFY_OUTPUT` from the previous failure, if any), commit and push, then run `verify_commands`. If verify passes, exit success. If it fails, set the verify output as `PREVIOUS_VERIFY_OUTPUT` and retry. After all attempts, fail. When this step succeeds: if trigger was `pull_request_review`, `pull_request_review_comment`, or `issue_comment` on a PR (`issue.pull_request` set), post a comment on the existing PR (no new PR); otherwise create PR. - -## Branch and PR - -- Branch name: `auto-implement-issue-`. -- PR title or body must include "Closes #N" (or "Fixes #N") so merging auto-closes the issue. -- On iteration (PR already exists): do not create a new PR; post a comment on the PR summarizing the new commit(s). - -## Restricting who can trigger - -The first step enforces one of two gates (exactly one must be configured; no bypass): - -1. **Permission check** — If `github_allowed_trigger_min_permission` is set (repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION`: `triage`, `push`, `maintain`, or `admin`), the action calls the repo collaborator permission API and requires the actor to have at least that permission. Works with the default `GITHUB_TOKEN`. -2. **Team check** — Otherwise, if `github_allowed_trigger_team` is set (repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`, e.g. `org/team`), the action checks `github.actor` against that team; if unset or not a member, fail. Token must have `read:org` (PAT if `GITHUB_TOKEN` lacks it). - -If neither variable is set, the step fails. When both are set, the permission check is used and the team value is ignored. - -## Labels - -All automation labels use `label_prefix` (default `automation`): `{prefix}/auto-implement`, `{prefix}/needs-info`, `{prefix}/pr-created`. Create via API if missing. - -## Local development - -Scripts load a `.env` file from the action root or cwd (see README **Local runs**). Key env: `AUTO_IMPLEMENT_ANTHROPIC_API_KEY`, `GITHUB_TOKEN`, `GITHUB_REPOSITORY`, `ISSUE_NUMBER` (implement). Copy `.env.example` to `.env` and fill; optional `./scripts/setup-local-env.sh --with-gh` to set `GITHUB_TOKEN` from `gh auth token`. - -## Verification - -When changing this action or the assess script: - -1. **Run the assess unit tests locally** before committing: `cd .github/actions/issue-auto-implement/assess && npm ci && npm test`. Do not rely on CI alone—catch failures locally first, then push. -2. **CI** runs the same tests in `.github/workflows/issue-auto-implement-test.yml` when you push or open a PR that touches `.github/actions/issue-auto-implement/**`. -3. **Optional:** Run the assess script with a fixture and `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` for end-to-end assessment behavior. -4. **Full workflow:** Trigger manually on a test issue (trigger label or comment). Ensure repo secrets/variables are set. Inspect the Actions run and the issue/PR; re-run after changes to the implement or verify loop. - -## Next steps (implementation backlog) - -Possible future improvements: - -1. **Fetch all issue comments** — For `issues` and `issue_comment` events, optionally call the GitHub API to list all comments on the issue and include them in the assessment payload (not only the single comment from the event). -2. **Optional comment when PR is created** — Input `post_pr_comment` exists; when true, post a short comment on the issue linking to the new PR when one is created. -3. **Local run with fixture and Claude** — `npm run assess:fixture` exists; optional end-to-end testing with real `AUTO_IMPLEMENT_ANTHROPIC_API_KEY`. - -Done: context files in assess, implement step (Claude Code CLI), implement–verify loop with re-implement on failure, PR review iteration (comment on PR, no new PR), comment when retries exhausted, secrets/variables and README docs, local assess script. diff --git a/.github/actions/issue-auto-implement/DIAGNOSIS-PR-261.md b/.github/actions/issue-auto-implement/DIAGNOSIS-PR-261.md deleted file mode 100644 index 9f54700a..00000000 --- a/.github/actions/issue-auto-implement/DIAGNOSIS-PR-261.md +++ /dev/null @@ -1,45 +0,0 @@ -# Diagnosis: PR #261 — Bot replied "Verification passed" instead of adding tests - -## What happened - -- **PR #261** added `--local` to `login` and `ci` (auto-implement from issue #215). -- **Reviewer comment:** "Could we add some unit and acceptance test coverage for this change?" -- **Bot response:** "The --local flag has already been implemented... Verification passed." — no new tests were added. - -## Root cause: review feedback never reaches the implement step - -The pipeline has two steps that use Claude: - -1. **Assess** (`assess/src/index.ts`) — decides `implement` vs `request_info`, outputs `verification_notes`. -2. **Implement** (`assess/src/implement.ts`) — runs Claude Code CLI in the repo to make changes. - -**Assess** receives the full event payload. For a comment on the PR (`issue_comment` on the PR, or `pull_request_review`), the prompt includes: -- Issue (for a PR comment: the PR title and body), -- "All issue comments" (PR comments from the API), -- "Latest event comment" or "PR review (address this feedback):" with the reviewer's text. - -So assess **does** see "Could we add some unit and acceptance test coverage for this change?" and correctly returns `action: implement`. - -**Implement** only receives: -- `ISSUE_NUMBER` (215), -- Issue title/body **fetched from the GitHub API for issue #215** (the original issue about `--local`), -- `VERIFICATION_NOTES` (from assess: generic "run test suite" style notes), -- Context files, `PREVIOUS_VERIFY_OUTPUT`. - -So implement **never** sees the PR comment or review body. The Claude Code CLI prompt is built from the **original issue** only. It has no instruction to "add unit and acceptance tests" — so it reasonably treats the issue as already done and writes `.comment_body` ("Verification passed") instead of making code changes. - -## Why it wasn't "enough coverage" - -The bot didn't decide there was "enough coverage." It never had the reviewer's ask in the implement prompt. So it didn't consider coverage at all; it only had the original issue text. - -## Design gap - -AGENTS.md says: *"assess with issue + review/comment content → implement ('address review feedback')"*. The **assess** step does get review/comment content, but that content is **not** passed through to **implement**. So "address review feedback" is not possible with the current wiring. - -## Fix (implemented in this worktree) - -1. **Assess** — When the trigger is PR review or a comment on a PR, derive the review/comment text from the payload and add it to the assessment JSON output as `review_feedback` (so the workflow can pass it on). -2. **Action** — Expose `review_feedback` from the assess step and pass it to the implement step as `REVIEW_FEEDBACK`. -3. **Implement** — Read `REVIEW_FEEDBACK`; when set, add a clear section to the Claude Code CLI prompt: **"Review feedback to address (you must implement this):"** so the CLI actually implements the reviewer's request (e.g. add unit and acceptance tests). - -After this change, a comment like "Could we add some unit and acceptance test coverage for this change?" will be passed into the implement prompt, and the implement step will be instructed to address it, so the bot should add tests instead of replying with "Verification passed." diff --git a/.github/actions/issue-auto-implement/README.md b/.github/actions/issue-auto-implement/README.md deleted file mode 100644 index 307385ce..00000000 --- a/.github/actions/issue-auto-implement/README.md +++ /dev/null @@ -1,155 +0,0 @@ -# Issue auto-implement action - -Reusable composite action for label-triggered issue automation: assess (request more info or implement), implement-verify loop, then create PR or iterate on an existing PR after review. - -## How to use (quick start) - -1. **Workflow** — Ensure `.github/workflows/issue-auto-implement.yml` exists and calls this action (see the workflow in this repo for the exact `on:` and `uses:`). If implement might change workflow files, see [CI/CD](#cicd-what-you-need-to-run-this-workflow) for push permission requirements. -2. **Secrets and variables** — In the repo: Settings → Secrets and variables → Actions. Add secret **`AUTO_IMPLEMENT_ANTHROPIC_API_KEY`** (Anthropic API key). Optionally add **`AUTO_IMPLEMENT_GITHUB_PUSH_TOKEN`** (a PAT with `repo` scope) so CI checks run on bot-created PRs (see [CI checks on bot-created PRs](#ci-checks-on-bot-created-prs)). For who can trigger, set **one** of: **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION`** (e.g. `push` or `maintain`; works with default token) or **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`** (e.g. `org/team`; token needs `read:org`). -3. **Trigger label** — Create the labels once so you can add them to issues. Either run the **Issue auto-implement setup** workflow (Actions → Issue auto-implement setup → Run workflow), which creates `automation/auto-implement`, `automation/needs-info`, and `automation/pr-created`; or create the trigger label **`automation/auto-implement`** manually in the repo (Settings or Issues → Labels). The main action also ensures these labels exist when it runs, but the trigger label must exist before you can add it to an issue. -4. **Trigger** — On an issue, add the label `automation/auto-implement`. The workflow runs: it assesses the issue (request more info vs implement), and if implement, runs the Claude Code CLI and opens a PR. You can also comment on the issue (to add context and re-trigger) or review the PR (to iterate). - -## CI checks on bot-created PRs - -By default, PRs created with `GITHUB_TOKEN` do not trigger `pull_request` workflows (a GitHub restriction to prevent recursive runs). To get CI checks on bot-created PRs, set the optional **`push_token`** input to a PAT or GitHub App installation token. The action uses this token for `git push` and PR creation, so GitHub sees events from a non-Actions identity and triggers all `pull_request` workflows normally. Accepted token types: - -- **Personal Access Token (classic)** — `repo` scope -- **Personal Access Token (fine-grained)** — `contents: write` + `pull-requests: write` permissions -- **GitHub App installation token** — same permissions (e.g. via `actions/create-github-app-token`) - -If `push_token` is not set, the action falls back to `github_token` and CI workflows will not trigger automatically on bot PRs. - -## Extra workflow runs when the action adds labels - -The workflow is triggered by `issues.labeled`. When this action adds a label (e.g. `automation/needs-info` or `automation/pr-created`), GitHub sends a new `issues.labeled` event, so **another workflow run is started**. The job only runs when the label added is **`automation/auto-implement`** (see the workflow’s `if:`), so those extra runs **skip the job** and do no work. You will see multiple runs per issue; only the runs triggered by the trigger label (or by comment/PR review) actually execute the action. GitHub does not support filtering `on: issues.labeled` by label name, so this behavior is expected. - -## Usage (reference) - -Used by `.github/workflows/issue-auto-implement.yml`. Requires `anthropic_api_key` (e.g. from repo secret `AUTO_IMPLEMENT_ANTHROPIC_API_KEY`), one of `github_allowed_trigger_min_permission` or `github_allowed_trigger_team` (repo variables), and `github_token` from the workflow. - -## Inputs - -| Input | Required | Default | Description | -|-------|----------|---------|-------------| -| `anthropic_api_key` | Yes | - | Claude API key. Set via repo secret `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` so multiple actions can use different keys. | -| `github_token` | Yes | - | Token (contents, issues, pull-requests; read:org only if using team check) | -| `context_files` | No | AGENTS.md,REFERENCE.md | Comma-separated paths for assessment context | -| `assessment_reference_issue` | No | 192 | Reference issue number for "enough information" | -| `label_prefix` | No | automation | Prefix for labels (e.g. automation/auto-implement) | -| `verify_commands` | No | go test ./... | Commands run for verification | -| `max_implement_retries` | No | 3 | Max retries on verify failure (cap 5) | -| `github_allowed_trigger_team` | No* | - | Team slug (e.g. org/team); only members can trigger. Repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`. Ignored if min_permission is set. Token needs read:org. | -| `github_allowed_trigger_min_permission` | No* | - | Require actor has at least this repo permission: triage, push, maintain, or admin. Repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION`. Works with default GITHUB_TOKEN. | -| `push_token` | No | - | PAT or GitHub App installation token for git push and PR creation. When set, GitHub triggers `pull_request` workflows on bot PRs. Falls back to `github_token`. | -| `post_pr_comment` | No | false | When true, post a comment on the issue linking to the new PR when one is created. | - -*One of `github_allowed_trigger_min_permission` or `github_allowed_trigger_team` must be set (via repo variables). - -Secrets and variables use an action-specific prefix (e.g. `AUTO_IMPLEMENT_`) so each action can have its own keys/variables and it's clear which workflow uses which. This also avoids clashing with platform-reserved names (e.g. `GITHUB_*`). - -## CI/CD: what you need to run this workflow - -To use this action in GitHub Actions: - -1. **Workflow** — Call the action from a workflow (e.g. `.github/workflows/issue-auto-implement.yml`) on `issues.labeled`, `issue_comment`, `pull_request_review`, and/or `pull_request_review_comment`. The job needs `contents: write`, `issues: write`, `pull-requests: write`. If the implement step may edit files under `.github/workflows/`, GitHub may reject the push; the workflow syntax has no `workflows` permission key. Enable **Settings → Actions → General → Allow GitHub Actions to create and approve pull requests** (or use a PAT with appropriate scope) so the run can push workflow file changes. -2. **Secrets** — Add **`AUTO_IMPLEMENT_ANTHROPIC_API_KEY`** (repo secret). Used for the assess step and passed to the Claude Code CLI in the implement step. Optionally add **`AUTO_IMPLEMENT_GITHUB_PUSH_TOKEN`** (a PAT with `repo` scope, or fine-grained with `contents: write` + `pull-requests: write`) so CI checks run on bot-created PRs. -3. **Variables (trigger gate)** — Set **one** of: - - **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION`** (repo variable): `triage`, `push`, `maintain`, or `admin`. Only users with at least this repo permission can trigger. Works with default `GITHUB_TOKEN`. - - **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`** (repo variable): org/team slug (e.g. `org/team-name`). Only team members can trigger. Token must have `read:org` (use a PAT if `GITHUB_TOKEN` lacks it). -4. **Token** — Pass `github_token` (e.g. `secrets.GITHUB_TOKEN`). If using the team check, the token needs `read:org`; the permission check works with the default token. -5. **Implement in CI** — The action installs the Claude Code CLI (`@anthropic-ai/claude-code`) when the assess outcome is `implement`, so the workflow does not need to install it. Implement runs in the repo with Read/Edit/Bash; the CLI uses `AUTO_IMPLEMENT_ANTHROPIC_API_KEY`. - -No other setup is required. Optionally set `verify_commands` (default `go test ./...`) and `context_files` (default `AGENTS.md,REFERENCE.md`) to match your repo. - -## Secrets and variables (repo setup) - -- **`AUTO_IMPLEMENT_ANTHROPIC_API_KEY`** (repo secret) — Claude API key for the assess and implement steps. Add under Settings → Secrets and variables → Actions. -- **`AUTO_IMPLEMENT_GITHUB_PUSH_TOKEN`** (repo secret, optional) — PAT or GitHub App installation token for git push and PR creation. When set, GitHub triggers `pull_request` workflows on bot PRs so CI checks appear. See [CI checks on bot-created PRs](#ci-checks-on-bot-created-prs) for accepted token types. -- **Trigger gate (set one):** - - **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION`** (repo variable) — Require the triggering user to have at least this repo permission: `triage`, `push`, `maintain`, or `admin`. Works with the default `GITHUB_TOKEN`. Add under Settings → Secrets and variables → Actions → Variables. - - **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`** (repo variable) — GitHub Team slug (e.g. `org/team-name`) whose members may trigger. The first step checks `github.actor` against this team. The token needs `read:org`; if `GITHUB_TOKEN` lacks it, use a PAT and pass it as `github_token`. - -## Triggers - -- **issues.labeled** — prefixed trigger label (e.g. `automation/auto-implement`) -- **issue_comment.created** — on an issue with that label (redirect to PR if PR exists) -- **pull_request_review.submitted** / **pull_request_review_comment.created** — PR from automation branch or with "Closes #N" - -## Labels - -The action ensures these labels exist (creates them if missing): `{prefix}/auto-implement`, `{prefix}/needs-info`, `{prefix}/pr-created`. - -## Diagnosing run failures (exit code 1) - -When a run fails, open the job log and check the **Implement and verify (loop)** step: - -- **`Error: spawnSync claude ETIMEDOUT`** — The Claude Code CLI hit the timeout in `implement.ts` (default 35 minutes). The runner killed the subprocess before Claude finished. Increase the timeout in `assess/src/implement.ts` (e.g. to 35–40 min) or simplify the issue scope. The issue comment will say "verification failed" but the real cause is implement timeout. -- **Other failure right after `npx tsx src/implement.ts`** — The implement script exited 1. Common causes: Claude Code CLI exited non-zero (error) or missing env (e.g. fetch issue failed). Check stderr for the exact error. -- **"Verification failed (attempt 1 of N)" and eventually "Verification failed after N attempts"** — The verify command (e.g. `go test -short ./...`) failed on every attempt. Fix the failing tests or adjust `verify_commands` in the workflow. - -When the issue is **already implemented** and Claude correctly writes only `.comment_body` (no code changes), the loop skips verification and exits success so the run completes and a PR/comment is posted. - -## Testing - -From the repo root, run the assess script tests: - -```bash -cd .github/actions/issue-auto-implement/assess && npm ci && npm test -``` - -CI runs these in `.github/workflows/issue-auto-implement-test.yml` when you push or open a PR that touches this action. - -**Integration tests (Claude API):** Tests in `assess/test/integration/` call the real Anthropic API. They do not run with `npm test`. From the assess directory, run `npm run test:integration` (requires `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` in `.env` or env). Unit tests live in `assess/test/unit/`; shared fixtures in `assess/test/fixtures/`. You can add integration tests to CI later with the secret configured. - -## Local runs (Claude) - -Scripts load a **local `.env`** file so you don't have to pass secrets on the command line. They look for `.env` in (1) the action root (`.github/actions/issue-auto-implement/.env`) and (2) the current working directory (e.g. `assess/.env`). Later overrides earlier; shell env still wins. - -### Env vars (local) - -| Variable | Required for | How to get it | -|----------|--------------|----------------| -| `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` | Assess, Implement | [Anthropic console](https://console.anthropic.com/) → API keys. Assess uses it directly; implement passes it to Claude Code CLI (`claude` on PATH). | -| `GITHUB_TOKEN` | Implement; optional for Assess | `gh auth token`, or a PAT with `repo`, `read:org`. | -| `GITHUB_REPOSITORY` | Implement | `owner/repo` (e.g. `hookdeck/hookdeck-cli`). | -| `ISSUE_NUMBER` | Implement | The issue number to implement. | -| `GITHUB_EVENT_PATH` | Assess (when not using fixture) | Path to event JSON; for fixture: `./fixtures/issue-labeled.json`. | -| `GITHUB_EVENT_NAME` | Assess | e.g. `issues` or `issue_comment`. | -| `GITHUB_WORKSPACE` | Optional | Repo root; default inferred from cwd when run from `assess/`. | -| `CONTEXT_FILES` | Optional | Comma-separated paths (relative to repo root) for Claude context. | -| `VERIFICATION_NOTES` | Optional (Implement) | Notes from assess step. | -| `REVIEW_FEEDBACK` | Optional (Implement) | When the run was triggered by a PR review or comment on a PR, the action passes the review/comment text so the implement step can address it (e.g. "add unit and acceptance tests"). | -| `PREVIOUS_VERIFY_OUTPUT` | Optional (Implement retries) | Previous verify failure output. | - -### One-time setup: `.env` - -1. From the action root: `cp .env.example .env` -2. Edit `.env` and set at least `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` and (for implement) `GITHUB_TOKEN`. Optionally run `./scripts/setup-local-env.sh --with-gh` to fill `GITHUB_TOKEN` from `gh auth token`; use `--template-only` to only create `.env` from `.env.example`. - -### Assess (issue → implement vs request_info) - -Uses a fixture as the GitHub event. Claude decides whether to `implement` or `request_info`; output is JSON to stdout. - -```bash -cd .github/actions/issue-auto-implement/assess -npm run assess:fixture -``` - -With `.env` in place, no need to pass the key on the command line. Optional: set `GITHUB_TOKEN` and `GITHUB_REPOSITORY` to exercise redirect-to-PR and fetch-comments. **Logging:** The script always logs Claude's raw response to stderr (the JSON decision and any surrounding text) and a one-line summary (e.g. `Assess: action=implement issue_number=215 review_feedback=52 chars`), so CI logs show what was decided and what the model returned. Set `ASSESS_DEBUG=1` to also log the full prompt (issue, comments, context files) when debugging. Other fixtures: `GITHUB_EVENT_PATH=./test/fixtures/issue-comment.json GITHUB_EVENT_NAME=issue_comment npx tsx src/index.ts`. - -### Implement (issue → Claude Code CLI → files on disk) - -Fetches the issue from the GitHub API, then runs **Claude Code CLI** in the repo (`claude` on PATH with Read/Edit/Bash). The CLI implements the issue in-repo and writes commit/PR meta files. Use a branch you can discard or reset. Requires Claude Code CLI installed and `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` set (passed to the CLI). - -```bash -cd .github/actions/issue-auto-implement/assess -npm run implement:issue -``` - -With `.env` set (e.g. `ISSUE_NUMBER`, `GITHUB_REPOSITORY`, `GITHUB_TOKEN`, `AUTO_IMPLEMENT_ANTHROPIC_API_KEY`), no need to pass them inline. Override any var on the command line if needed (e.g. `ISSUE_NUMBER=42 npm run implement:issue`). Then from the repo root inspect `git status` and the commit message at `.github/actions/issue-auto-implement/.commit_msg`. Optionally set `VERIFICATION_NOTES`, `REVIEW_FEEDBACK` (reviewer or PR comment text to address), and `CONTEXT_FILES`. - -For implementation details and verification steps, see `AGENTS.md`. - -### Local run against a real issue (no workflow events) - -To test the full flow locally against a real GitHub issue and create a PR, use the **local assess** script (fetches the issue from the API and runs the same assess logic). With **APPLY=1** the script applies the outcome: posts the request-for-more-info or redirect comment on the issue, or runs implement and push (creates/updates the PR). When the outcome is implement, the script creates or reuses a **worktree** at `.worktrees/auto-implement-issue-` so your current branch is left untouched; implement runs there, then commit/push/PR is done in TypeScript (`assess/src/push-and-open-pr.ts`). The workflow does not need to run; you trigger each step locally and optionally pass **COMMENT_BODY** (after you add a comment on the issue) or **REVIEW_BODY** (after you review the PR). diff --git a/.github/actions/issue-auto-implement/action.yml b/.github/actions/issue-auto-implement/action.yml deleted file mode 100644 index d5b17bdb..00000000 --- a/.github/actions/issue-auto-implement/action.yml +++ /dev/null @@ -1,359 +0,0 @@ -name: 'Issue auto-implement' -description: 'Assess labeled issues (or PR reviews), request more info or implement with verify loop; create PR or iterate on existing PR.' -inputs: - anthropic_api_key: - description: 'API key for Claude (assessment uses it; implement passes it to Claude Code CLI as AUTO_IMPLEMENT_ANTHROPIC_API_KEY)' - required: true - github_token: - description: 'GitHub token (contents, issues, pull-requests, read:org)' - required: true - context_files: - description: 'Comma-separated paths to repo files to include in assessment (e.g. AGENTS.md, REFERENCE.md)' - required: false - default: 'AGENTS.md,REFERENCE.md' - assessment_reference_issue: - description: 'Reference issue number for "enough information" example (e.g. 192)' - required: false - default: '192' - label_prefix: - description: 'Prefix for automation labels (e.g. automation/auto-implement, automation/needs-info, automation/pr-created)' - required: false - default: 'automation' - verify_commands: - description: 'Shell commands to run for verification. Should run tests and ensure the application builds (e.g. go test ./... && go build ./..., or npm test && npm run build). Repo-specific.' - required: false - default: 'go test ./...' - max_implement_retries: - description: 'Max retries for implement step on verify failure (default 3, max 5)' - required: false - default: '3' - github_allowed_trigger_team: - description: 'GitHub Team slug whose members may trigger (e.g. org/team). Requires token with read:org. Set via repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM. Ignored if github_allowed_trigger_min_permission is set.' - required: false - github_allowed_trigger_min_permission: - description: 'Alternative to team check: require actor has at least this repo permission (triage, push, maintain, admin). Works with default GITHUB_TOKEN. Set via repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION.' - required: false - push_token: - description: 'Optional PAT or GitHub App token for git push and PR creation. When provided, push and PR-creation use this token so GitHub triggers pull_request workflows (GITHUB_TOKEN does not trigger them). Falls back to github_token.' - required: false - post_pr_comment: - description: 'When true, post a comment on the issue linking to the new PR when one is created' - required: false - default: 'false' -runs: - using: 'composite' - steps: - - name: Check allowed trigger (permission or team) - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.github_token }} - REPO: ${{ github.repository }} - ACTOR: ${{ github.actor }} - MIN_PERM: ${{ inputs.github_allowed_trigger_min_permission }} - TEAM: ${{ inputs.github_allowed_trigger_team }} - run: | - # Option A: Require minimum repo permission (works with default GITHUB_TOKEN, no read:org) - if [ -n "$MIN_PERM" ]; then - case "$MIN_PERM" in - triage|push|maintain|admin) ;; - *) echo "::error::github_allowed_trigger_min_permission must be one of: triage, push, maintain, admin"; exit 1 ;; - esac - RESP=$(curl -s -w "\n%{http_code}" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/collaborators/$ACTOR/permission") - HTTP=$(echo "$RESP" | tail -n1) - BODY=$(echo "$RESP" | sed '$d') - if [ "$HTTP" != "200" ]; then - echo "::error::Actor $ACTOR has no collaborator permission on this repo (HTTP $HTTP). Only users with at least $MIN_PERM can trigger." - exit 1 - fi - PERM=$(echo "$BODY" | jq -r '.permission // "read"') - # Order: read/pull < triage < push < maintain < admin - level() { case "$1" in read|pull) echo 1;; triage) echo 2;; push) echo 3;; maintain) echo 4;; admin) echo 5;; *) echo 0;; esac; } - ACTOR_LEVEL=$(level "$PERM") - MIN_LEVEL=$(level "$MIN_PERM") - if [ "$ACTOR_LEVEL" -lt "$MIN_LEVEL" ]; then - echo "::error::Actor $ACTOR has permission $PERM; at least $MIN_PERM is required to trigger this workflow." - exit 1 - fi - echo "Actor $ACTOR has permission $PERM (>= $MIN_PERM)." - exit 0 - fi - # Option B: Require team membership (token needs read:org) - if [ -z "$TEAM" ]; then - echo "::error::Set one of: repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION (e.g. push) or AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM (e.g. org/team)." - exit 1 - fi - ORG="${TEAM%/*}" - SLUG="${TEAM#*/}" - HTTP=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/orgs/${ORG}/teams/${SLUG}/members/$ACTOR") - if [ "$HTTP" != "204" ]; then - echo "::error::Actor $ACTOR is not in team $TEAM (HTTP $HTTP). Only team members can trigger. Token may need read:org." - exit 1 - fi - echo "Actor $ACTOR is in team $TEAM." - - name: Ensure labels exist - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.github_token }} - REPO: ${{ github.repository }} - LABEL_PREFIX: ${{ inputs.label_prefix }} - run: | - for LABEL in auto-implement needs-info pr-created; do - NAME="${LABEL_PREFIX}/${LABEL}" - curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/labels" \ - -d "{\"name\":\"$NAME\",\"color\":\"ededed\"}" | grep -q 201 || true - done - - name: Install assess script dependencies - shell: bash - run: cd .github/actions/issue-auto-implement/assess && npm ci --omit=dev - - id: assess - name: Run assess - shell: bash - env: - AUTO_IMPLEMENT_ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }} - GITHUB_TOKEN: ${{ inputs.github_token }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_WORKSPACE: ${{ github.workspace }} - ASSESSMENT_REFERENCE_ISSUE: ${{ inputs.assessment_reference_issue }} - CONTEXT_FILES: ${{ inputs.context_files }} - run: | - RESULT=$(cd .github/actions/issue-auto-implement/assess && npx tsx src/index.ts) - echo "action=$(echo "$RESULT" | jq -r '.action')" >> $GITHUB_OUTPUT - echo "issue_number=$(echo "$RESULT" | jq -r '.issue_number // empty')" >> $GITHUB_OUTPUT - echo "pr_url=$(echo "$RESULT" | jq -r '.pr_url // empty')" >> $GITHUB_OUTPUT - echo "comment_body<> $GITHUB_OUTPUT - echo "$RESULT" | jq -r '.comment_body // empty' >> $GITHUB_OUTPUT - echo "BODY_EOF" >> $GITHUB_OUTPUT - echo "verification_notes<> $GITHUB_OUTPUT - echo "$RESULT" | jq -r '.verification_notes // empty' >> $GITHUB_OUTPUT - echo "NOTES_EOF" >> $GITHUB_OUTPUT - echo "review_feedback<> $GITHUB_OUTPUT - echo "$RESULT" | jq -r '.review_feedback // empty' >> $GITHUB_OUTPUT - echo "REVIEW_EOF" >> $GITHUB_OUTPUT - - name: Handle redirect to PR - if: steps.assess.outputs.action == 'redirect_to_pr' - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.github_token }} - REPO: ${{ github.repository }} - ISSUE_NUMBER: ${{ steps.assess.outputs.issue_number }} - PR_URL: ${{ steps.assess.outputs.pr_url }} - run: | - BODY="A PR is open for this issue. Please review and comment on the PR: $PR_URL" - curl -s -X POST \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/issues/$ISSUE_NUMBER/comments" \ - -d "{\"body\":$(echo "$BODY" | jq -Rs .)}" - echo "Redirected user to PR. Exiting." - exit 0 - - name: Handle request more info - if: steps.assess.outputs.action == 'request_info' - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.github_token }} - REPO: ${{ github.repository }} - ISSUE_NUMBER: ${{ steps.assess.outputs.issue_number }} - LABEL_PREFIX: ${{ inputs.label_prefix }} - run: | - COMMENT_BODY="${{ steps.assess.outputs.comment_body }}" - curl -s -X POST \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/issues/$ISSUE_NUMBER/comments" \ - -d "$(jq -n --arg b "$COMMENT_BODY" '{body: $b}')" - NEEDS_LABEL="${LABEL_PREFIX}/needs-info" - curl -s -X POST \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/issues/$ISSUE_NUMBER/labels" \ - -d "{\"labels\":[\"$NEEDS_LABEL\"]}" - echo "Posted request for more info. Exiting." - exit 0 - - name: Checkout branch for implement - if: steps.assess.outputs.action == 'implement' - shell: bash - env: - ISSUE_NUMBER: ${{ steps.assess.outputs.issue_number }} - GITHUB_ACTOR: ${{ github.actor }} - PUSH_TOKEN: ${{ inputs.push_token || inputs.github_token }} - REPO: ${{ github.repository }} - run: | - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - # Use push_token for push operations so GitHub triggers pull_request workflows - git remote set-url origin "https://x-access-token:${PUSH_TOKEN}@github.com/${REPO}.git" - BRANCH="auto-implement-issue-${ISSUE_NUMBER}" - git fetch origin main - if git show-ref --verify --quiet refs/remotes/origin/"$BRANCH"; then - git checkout "$BRANCH" - git merge origin/main --no-edit - else - git checkout -b "$BRANCH" origin/main - fi - - name: Install Claude Code CLI - if: steps.assess.outputs.action == 'implement' - shell: bash - run: npm install -g @anthropic-ai/claude-code - - id: implement_verify_loop - name: Implement and verify (loop) - if: steps.assess.outputs.action == 'implement' - shell: bash - env: - ISSUE_NUMBER: ${{ steps.assess.outputs.issue_number }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_TOKEN: ${{ inputs.github_token }} - GITHUB_WORKSPACE: ${{ github.workspace }} - AUTO_IMPLEMENT_ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }} - VERIFICATION_NOTES: ${{ steps.assess.outputs.verification_notes }} - REVIEW_FEEDBACK: ${{ steps.assess.outputs.review_feedback }} - CONTEXT_FILES: ${{ inputs.context_files }} - IMPLEMENT_COMMIT_MSG_FILE: ${{ github.workspace }}/.github/actions/issue-auto-implement/.commit_msg - MAX_RETRIES: ${{ inputs.max_implement_retries }} - VERIFY_CMDS: ${{ inputs.verify_commands }} - run: | - set -e - MAX=${MAX_RETRIES:-3} - COMMIT_MSG_FILE=".github/actions/issue-auto-implement/.commit_msg" - BRANCH="auto-implement-issue-${ISSUE_NUMBER}" - VERIFY_FAILURE="" - CHANGES_PUSHED="false" - for i in $(seq 1 "$MAX"); do - echo "Implement–verify attempt $i of $MAX" - export PREVIOUS_VERIFY_OUTPUT="$VERIFY_FAILURE" - cd .github/actions/issue-auto-implement/assess && npx tsx src/implement.ts - cd "$GITHUB_WORKSPACE" - git add -A - git reset -- "$COMMIT_MSG_FILE" ".github/actions/issue-auto-implement/.pr_title" ".github/actions/issue-auto-implement/.pr_body" ".github/actions/issue-auto-implement/.comment_body" 2>/dev/null || true - if ! git diff --staged --quiet; then - git commit -F "$COMMIT_MSG_FILE" - rm -f "$COMMIT_MSG_FILE" - git push -u origin "$BRANCH" - CHANGES_PUSHED="true" - else - echo "No changes to commit (attempt $i)." - # When Claude wrote only .comment_body (e.g. "already implemented"), skip verify and succeed - if [ -f ".github/actions/issue-auto-implement/.comment_body" ]; then - echo "No code changes and .comment_body present; skipping verify and exiting success." - PR_DIR=".github/actions/issue-auto-implement" - if [ -f "$PR_DIR/.pr_title" ]; then echo "pr_title<<__HOOKDECK_PR_TITLE_END_8a3f2b1c9d4e__" >> $GITHUB_OUTPUT; cat "$PR_DIR/.pr_title" >> $GITHUB_OUTPUT; echo "__HOOKDECK_PR_TITLE_END_8a3f2b1c9d4e__" >> $GITHUB_OUTPUT; else echo "pr_title=Implement issue #${ISSUE_NUMBER}" >> $GITHUB_OUTPUT; fi - if [ -f "$PR_DIR/.pr_body" ]; then echo "pr_body<<__HOOKDECK_PR_BODY_END_8a3f2b1c9d4e__" >> $GITHUB_OUTPUT; cat "$PR_DIR/.pr_body" >> $GITHUB_OUTPUT; echo "__HOOKDECK_PR_BODY_END_8a3f2b1c9d4e__" >> $GITHUB_OUTPUT; else echo "pr_body=Closes #${ISSUE_NUMBER}" >> $GITHUB_OUTPUT; fi - echo "verified=true" >> $GITHUB_OUTPUT - echo "changes_pushed=$CHANGES_PUSHED" >> $GITHUB_OUTPUT - exit 0 - fi - fi - VERIFY_OUTPUT=$(eval "$VERIFY_CMDS" 2>&1); VERIFY_EXIT=$? - if [ "$VERIFY_EXIT" -eq 0 ]; then - # Pass PR meta to Create PR step via outputs (files are Claude handoff only; do not re-read in next step) - PR_DIR=".github/actions/issue-auto-implement" - if [ -f "$PR_DIR/.pr_title" ]; then echo "pr_title<<__HOOKDECK_PR_TITLE_END_8a3f2b1c9d4e__" >> $GITHUB_OUTPUT; cat "$PR_DIR/.pr_title" >> $GITHUB_OUTPUT; echo "__HOOKDECK_PR_TITLE_END_8a3f2b1c9d4e__" >> $GITHUB_OUTPUT; else echo "pr_title=Implement issue #${ISSUE_NUMBER}" >> $GITHUB_OUTPUT; fi - if [ -f "$PR_DIR/.pr_body" ]; then echo "pr_body<<__HOOKDECK_PR_BODY_END_8a3f2b1c9d4e__" >> $GITHUB_OUTPUT; cat "$PR_DIR/.pr_body" >> $GITHUB_OUTPUT; echo "__HOOKDECK_PR_BODY_END_8a3f2b1c9d4e__" >> $GITHUB_OUTPUT; else echo "pr_body=Closes #${ISSUE_NUMBER}" >> $GITHUB_OUTPUT; fi - echo "verified=true" >> $GITHUB_OUTPUT - echo "changes_pushed=$CHANGES_PUSHED" >> $GITHUB_OUTPUT - exit 0 - fi - VERIFY_FAILURE="$VERIFY_OUTPUT" - echo "::warning::Verification failed (attempt $i of $MAX)" - echo "
Verify command output" - echo '```' - printf '%s\n' "$VERIFY_OUTPUT" - echo '```' - echo "
" - done - echo "verified=false" >> $GITHUB_OUTPUT - echo "::error::Verification failed after $MAX attempts" - exit 1 - - name: Comment on issue when verify retries exhausted - if: failure() && steps.assess.outputs.action == 'implement' && steps.implement_verify_loop.outcome == 'failure' - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.github_token }} - REPO: ${{ github.repository }} - ISSUE_NUMBER: ${{ steps.assess.outputs.issue_number }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - MAX_RETRIES: ${{ inputs.max_implement_retries }} - run: | - BODY="The auto-implement run could not complete. See the [workflow run]($RUN_URL) for logs. This can happen if the Claude Code CLI timed out or failed, or if verification (e.g. \`go test\`) failed after ${MAX_RETRIES} attempts. Re-trigger by adding a comment or re-applying the label." - curl -s -X POST \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/issues/$ISSUE_NUMBER/comments" \ - -d "$(jq -n --arg b "$BODY" '{body: $b}')" - echo "Posted comment on issue #$ISSUE_NUMBER (verify exhausted)" - - name: Comment on PR (review iteration) - if: steps.assess.outputs.action == 'implement' && steps.implement_verify_loop.outcome == 'success' && (github.event_name == 'pull_request_review' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'issue_comment' && github.event.issue.pull_request)) - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.github_token }} - REPO: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} - HEAD_REF: ${{ github.event.pull_request.head.ref || format('auto-implement-issue-{0}', steps.assess.outputs.issue_number) }} - CHANGES_PUSHED: ${{ steps.implement_verify_loop.outputs.changes_pushed }} - run: | - if [ "$CHANGES_PUSHED" = "true" ]; then - BODY="Addressed review feedback. New commit(s) pushed; verification passed." - else - COMMENT_BODY_FILE=".github/actions/issue-auto-implement/.comment_body" - if [ -f "$COMMENT_BODY_FILE" ]; then - BODY="$(cat "$COMMENT_BODY_FILE"). Verification passed." - else - BODY="Thanks for the suggestion. We reviewed it and are keeping the current implementation; no code changes were made. Verification passed." - fi - fi - curl -s -X POST \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/issues/$PR_NUMBER/comments" \ - -d "$(jq -n --arg b "$BODY" '{body: $b}')" - echo "Posted comment on PR #$PR_NUMBER" - - name: Create PR - if: steps.assess.outputs.action == 'implement' && steps.implement_verify_loop.outcome == 'success' && github.event_name != 'pull_request_review' && github.event_name != 'pull_request_review_comment' && !(github.event_name == 'issue_comment' && github.event.issue.pull_request) - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.github_token }} - PUSH_TOKEN: ${{ inputs.push_token || inputs.github_token }} - REPO: ${{ github.repository }} - ISSUE_NUMBER: ${{ steps.assess.outputs.issue_number }} - LABEL_PREFIX: ${{ inputs.label_prefix }} - POST_PR_COMMENT: ${{ inputs.post_pr_comment }} - PR_TITLE: ${{ steps.implement_verify_loop.outputs.pr_title }} - PR_BODY: ${{ steps.implement_verify_loop.outputs.pr_body }} - run: | - BRANCH="auto-implement-issue-${ISSUE_NUMBER}" - TITLE="${PR_TITLE:-Implement issue #${ISSUE_NUMBER}}" - BODY="${PR_BODY:-Closes #${ISSUE_NUMBER}}" - PAYLOAD=$(jq -n --arg t "$TITLE" --arg b "$BODY" --arg h "$BRANCH" '{title: $t, body: $b, head: $h, base: "main"}') - # Use push_token for PR creation so GitHub triggers pull_request workflows - PR_JSON=$(curl -s -X POST \ - -H "Authorization: Bearer $PUSH_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/pulls" \ - -d "$PAYLOAD") - PR_URL=$(echo "$PR_JSON" | jq -r '.html_url // empty') - if [ -n "$PR_URL" ]; then - NEEDS_LABEL="${LABEL_PREFIX}/pr-created" - curl -s -X POST \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/issues/$ISSUE_NUMBER/labels" \ - -d "{\"labels\":[\"$NEEDS_LABEL\"]}" - echo "Created PR: $PR_URL" - if [ "$POST_PR_COMMENT" = "true" ]; then - COMMENT_BODY="Opened PR: $PR_URL" - curl -s -X POST \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/issues/$ISSUE_NUMBER/comments" \ - -d "$(jq -n --arg b "$COMMENT_BODY" '{body: $b}')" - echo "Posted comment on issue #$ISSUE_NUMBER" - fi - fi diff --git a/.github/actions/issue-auto-implement/assess/package-lock.json b/.github/actions/issue-auto-implement/assess/package-lock.json deleted file mode 100644 index 63c4122d..00000000 --- a/.github/actions/issue-auto-implement/assess/package-lock.json +++ /dev/null @@ -1,2252 +0,0 @@ -{ - "name": "issue-auto-implement-assess", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "issue-auto-implement-assess", - "dependencies": { - "@anthropic-ai/sdk": "^0.32.1", - "dotenv": "^16.4.5" - }, - "devDependencies": { - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "vitest": "^4.1.0" - } - }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.32.1.tgz", - "integrity": "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg==", - "license": "MIT", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - } - }, - "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, - "node_modules/@emnapi/core": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", - "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", - "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", - "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@oxc-project/runtime": { - "version": "0.115.0", - "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz", - "integrity": "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.115.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz", - "integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz", - "integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz", - "integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz", - "integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz", - "integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz", - "integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz", - "integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz", - "integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz", - "integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz", - "integrity": "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.4" - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", - "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.0", - "@vitest/utils": "4.1.0", - "chai": "^6.2.2", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", - "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.0", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", - "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", - "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.0", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", - "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.0", - "@vitest/utils": "4.1.0", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", - "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", - "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.0", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "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/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "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/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/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "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/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/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/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "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/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-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, - "license": "MIT" - }, - "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/esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/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/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "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/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-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "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/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "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-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/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/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/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/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/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/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-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/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/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/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.9.tgz", - "integrity": "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.115.0", - "@rolldown/pluginutils": "1.0.0-rc.9" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.9", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", - "@rolldown/binding-darwin-x64": "1.0.0-rc.9", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "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/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "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/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "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/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "license": "MIT" - }, - "node_modules/vite": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0.tgz", - "integrity": "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/runtime": "0.115.0", - "lightningcss": "^1.32.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.9", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.0.0-alpha.31", - "esbuild": "^0.27.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", - "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.0", - "@vitest/mocker": "4.1.0", - "@vitest/pretty-format": "4.1.0", - "@vitest/runner": "4.1.0", - "@vitest/snapshot": "4.1.0", - "@vitest/spy": "4.1.0", - "@vitest/utils": "4.1.0", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.0", - "@vitest/browser-preview": "4.1.0", - "@vitest/browser-webdriverio": "4.1.0", - "@vitest/ui": "4.1.0", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "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/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - } - } -} diff --git a/.github/actions/issue-auto-implement/assess/package.json b/.github/actions/issue-auto-implement/assess/package.json deleted file mode 100644 index 4348fd5f..00000000 --- a/.github/actions/issue-auto-implement/assess/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "issue-auto-implement-assess", - "private": true, - "type": "module", - "scripts": { - "test": "vitest run", - "test:integration": "vitest run --config vitest.integration.config.ts", - "start": "tsx src/index.ts", - "assess:fixture": "GITHUB_EVENT_PATH=./test/fixtures/issue-labeled.json GITHUB_EVENT_NAME=issues tsx src/index.ts", - "assess:local": "tsx src/run-local-assess.ts", - "implement:issue": "tsx src/implement.ts" - }, - "dependencies": { - "@anthropic-ai/sdk": "^0.32.1", - "dotenv": "^16.4.5" - }, - "devDependencies": { - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "vitest": "^4.1.0" - } -} diff --git a/.github/actions/issue-auto-implement/assess/src/implement.ts b/.github/actions/issue-auto-implement/assess/src/implement.ts deleted file mode 100644 index 5a3e5139..00000000 --- a/.github/actions/issue-auto-implement/assess/src/implement.ts +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env -S npx tsx -/** - * Implement script: fetch issue, then run Claude Code CLI in the repo to implement it. - * - * Env: ISSUE_NUMBER, GITHUB_REPOSITORY, GITHUB_TOKEN; VERIFICATION_NOTES, GITHUB_WORKSPACE (optional), CONTEXT_FILES. - * Claude Code CLI must be on PATH. AUTO_IMPLEMENT_ANTHROPIC_API_KEY is passed to the CLI as ANTHROPIC_API_KEY. - * Writes commit message and PR meta to ACTION_DIR for push-and-open-pr. - */ - -import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs'; -import { resolve, dirname } from 'path'; -import { spawnSync } from 'child_process'; -import { config } from 'dotenv'; - -// Load .env from action root then cwd (cwd is assess/ when run from there). No-op if files missing. -config({ path: resolve(process.cwd(), '../.env') }); -config({ path: resolve(process.cwd(), '.env') }); - -// Default repo root: in CI GITHUB_WORKSPACE is set; when run from assess/ locally, cwd is assess/ so repo root is 4 levels up -const REPO_ROOT = process.env.GITHUB_WORKSPACE || resolve(process.cwd(), '../../../..'); -const ACTION_DIR = '.github/actions/issue-auto-implement'; -const COMMIT_MSG_FILE = process.env.IMPLEMENT_COMMIT_MSG_FILE || resolve(REPO_ROOT, ACTION_DIR + '/.commit_msg'); -const PR_TITLE_FILE = resolve(REPO_ROOT, ACTION_DIR + '/.pr_title'); -const PR_BODY_FILE = resolve(REPO_ROOT, ACTION_DIR + '/.pr_body'); -/** When implement makes no code changes, Claude writes the PR comment body here (no-change rationale or request for clarification). */ -const COMMENT_BODY_FILE = resolve(REPO_ROOT, ACTION_DIR + '/.comment_body'); - -async function fetchIssue(owner: string, repo: string, issueNumber: number, token: string): Promise<{ title: string; body: string }> { - const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`; - const res = await fetch(url, { - headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' }, - }); - if (!res.ok) throw new Error(`Failed to fetch issue: ${res.status}`); - const data = (await res.json()) as { title?: string; body?: string }; - return { title: data.title ?? '', body: data.body ?? '' }; -} - -function loadContextFiles(): string { - const contextFiles = process.env.CONTEXT_FILES || ''; - if (!contextFiles.trim()) return ''; - const paths = contextFiles.split(',').map((s) => s.trim()).filter(Boolean); - const chunks: string[] = []; - for (const rel of paths) { - try { - const full = resolve(REPO_ROOT, rel); - const content = readFileSync(full, 'utf-8'); - chunks.push(`--- ${rel} ---\n${content}`); - } catch { - // Skip missing files - } - } - return chunks.length ? ['Repository context:', '', ...chunks].join('\n') : ''; -} - -/** - * Prompt for Claude Code CLI: implement in-repo with Read/Edit/Bash; write meta files when done. - */ -function buildClaudeCliPrompt( - issueTitle: string, - issueBody: string, - verificationNotes: string, - contextBlock: string, - previousVerifyOutput: string, - issueNumber: number, - reviewFeedback: string -): string { - const metaDir = ACTION_DIR; - const parts = [ - 'Implement this GitHub issue in the current repository. You have full access to read and edit files and run commands.', - '', - 'Rules:', - '- Only change what is necessary to implement the issue. Preserve existing exported symbols and call sites unless the issue explicitly asks to remove or replace them.', - '- Consider the broader codebase—other code may depend on the files you edit; make minimal, targeted edits and keep the public API intact.', - '- When you MAKE code changes, you MUST write three files (create the directory if needed):', - ` 1. ${metaDir}/.commit_msg — one line, conventional commit message (e.g. "fix: correct version comparison for beta").`, - ` 2. ${metaDir}/.pr_title — one-line PR title.`, - ` 3. ${metaDir}/.pr_body — markdown body: brief problem summary, then "How it was solved" or "Solution". Do NOT include "Closes #N" (it will be appended).`, - ` These files are workflow-only inputs (consumed by the action to create the commit and PR). Do NOT add or commit them to the repository.`, - '', - `- When you decide NOT to make any code changes, you MUST NOT write .commit_msg, .pr_title, or .pr_body. You MUST write ${metaDir}/.comment_body instead — one or two sentences that will be posted on the PR. Required for: (a) no-change scenarios (e.g. the feedback is a question or the current approach is preferred; thank the reviewer and briefly explain why no change), or (b) when more information is needed (e.g. "Could you clarify whether you want X or Y?"). Without .comment_body the workflow posts a generic fallback; always write it when you make no code changes so the reviewer gets a useful reply.`, - '', - 'Issue title:', - issueTitle, - '', - 'Issue body:', - issueBody, - '', - ]; - if (reviewFeedback.trim()) { - parts.push( - '', - '--- Review feedback to address (you must implement this) ---', - reviewFeedback.trim(), - '--- End review feedback ---', - '' - ); - } - if (verificationNotes) { - parts.push('Verification (run these to confirm):', verificationNotes, ''); - } - if (previousVerifyOutput.trim()) { - parts.push( - '', - 'The previous implementation was applied but verification failed. Fix based on:', - '', - '--- Verification output ---', - previousVerifyOutput.trim(), - '--- End ---', - '' - ); - } - if (contextBlock) { - parts.push('', contextBlock); - } - parts.push('', 'After implementing, write exactly one of: (A) .commit_msg, .pr_title, and .pr_body if you made code changes; or (B) .comment_body only if you made no code changes. When you make no code changes, writing .comment_body is required so the PR gets a specific reply instead of a generic one.'); - return parts.join('\n'); -} - -/** - * Run Claude Code CLI in REPO_ROOT with prompt on stdin. Throws if CLI is not found or exits non-zero. - * Uses AUTO_IMPLEMENT_ANTHROPIC_API_KEY (passed to the CLI as ANTHROPIC_API_KEY). - */ -function runClaudeCli(prompt: string): void { - const apiKey = process.env.AUTO_IMPLEMENT_ANTHROPIC_API_KEY; - if (!apiKey) { - throw new Error('AUTO_IMPLEMENT_ANTHROPIC_API_KEY must be set for the Claude Code CLI.'); - } - const env = { ...process.env, ANTHROPIC_API_KEY: apiKey }; - - const result = spawnSync( - 'claude', - ['-p', '--allowedTools', 'Read,Edit,Bash'], - { - cwd: REPO_ROOT, - input: prompt, - stdio: ['pipe', 'inherit', 'inherit'], - encoding: 'utf-8', - timeout: 35 * 60 * 1000, // 35 minutes (complex issues or "already implemented" analysis may need more than 25) - env, - } - ); - if (result.error && (result.error as NodeJS.ErrnoException).code === 'ENOENT') { - throw new Error('Claude Code CLI not found (claude not on PATH). Install it and set AUTO_IMPLEMENT_ANTHROPIC_API_KEY.'); - } - if (result.error) throw result.error; - if (result.status !== 0) { - process.exit(result.status ?? 1); - } -} - -/** Ensure commit message and PR meta files exist when Claude made code changes; skip defaults if Claude wrote .comment_body. */ -function ensureMetaFiles(issueNumber: number): void { - const metaDir = dirname(COMMIT_MSG_FILE); - if (!existsSync(metaDir)) mkdirSync(metaDir, { recursive: true }); - if (existsSync(COMMENT_BODY_FILE)) { - return; - } - if (!existsSync(COMMIT_MSG_FILE)) { - writeFileSync(COMMIT_MSG_FILE, `fix: implement issue #${issueNumber}`, 'utf-8'); - } - if (!existsSync(PR_TITLE_FILE)) { - writeFileSync(PR_TITLE_FILE, `Implement issue #${issueNumber}`, 'utf-8'); - } - if (!existsSync(PR_BODY_FILE)) { - writeFileSync(PR_BODY_FILE, `Closes #${issueNumber}`, 'utf-8'); - } -} - -async function main(): Promise { - const issueNumber = process.env.ISSUE_NUMBER; - const repo = process.env.GITHUB_REPOSITORY; - const token = process.env.GITHUB_TOKEN; - const verificationNotes = process.env.VERIFICATION_NOTES || ''; - const previousVerifyOutput = process.env.PREVIOUS_VERIFY_OUTPUT || ''; - - if (!issueNumber || !repo || !token) { - throw new Error('Missing required env: ISSUE_NUMBER, GITHUB_REPOSITORY, GITHUB_TOKEN'); - } - - const [owner, repoName] = repo.split('/'); - if (!owner || !repoName) throw new Error('Invalid GITHUB_REPOSITORY'); - - const issueNum = parseInt(issueNumber, 10); - const reviewFeedback = process.env.REVIEW_FEEDBACK || ''; - const { title, body } = await fetchIssue(owner, repoName, issueNum, token); - const contextBlock = loadContextFiles(); - - const prompt = buildClaudeCliPrompt( - title, - body, - verificationNotes, - contextBlock, - previousVerifyOutput, - issueNum, - reviewFeedback - ); - runClaudeCli(prompt); - ensureMetaFiles(issueNum); -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/.github/actions/issue-auto-implement/assess/src/index.ts b/.github/actions/issue-auto-implement/assess/src/index.ts deleted file mode 100644 index 188f84f0..00000000 --- a/.github/actions/issue-auto-implement/assess/src/index.ts +++ /dev/null @@ -1,298 +0,0 @@ -#!/usr/bin/env -S npx tsx -/** - * Assess script: read GitHub event, normalize, optionally check redirect, call Claude, output JSON. - * Output: { action: 'implement' | 'request_info' | 'redirect_to_pr', comment_body?, verification_notes?, pr_url? } - * Run: GITHUB_EVENT_PATH=... GITHUB_EVENT_NAME=... [AUTO_IMPLEMENT_ANTHROPIC_API_KEY=...] npx tsx src/index.ts - */ - -import { readFileSync } from 'fs'; -import { resolve } from 'path'; -import { config } from 'dotenv'; -import Anthropic from '@anthropic-ai/sdk'; -import { normalizeEvent, issueNumberFromPrPayload } from './normalize.js'; - -// Load .env from action root then cwd (cwd is assess/ when run from there). No-op if files missing. -config({ path: resolve(process.cwd(), '../.env') }); -config({ path: resolve(process.cwd(), '.env') }); - -const EVENT_PATH = process.env.GITHUB_EVENT_PATH || ''; -const EVENT_NAME = process.env.GITHUB_EVENT_NAME || ''; -const GITHUB_TOKEN = process.env.GITHUB_TOKEN || ''; -const ANTHROPIC_API_KEY = process.env.AUTO_IMPLEMENT_ANTHROPIC_API_KEY || ''; -const REPO = process.env.GITHUB_REPOSITORY || ''; -const CONTEXT_FILES = process.env.CONTEXT_FILES || ''; -const REPO_ROOT = process.env.GITHUB_WORKSPACE || resolve(process.cwd(), '../../..'); - -export type AssessmentOutput = { - action: 'implement' | 'request_info' | 'redirect_to_pr'; - issue_number?: number; - comment_body?: string; - verification_notes?: string; - pr_url?: string; - /** When trigger is PR review or comment on PR: exact text for implement step to address. Passed as REVIEW_FEEDBACK. */ - review_feedback?: string; -}; - -async function checkExistingPr(owner: string, repo: string, issueNumber: number): Promise<{ pr_url: string } | null> { - if (!GITHUB_TOKEN) return null; - const branch = `auto-implement-issue-${issueNumber}`; - const url = `https://api.github.com/repos/${owner}/${repo}/pulls?head=${owner}:${branch}&state=open`; - const res = await fetch(url, { - headers: { Authorization: `Bearer ${GITHUB_TOKEN}`, Accept: 'application/vnd.github+json' }, - }); - if (!res.ok) return null; - const data = (await res.json()) as { html_url?: string }[]; - const pr = data?.[0]; - return pr?.html_url ? { pr_url: pr.html_url } : null; -} - -/** Fetch PR by number; returns issue number derived from head ref or body (Closes #N). Used when issue_comment is on a PR. */ -async function issueNumberFromPrNumber( - owner: string, - repo: string, - prNumber: number, - token: string -): Promise { - const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`; - const res = await fetch(url, { - headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' }, - }); - if (!res.ok) return null; - const pr = (await res.json()) as { body?: string | null; head?: { ref?: string } }; - return issueNumberFromPrPayload(pr); -} - -type IssueComment = { body?: string; user?: { login?: string }; created_at?: string }; - -async function fetchIssueComments( - owner: string, - repo: string, - issueNumber: number, - token: string -): Promise { - const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`; - const res = await fetch(url, { - headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' }, - }); - if (!res.ok) return []; - const data = (await res.json()) as IssueComment[]; - return Array.isArray(data) ? data : []; -} - -function loadPayload(): { eventName: string; payload: unknown } { - if (!EVENT_PATH) { - throw new Error('GITHUB_EVENT_PATH is not set'); - } - const raw = readFileSync(EVENT_PATH, 'utf-8'); - const payload = JSON.parse(raw) as unknown; - const eventName = EVENT_NAME || (payload as Record)?.action ? inferEventName(payload) : ''; - if (!eventName) throw new Error('Could not determine event name (set GITHUB_EVENT_NAME or use a standard payload)'); - return { eventName, payload }; -} - -function inferEventName(payload: unknown): string { - const p = payload as Record; - if (p.issue && p.comment) return 'issue_comment'; - if (p.pull_request && p.review) return 'pull_request_review'; - if (p.pull_request && (p as { comment?: unknown }).comment) return 'pull_request_review_comment'; - if (p.issue && p.label) return 'issues'; - return ''; -} - -function loadContextFiles(): string { - if (!CONTEXT_FILES.trim()) return ''; - const paths = CONTEXT_FILES.split(',').map((s) => s.trim()).filter(Boolean); - const chunks: string[] = []; - for (const rel of paths) { - try { - const full = resolve(REPO_ROOT, rel); - const content = readFileSync(full, 'utf-8'); - chunks.push(`--- ${rel} ---\n${content}`); - } catch { - // Skip missing files (e.g. REFERENCE.md may not exist in all repos) - } - } - return chunks.length ? ['Repository context:', '', ...chunks].join('\n') : ''; -} - -function buildAssessmentPrompt( - payload: unknown, - eventName: string, - referenceIssue: string, - contextBlock: string, - issueComments: IssueComment[] = [] -): string { - const p = payload as Record; - const issue = p.issue as { title?: string; body?: string; number?: number } | undefined; - const parts: string[] = [ - 'You are assessing a GitHub issue to decide if there is enough information to implement a fix or feature.', - 'Reply with a single JSON object only, no markdown or extra text, with these keys:', - '- action: either "implement" or "request_info"', - '- comment_body: (required if action is request_info) a short message to post on the issue asking for the missing information', - '- verification_notes: (optional if action is implement) free-form notes for the implementer. Include running the test suite and ensuring the application builds; infer the repo\'s usual test and build commands from the repository context (e.g. go test ./... && go build ., npm test && npm run build, make test, etc.).', - '', - 'Issue:', - `Title: ${issue?.title ?? 'N/A'}`, - `Body: ${issue?.body ?? 'N/A'}`, - '', - `Reference example of "enough information": GitHub issue #${referenceIssue} (use similar clarity and specificity).`, - ]; - if (contextBlock) { - parts.push('', contextBlock); - } - if (issueComments.length > 0) { - parts.push( - '', - 'All issue comments (from API):', - issueComments - .map((c) => `[${c.user?.login ?? 'unknown'} @ ${c.created_at ?? 'N/A'}]: ${c.body ?? ''}`) - .join('\n\n') - ); - } - const comment = p.comment as { body?: string } | undefined; - if (comment?.body && !issueComments.some((c) => c.body === comment.body)) { - parts.push('', 'Latest event comment:', comment.body); - } - if (Array.isArray(p.comments) && p.comments.length && issueComments.length === 0) { - parts.push('', 'Comments:', JSON.stringify(p.comments, null, 2)); - } - if (eventName === 'pull_request_review' || eventName === 'pull_request_review_comment') { - const review = (p.review as { body?: string }) ?? {}; - parts.push('', 'PR review (address this feedback):', review.body ?? 'N/A'); - } - - parts.push('', 'Output only the JSON object:'); - return parts.join('\n'); -} - -// Logging: we always log Claude's raw response (the decision record; usually one short JSON blob). -// ASSESS_DEBUG=1 additionally logs the full prompt (can be large: issue, comments, context files). -const LOG_PROMPT = process.env.ASSESS_DEBUG === '1' || process.env.ASSESS_DEBUG === 'true'; - -function logAssessSummary(result: AssessmentOutput): void { - const rf = result.review_feedback?.length ?? 0; - const parts = [`action=${result.action}`, `issue_number=${result.issue_number ?? '?'}`]; - if (rf > 0) parts.push(`review_feedback=${rf} chars`); - process.stderr.write(`Assess: ${parts.join(' ')}\n`); -} - -async function callClaude(prompt: string, client?: Anthropic): Promise { - const api = client ?? new Anthropic({ apiKey: ANTHROPIC_API_KEY }); - if (!client && !ANTHROPIC_API_KEY) { - throw new Error('AUTO_IMPLEMENT_ANTHROPIC_API_KEY is not set'); - } - if (LOG_PROMPT) { - process.stderr.write('--- ASSESS PROMPT (sent to Claude) ---\n'); - process.stderr.write(prompt); - process.stderr.write('\n--- END PROMPT ---\n'); - } - const response = await api.messages.create({ - model: 'claude-sonnet-4-20250514', - max_tokens: 1024, - messages: [{ role: 'user', content: prompt }], - }); - const text = response.content?.[0]?.type === 'text' ? response.content[0].text : ''; - process.stderr.write('--- Claude response ---\n'); - process.stderr.write(text); - process.stderr.write('\n--- end response ---\n'); - const jsonMatch = text.match(/\{[\s\S]*\}/); - if (!jsonMatch) { - throw new Error('Claude did not return valid JSON: ' + text.slice(0, 200)); - } - const parsed = JSON.parse(jsonMatch[0]) as AssessmentOutput; - if (parsed.action !== 'implement' && parsed.action !== 'request_info') { - parsed.action = 'request_info'; - } - return parsed; -} - -async function main(): Promise { - const { eventName, payload } = loadPayload(); - const result = await assess(eventName, payload, { - repo: REPO, - token: GITHUB_TOKEN, - referenceIssue: process.env.ASSESSMENT_REFERENCE_ISSUE || '192', - }); - console.log(JSON.stringify(result)); -} - -/** Exported for tests: run assessment with given payload and optional mock client */ -export async function assess( - eventName: string, - payload: unknown, - opts: { - repo?: string; - token?: string; - referenceIssue?: string; - anthropicClient?: Anthropic; - contextFilesContent?: string; - } -): Promise { - const normalized = normalizeEvent(eventName, payload); - if (!normalized) throw new Error('Could not normalize event'); - - /** When issue_comment is on a PR, we fetch comments for the PR (this number); implement uses resolved issue number. */ - let commentTargetIssueNumber: number | undefined; - - if (eventName === 'issue_comment' && opts.repo && opts.token) { - const [owner, repo] = opts.repo.split('/'); - if (owner && repo) { - const p = payload as Record; - const issue = p.issue as { pull_request?: unknown } | undefined; - const commentOnPr = Boolean(issue?.pull_request); - let issueNumber = normalized.issueNumber; - if (commentOnPr) { - commentTargetIssueNumber = normalized.issueNumber; - const resolved = await issueNumberFromPrNumber(owner, repo, normalized.issueNumber, opts.token); - if (resolved != null) issueNumber = resolved; - // Comment was on the PR: do not redirect — run assess and iterate on existing branch (same issue number). - } else { - const existing = await checkExistingPr(owner, repo, issueNumber); - if (existing) return { action: 'redirect_to_pr', issue_number: issueNumber, pr_url: existing.pr_url }; - } - normalized.issueNumber = issueNumber; - } - } - - const referenceIssue = opts.referenceIssue ?? '192'; - const contextBlock = opts.contextFilesContent ?? loadContextFiles(); - let issueComments: IssueComment[] = []; - if ((eventName === 'issues' || eventName === 'issue_comment') && opts.repo && opts.token) { - const [owner, repo] = opts.repo.split('/'); - if (owner && repo) { - const fetchCommentsFor = commentTargetIssueNumber ?? normalized.issueNumber; - issueComments = await fetchIssueComments(owner, repo, fetchCommentsFor, opts.token); - } - } - const prompt = buildAssessmentPrompt(payload, eventName, referenceIssue, contextBlock, issueComments); - const result = await callClaude(prompt, opts.anthropicClient); - result.issue_number = normalized.issueNumber; - - // Populate review_feedback so implement step can address reviewer/comment (it only gets issue #N from API otherwise). - if (eventName === 'pull_request_review') { - const review = (payload as Record).review as { body?: string } | undefined; - result.review_feedback = review?.body?.trim() ?? ''; - } else if (eventName === 'pull_request_review_comment') { - const comment = (payload as Record).comment as { body?: string } | undefined; - result.review_feedback = comment?.body?.trim() ?? ''; - } else if (eventName === 'issue_comment') { - const p = payload as Record; - const issue = p.issue as { pull_request?: unknown } | undefined; - if (issue?.pull_request) { - const comment = p.comment as { body?: string } | undefined; - result.review_feedback = comment?.body?.trim() ?? ''; - } - } - - logAssessSummary(result); - return result; -} - -// Only run main when invoked as script (not when imported by tests). In CI, the runner sets -// GITHUB_EVENT_PATH, so we must skip main() when Vitest is running to avoid unhandled process.exit. -if (process.env.GITHUB_EVENT_PATH && !process.env.VITEST) { - main().catch((err) => { - console.error(err); - process.exit(1); - }); -} diff --git a/.github/actions/issue-auto-implement/assess/src/load-dotenv.ts b/.github/actions/issue-auto-implement/assess/src/load-dotenv.ts deleted file mode 100644 index 6769ea04..00000000 --- a/.github/actions/issue-auto-implement/assess/src/load-dotenv.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Load .env from action root and assess dir before any other module that reads process.env. - * Import this first in run-local-assess so env is set before index.js loads. - */ -import { resolve, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { config } from 'dotenv'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const actionRoot = resolve(__dirname, '../..'); -config({ path: resolve(actionRoot, '.env'), override: true }); -config({ path: resolve(__dirname, '../.env'), override: true }); diff --git a/.github/actions/issue-auto-implement/assess/src/normalize.ts b/.github/actions/issue-auto-implement/assess/src/normalize.ts deleted file mode 100644 index f6fb39d3..00000000 --- a/.github/actions/issue-auto-implement/assess/src/normalize.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Event normalization: derive issue number (and related) from GitHub workflow event payloads. - * Used for: issues, issue_comment, pull_request_review, pull_request_review_comment. - */ - -export interface NormalizedEvent { - eventName: string; - issueNumber: number; - /** For issue_comment: should we redirect to PR instead of assessing? (PR exists for this issue) */ - redirectToPr?: boolean; - prUrl?: string; - /** Pull request head ref (e.g. auto-implement-issue-123) for PR events */ - headRef?: string; -} - -/** Match "Closes #123" or "Fixes #456" in text; returns first match or null */ -export function parseClosesIssueNumber(text: string): number | null { - if (!text || typeof text !== 'string') return null; - const match = text.match(/(?:Closes|Fixes)\s+#(\d+)/i); - return match ? parseInt(match[1], 10) : null; -} - -/** Match branch name auto-implement-issue-; returns N or null */ -export function parseIssueNumberFromBranch(branchName: string): number | null { - if (!branchName || typeof branchName !== 'string') return null; - const match = branchName.match(/^auto-implement-issue-(\d+)$/); - return match ? parseInt(match[1], 10) : null; -} - -/** Derive issue number from a PR payload (body or head ref) */ -export function issueNumberFromPrPayload(pr: { body?: string | null; head?: { ref?: string } }): number | null { - const fromBody = pr?.body != null ? parseClosesIssueNumber(pr.body) : null; - if (fromBody != null) return fromBody; - const ref = pr?.head?.ref; - return ref != null ? parseIssueNumberFromBranch(ref) : null; -} - -/** Minimal payload shapes we need from GitHub Actions event */ -export type IssuePayload = { issue: { number: number }; label?: { name: string } }; -export type IssueCommentPayload = { issue: { number: number } }; -export type PullRequestReviewPayload = { pull_request: { number: number; body?: string | null; head?: { ref?: string }; html_url?: string }; review?: { body?: string | null } }; - -export function normalizeEvent(eventName: string, payload: unknown): NormalizedEvent | null { - if (!payload || typeof payload !== 'object') return null; - - const p = payload as Record; - - if (eventName === 'issues') { - const issue = (p.issue as { number: number })?.number; - if (issue == null) return null; - return { eventName: 'issues', issueNumber: issue }; - } - - if (eventName === 'issue_comment') { - const issue = (p.issue as { number: number })?.number; - if (issue == null) return null; - return { - eventName: 'issue_comment', - issueNumber: issue, - redirectToPr: false, // caller must set from API if PR exists - }; - } - - if (eventName === 'pull_request_review' || eventName === 'pull_request_review_comment') { - const pr = p.pull_request as { body?: string | null; head?: { ref?: string }; html_url?: string } | undefined; - if (!pr) return null; - const issueNumber = issueNumberFromPrPayload(pr); - if (issueNumber == null) return null; - return { - eventName, - issueNumber, - headRef: pr.head?.ref, - }; - } - - return null; -} diff --git a/.github/actions/issue-auto-implement/assess/src/push-and-open-pr.ts b/.github/actions/issue-auto-implement/assess/src/push-and-open-pr.ts deleted file mode 100644 index af66833a..00000000 --- a/.github/actions/issue-auto-implement/assess/src/push-and-open-pr.ts +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env -S npx tsx -/** - * Commit implement output, push branch, create PR if missing. - * Caller must ensure repoRoot is the implementation branch (e.g. after checkout or worktree add). - * - * Env: GITHUB_TOKEN (for push and gh). IMPLEMENT_COMMIT_MSG_FILE overrides default path. - * When run as script: ISSUE_NUMBER and GITHUB_WORKSPACE (or cwd = assess/ with repo root 4 levels up). - */ -import { resolve } from 'path'; -import { unlinkSync, existsSync, readFileSync } from 'fs'; -import { execSync } from 'child_process'; - -const REL_DIR = '.github/actions/issue-auto-implement'; -const REL_COMMIT_MSG = `${REL_DIR}/.commit_msg`; -const REL_PR_TITLE = `${REL_DIR}/.pr_title`; -const REL_PR_BODY = `${REL_DIR}/.pr_body`; - -export function pushAndOpenPr(repoRoot: string, issueNumber: number, token?: string): void { - const commitMsgFile = resolve(repoRoot, REL_COMMIT_MSG); - if (!existsSync(commitMsgFile)) { - throw new Error(`Missing ${commitMsgFile}; run implement step first.`); - } - const branch = `auto-implement-issue-${issueNumber}`; - const env = { ...process.env, GH_TOKEN: token || process.env.GITHUB_TOKEN }; - - execSync('git add -A', { cwd: repoRoot, stdio: 'inherit' }); - for (const rel of [REL_COMMIT_MSG, REL_PR_TITLE, REL_PR_BODY]) { - try { - execSync(`git reset -- ${rel}`, { cwd: repoRoot, stdio: 'pipe' }); - } catch { - // ignore if path not staged - } - } - - let hasStaged: boolean; - try { - execSync('git diff --staged --quiet', { cwd: repoRoot, stdio: 'pipe' }); - hasStaged = false; - } catch { - hasStaged = true; - } - - if (hasStaged) { - execSync(`git commit -F ${REL_COMMIT_MSG}`, { cwd: repoRoot, stdio: 'inherit' }); - unlinkSync(commitMsgFile); - execSync(`git push -u origin ${branch} --force-with-lease`, { cwd: repoRoot, stdio: 'inherit', env }); - } else { - console.error('No changes to commit.'); - } - - const prTitleFile = resolve(repoRoot, REL_PR_TITLE); - const prBodyFile = resolve(repoRoot, REL_PR_BODY); - - let shouldCreatePr = true; - try { - const out = execSync('gh pr view --json state', { cwd: repoRoot, encoding: 'utf-8', env }); - const { state } = JSON.parse(out) as { state: string }; - if (state === 'OPEN') { - shouldCreatePr = false; - console.error('PR already exists; branch pushed.'); - } - // If state is CLOSED or MERGED, create a new PR for this branch (GitHub allows that). - } catch { - // No PR for this branch; create one. - } - - if (shouldCreatePr) { - const title = existsSync(prTitleFile) ? readFileSync(prTitleFile, 'utf-8').trim() : `Implement issue #${issueNumber}`; - const bodyPath = existsSync(prBodyFile) ? prBodyFile : null; - const createArgs = bodyPath - ? `--title ${JSON.stringify(title)} --body-file ${JSON.stringify(prBodyFile)}` - : `--title ${JSON.stringify(title)} --body ${JSON.stringify(`Closes #${issueNumber}`)}`; - execSync(`gh pr create ${createArgs}`, { - cwd: repoRoot, - stdio: 'inherit', - env, - }); - if (existsSync(prTitleFile)) unlinkSync(prTitleFile); - if (existsSync(prBodyFile)) unlinkSync(prBodyFile); - console.error('PR created.'); - } else { - if (existsSync(prTitleFile)) unlinkSync(prTitleFile); - if (existsSync(prBodyFile)) unlinkSync(prBodyFile); - } -} - -function main(): void { - const repoRoot = process.env.GITHUB_WORKSPACE || resolve(process.cwd(), '../../../..'); - const issueNumber = process.env.ISSUE_NUMBER; - if (!issueNumber) { - console.error('Set ISSUE_NUMBER'); - process.exit(1); - } - pushAndOpenPr(repoRoot, parseInt(issueNumber, 10)); -} - -const isMain = - typeof process.argv[1] === 'string' && - (process.argv[1].endsWith('push-and-open-pr.ts') || process.argv[1].endsWith('push-and-open-pr.js')); -if (isMain) { - main(); -} diff --git a/.github/actions/issue-auto-implement/assess/src/run-local-assess.ts b/.github/actions/issue-auto-implement/assess/src/run-local-assess.ts deleted file mode 100644 index e038b517..00000000 --- a/.github/actions/issue-auto-implement/assess/src/run-local-assess.ts +++ /dev/null @@ -1,229 +0,0 @@ -#!/usr/bin/env -S npx tsx -/** - * Run assess against a real issue by fetching it from GitHub (no event file). - * Optionally APPLY=1: post comments to the issue or run implement + push (same as the workflow would). - * - * Local runs force the same default context as CI so assess and implement see the same repo guidance. - * Env: ISSUE_NUMBER (required), GITHUB_REPOSITORY, GITHUB_TOKEN, AUTO_IMPLEMENT_ANTHROPIC_API_KEY. - * Optional: EVENT_TYPE=issues|issue_comment|pull_request_review; COMMENT_BODY (for issue_comment); REVIEW_BODY (for pull_request_review). - * Optional: APPLY=1 — post comment on issue (request_info/redirect_to_pr) or run implement then push-and-open-pr (implement). - * Optional: CONTEXT_FILES — overrides default; default matches action.yml context_files (AGENTS.md,REFERENCE.md). - * - * Output: same JSON as index.ts (action, issue_number, comment_body?, verification_notes?, pr_url?). - */ -import './load-dotenv.js'; - -/** Must match action.yml inputs.context_files default so local and CI use the same repo context. */ -const DEFAULT_CONTEXT_FILES = 'AGENTS.md,REFERENCE.md'; -import { resolve, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { existsSync } from 'fs'; -import { execSync } from 'child_process'; -import { assess } from './index.js'; -import { pushAndOpenPr } from './push-and-open-pr.js'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -const ISSUE_NUMBER = process.env.ISSUE_NUMBER; -const REPO = process.env.GITHUB_REPOSITORY || ''; -const TOKEN = process.env.GITHUB_TOKEN || ''; -const EVENT_TYPE = (process.env.EVENT_TYPE || 'issues') as 'issues' | 'issue_comment' | 'pull_request_review'; -const COMMENT_BODY = process.env.COMMENT_BODY || ''; -const REVIEW_BODY = process.env.REVIEW_BODY || ''; -const APPLY = process.env.APPLY === '1' || process.env.APPLY === 'true'; -const LABEL_PREFIX = process.env.LABEL_PREFIX || 'automation'; - -async function fetchIssue(owner: string, repo: string, issueNumber: number): Promise<{ title: string; body: string; number: number; labels: { name: string }[] }> { - const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`; - const res = await fetch(url, { - headers: { Authorization: `Bearer ${TOKEN}`, Accept: 'application/vnd.github+json' }, - }); - if (!res.ok) throw new Error(`Failed to fetch issue: ${res.status} ${await res.text()}`); - const data = (await res.json()) as { title?: string; body?: string; number?: number; labels?: { name: string }[] }; - return { - title: data.title ?? '', - body: data.body ?? '', - number: data.number ?? issueNumber, - labels: data.labels ?? [], - }; -} - -async function fetchIssueComments(owner: string, repo: string, issueNumber: number): Promise<{ body?: string; user?: { login?: string }; created_at?: string }[]> { - const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`; - const res = await fetch(url, { - headers: { Authorization: `Bearer ${TOKEN}`, Accept: 'application/vnd.github+json' }, - }); - if (!res.ok) return []; - const data = (await res.json()) as { body?: string; user?: { login?: string }; created_at?: string }[]; - return Array.isArray(data) ? data : []; -} - -async function postIssueComment(owner: string, repo: string, issueNumber: number, body: string): Promise { - const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`; - const res = await fetch(url, { - method: 'POST', - headers: { - Authorization: `Bearer ${TOKEN}`, - Accept: 'application/vnd.github+json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ body }), - }); - if (!res.ok) throw new Error(`Failed to post comment: ${res.status} ${await res.text()}`); -} - -async function addLabel(owner: string, repo: string, issueNumber: number, label: string): Promise { - const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/labels`; - const res = await fetch(url, { - method: 'POST', - headers: { - Authorization: `Bearer ${TOKEN}`, - Accept: 'application/vnd.github+json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ labels: [label] }), - }); - if (!res.ok) { - // 422 = label may not exist; ignore - if (res.status !== 422) throw new Error(`Failed to add label: ${res.status} ${await res.text()}`); - } -} - -async function main(): Promise { - if (!process.env.CONTEXT_FILES?.trim()) { - process.env.CONTEXT_FILES = DEFAULT_CONTEXT_FILES; - } - - const missing: string[] = []; - if (!ISSUE_NUMBER) missing.push('ISSUE_NUMBER'); - if (!REPO) missing.push('GITHUB_REPOSITORY'); - if (!TOKEN) missing.push('GITHUB_TOKEN'); - if (missing.length) { - throw new Error( - `Missing or empty: ${missing.join(', ')}. Set in .env (action root or assess/) or in the environment.` - ); - } - const issueNumber = parseInt(ISSUE_NUMBER, 10); - if (Number.isNaN(issueNumber)) throw new Error('Invalid ISSUE_NUMBER'); - - const [owner, repoName] = REPO.split('/'); - if (!owner || !repoName) throw new Error('Invalid GITHUB_REPOSITORY (use owner/repo)'); - - if (EVENT_TYPE === 'pull_request_review' && !REVIEW_BODY.trim()) { - throw new Error('Set REVIEW_BODY when EVENT_TYPE=pull_request_review (paste the review you left on the PR)'); - } - - const issue = await fetchIssue(owner, repoName, issueNumber); - const comments = await fetchIssueComments(owner, repoName, issueNumber); - - let eventName: string; - let payload: unknown; - - if (EVENT_TYPE === 'pull_request_review') { - eventName = 'pull_request_review'; - payload = { - pull_request: { - body: `Closes #${issueNumber}`, - head: { ref: `auto-implement-issue-${issueNumber}` }, - }, - review: { body: REVIEW_BODY }, - issue: { number: issue.number, title: issue.title, body: issue.body, labels: issue.labels }, - }; - } else if (EVENT_TYPE === 'issue_comment' || COMMENT_BODY.trim()) { - eventName = 'issue_comment'; - payload = { - action: 'created', - issue: { - number: issue.number, - title: issue.title, - body: issue.body, - labels: issue.labels.length ? issue.labels : [{ name: `${LABEL_PREFIX}/auto-implement` }], - }, - comment: { body: COMMENT_BODY.trim() || '(new comment)' }, - repository: { full_name: REPO }, - }; - } else { - eventName = 'issues'; - payload = { - action: 'labeled', - issue: { - number: issue.number, - title: issue.title, - body: issue.body, - labels: issue.labels.length ? issue.labels : [{ name: `${LABEL_PREFIX}/auto-implement` }], - }, - repository: { full_name: REPO }, - }; - } - - const result = await assess(eventName, payload, { - repo: REPO, - token: TOKEN, - referenceIssue: process.env.ASSESSMENT_REFERENCE_ISSUE || '192', - }); - - console.log(JSON.stringify(result)); - - if (!APPLY) return; - - if (result.action === 'request_info' && result.comment_body) { - await postIssueComment(owner, repoName, issueNumber, result.comment_body); - await addLabel(owner, repoName, issueNumber, `${LABEL_PREFIX}/needs-info`); - console.error('Posted request for more info on issue #' + issueNumber); - } else if (result.action === 'redirect_to_pr' && result.pr_url) { - const body = `A PR is open for this issue. Please review and comment on the PR: ${result.pr_url}`; - await postIssueComment(owner, repoName, issueNumber, body); - console.error('Posted redirect to PR on issue #' + issueNumber); - } else if (result.action === 'implement') { - const assessDir = process.cwd(); - const repoRoot = resolve(assessDir, '../../../..'); - const branch = `auto-implement-issue-${issueNumber}`; - const worktreePath = resolve(repoRoot, '.worktrees', branch); - - // Always start from a clean branch: remove existing worktree and local branch, then create fresh from origin/main - if (existsSync(worktreePath)) { - try { - execSync(`git worktree remove "${worktreePath}" --force`, { cwd: repoRoot, stdio: 'inherit' }); - } catch (e) { - console.error('Failed to remove existing worktree:', e); - throw e; - } - } - try { - execSync(`git branch -D ${branch}`, { cwd: repoRoot, stdio: 'pipe' }); - } catch { - // local branch may not exist - } - try { - execSync('git fetch origin main', { cwd: repoRoot, stdio: 'pipe' }); - } catch { - // ignore - } - execSync(`git worktree add "${worktreePath}" -b "${branch}" origin/main`, { - cwd: repoRoot, - stdio: 'inherit', - }); - - const reviewFeedback = - result.review_feedback || - (EVENT_TYPE === 'issue_comment' ? COMMENT_BODY : '') || - (EVENT_TYPE === 'pull_request_review' ? REVIEW_BODY : ''); - const env = { - ...process.env, - ISSUE_NUMBER: String(issueNumber), - VERIFICATION_NOTES: result.verification_notes || '', - REVIEW_FEEDBACK: reviewFeedback, - GITHUB_REPOSITORY: REPO, - GITHUB_TOKEN: TOKEN, - GITHUB_WORKSPACE: worktreePath, - }; - execSync('npx tsx src/implement.ts', { cwd: assessDir, env, stdio: 'inherit' }); - pushAndOpenPr(worktreePath, issueNumber, TOKEN); - console.error('Ran implement in worktree and pushed; PR created or updated.'); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/.github/actions/issue-auto-implement/assess/test/fixtures/issue-comment.json b/.github/actions/issue-auto-implement/assess/test/fixtures/issue-comment.json deleted file mode 100644 index 495653b5..00000000 --- a/.github/actions/issue-auto-implement/assess/test/fixtures/issue-comment.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "action": "created", - "issue": { - "number": 42, - "title": "Example issue", - "body": "Description", - "labels": [{ "name": "automation/auto-implement" }] - }, - "comment": { "body": "Here is more context for implementation." }, - "repository": { "full_name": "hookdeck/hookdeck-cli" } -} diff --git a/.github/actions/issue-auto-implement/assess/test/fixtures/issue-labeled.json b/.github/actions/issue-auto-implement/assess/test/fixtures/issue-labeled.json deleted file mode 100644 index 7c29f204..00000000 --- a/.github/actions/issue-auto-implement/assess/test/fixtures/issue-labeled.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "action": "labeled", - "issue": { - "number": 192, - "title": "Fix rule-filter-headers storing JSON as escaped string", - "body": "When using --rule-filter-headers the value is stored as an escaped JSON string...", - "labels": [{ "name": "automation/auto-implement" }] - }, - "label": { "name": "automation/auto-implement" }, - "repository": { "full_name": "hookdeck/hookdeck-cli" } -} diff --git a/.github/actions/issue-auto-implement/assess/test/fixtures/pull_request_review.json b/.github/actions/issue-auto-implement/assess/test/fixtures/pull_request_review.json deleted file mode 100644 index df81f386..00000000 --- a/.github/actions/issue-auto-implement/assess/test/fixtures/pull_request_review.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "action": "submitted", - "pull_request": { - "number": 99, - "body": "Closes #192", - "head": { "ref": "auto-implement-issue-192" }, - "html_url": "https://github.com/hookdeck/hookdeck-cli/pull/99" - }, - "review": { - "body": "Please add a test for the new behavior.", - "state": "changes_requested" - }, - "repository": { "full_name": "hookdeck/hookdeck-cli" } -} diff --git a/.github/actions/issue-auto-implement/assess/test/integration/assess.integration.test.ts b/.github/actions/issue-auto-implement/assess/test/integration/assess.integration.test.ts deleted file mode 100644 index da8e6504..00000000 --- a/.github/actions/issue-auto-implement/assess/test/integration/assess.integration.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Integration tests: call real Claude API. Require AUTO_IMPLEMENT_ANTHROPIC_API_KEY (or ANTHROPIC_API_KEY). - * Run with: npm run test:integration - * Not run with: npm test (unit tests only) - */ -import { config } from 'dotenv'; -import { resolve, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { readFileSync } from 'fs'; -import { describe, it, expect } from 'vitest'; -import { assess } from '../../src/index.js'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -// Load .env from action root then cwd so integration tests see the same env as local runs -config({ path: resolve(process.cwd(), '../.env') }); -config({ path: resolve(process.cwd(), '.env') }); - -const hasApiKey = !!( - process.env.AUTO_IMPLEMENT_ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY -); - -const FIXTURES_DIR = resolve(__dirname, '../fixtures'); - -function loadFixture(name: string): unknown { - const raw = readFileSync(resolve(FIXTURES_DIR, name), 'utf-8'); - return JSON.parse(raw); -} - -describe.skipIf(!hasApiKey)('assess (integration with Claude)', () => { - it('returns valid assessment shape for issue-labeled fixture (real API)', async () => { - const payload = loadFixture('issue-labeled.json'); - const result = await assess('issues', payload, { - referenceIssue: '192', - // No anthropicClient: use real API - }); - - expect(result).toBeDefined(); - expect(['implement', 'request_info']).toContain(result.action); - expect(typeof result.issue_number).toBe('number'); - expect(result.issue_number).toBe(192); - - if (result.action === 'request_info') { - expect(typeof result.comment_body).toBe('string'); - expect(result.comment_body.length).toBeGreaterThan(0); - } - if (result.action === 'implement' && result.verification_notes !== undefined) { - expect(typeof result.verification_notes).toBe('string'); - } - }, 45_000); - - it('returns valid assessment shape for issue-comment fixture (real API)', async () => { - const payload = loadFixture('issue-comment.json'); - const result = await assess('issue_comment', payload, { - referenceIssue: '192', - }); - - expect(result).toBeDefined(); - expect(['implement', 'request_info', 'redirect_to_pr']).toContain(result.action); - expect(typeof result.issue_number).toBe('number'); - - if (result.action === 'request_info') { - expect(typeof result.comment_body).toBe('string'); - } - }, 45_000); - - it('returns valid assessment shape for pull_request_review fixture (real API)', async () => { - const payload = loadFixture('pull_request_review.json'); - const result = await assess('pull_request_review', payload, { - referenceIssue: '192', - }); - - expect(result).toBeDefined(); - expect(['implement', 'request_info']).toContain(result.action); - expect(typeof result.issue_number).toBe('number'); - }, 45_000); -}); diff --git a/.github/actions/issue-auto-implement/assess/test/unit/index.test.ts b/.github/actions/issue-auto-implement/assess/test/unit/index.test.ts deleted file mode 100644 index b17a2f6b..00000000 --- a/.github/actions/issue-auto-implement/assess/test/unit/index.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { readFileSync } from 'fs'; -import { resolve, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { assess } from '../../src/index.js'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const FIXTURES_DIR = resolve(__dirname, '../fixtures'); - -function loadFixture(name: string): unknown { - const raw = readFileSync(resolve(FIXTURES_DIR, name), 'utf-8'); - return JSON.parse(raw); -} - -describe('assess', () => { - it('returns implement or request_info with valid shape when using mock client', async () => { - const mockClient = { - messages: { - create: vi.fn().mockResolvedValue({ - content: [ - { - type: 'text', - text: '{"action":"implement","verification_notes":"Run go test ./..."}', - }, - ], - }), - }, - } as unknown as import('@anthropic-ai/sdk').Anthropic; - - const payload = loadFixture('issue-labeled.json'); - const result = await assess('issues', payload, { - referenceIssue: '192', - anthropicClient: mockClient, - }); - - expect(result.action).toMatch(/^(implement|request_info)$/); - if (result.action === 'request_info') { - expect(typeof result.comment_body).toBe('string'); - } - if (result.action === 'implement' && result.verification_notes !== undefined) { - expect(typeof result.verification_notes).toBe('string'); - } - }); - - it('returns valid output shape for issue_comment fixture with mock client', async () => { - const mockClient = { - messages: { - create: vi.fn().mockResolvedValue({ - content: [{ type: 'text', text: '{"action":"request_info","comment_body":"Please add steps to reproduce."}' }], - }), - }, - } as unknown as import('@anthropic-ai/sdk').Anthropic; - - const payload = loadFixture('issue-comment.json'); - const result = await assess('issue_comment', payload, { - referenceIssue: '192', - anthropicClient: mockClient, - }); - - expect(['implement', 'request_info', 'redirect_to_pr']).toContain(result.action); - if (result.action === 'request_info') expect(typeof result.comment_body).toBe('string'); - }); - - it('sets review_feedback from PR review body when event is pull_request_review', async () => { - const mockClient = { - messages: { - create: vi.fn().mockResolvedValue({ - content: [ - { - type: 'text', - text: '{"action":"implement","verification_notes":"Run go test ./..."}', - }, - ], - }), - }, - } as unknown as import('@anthropic-ai/sdk').Anthropic; - - const payload = loadFixture('pull_request_review.json'); - const result = await assess('pull_request_review', payload, { - referenceIssue: '192', - anthropicClient: mockClient, - }); - - expect(result.action).toBe('implement'); - expect(result.review_feedback).toBe('Please add a test for the new behavior.'); - }); - - it('sets review_feedback from comment when event is issue_comment on PR', async () => { - const mockClient = { - messages: { - create: vi.fn().mockResolvedValue({ - content: [ - { type: 'text', text: '{"action":"implement","verification_notes":"Run tests."}' }, - ], - }), - }, - } as unknown as import('@anthropic-ai/sdk').Anthropic; - - const payload = { - action: 'created', - issue: { - number: 261, - title: 'PR title', - body: 'Closes #215', - pull_request: {}, // comment on PR - }, - comment: { body: 'Could we add some unit and acceptance test coverage for this change?' }, - repository: { full_name: 'hookdeck/hookdeck-cli' }, - }; - const result = await assess('issue_comment', payload, { - referenceIssue: '192', - anthropicClient: mockClient, - }); - - expect(result.action).toBe('implement'); - expect(result.review_feedback).toBe( - 'Could we add some unit and acceptance test coverage for this change?' - ); - }); -}); diff --git a/.github/actions/issue-auto-implement/assess/test/unit/normalize.test.ts b/.github/actions/issue-auto-implement/assess/test/unit/normalize.test.ts deleted file mode 100644 index 8ae254a2..00000000 --- a/.github/actions/issue-auto-implement/assess/test/unit/normalize.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { - parseClosesIssueNumber, - parseIssueNumberFromBranch, - issueNumberFromPrPayload, - normalizeEvent, -} from '../../src/normalize.js'; - -describe('parseClosesIssueNumber', () => { - it('extracts issue number from Closes #123', () => { - expect(parseClosesIssueNumber('Closes #123')).toBe(123); - expect(parseClosesIssueNumber('Closes #1')).toBe(1); - }); - it('extracts issue number from Fixes #456', () => { - expect(parseClosesIssueNumber('Fixes #456')).toBe(456); - }); - it('returns first match', () => { - expect(parseClosesIssueNumber('Closes #10 and Fixes #20')).toBe(10); - }); - it('returns null for empty or no match', () => { - expect(parseClosesIssueNumber('')).toBeNull(); - expect(parseClosesIssueNumber('No issue here')).toBeNull(); - expect(parseClosesIssueNumber(null as unknown as string)).toBeNull(); - }); -}); - -describe('parseIssueNumberFromBranch', () => { - it('extracts issue number from auto-implement-issue-', () => { - expect(parseIssueNumberFromBranch('auto-implement-issue-42')).toBe(42); - expect(parseIssueNumberFromBranch('auto-implement-issue-1')).toBe(1); - }); - it('returns null for non-matching branch', () => { - expect(parseIssueNumberFromBranch('main')).toBeNull(); - expect(parseIssueNumberFromBranch('feature/foo')).toBeNull(); - expect(parseIssueNumberFromBranch('auto-implement-issue-')).toBeNull(); - }); -}); - -describe('issueNumberFromPrPayload', () => { - it('prefers body Closes #N over branch', () => { - expect( - issueNumberFromPrPayload({ - body: 'Closes #99', - head: { ref: 'auto-implement-issue-42' }, - }) - ).toBe(99); - }); - it('uses branch when body has no Closes/Fixes', () => { - expect( - issueNumberFromPrPayload({ - body: 'Some description', - head: { ref: 'auto-implement-issue-42' }, - }) - ).toBe(42); - }); - it('returns null when neither present', () => { - expect(issueNumberFromPrPayload({})).toBeNull(); - expect(issueNumberFromPrPayload({ body: '', head: { ref: 'main' } })).toBeNull(); - }); -}); - -describe('normalizeEvent', () => { - it('extracts issue number from issues payload', () => { - const r = normalizeEvent('issues', { issue: { number: 192 } }); - expect(r).toEqual({ eventName: 'issues', issueNumber: 192 }); - }); - - it('extracts issue number from issue_comment payload', () => { - const r = normalizeEvent('issue_comment', { issue: { number: 5 } }); - expect(r).toEqual({ eventName: 'issue_comment', issueNumber: 5, redirectToPr: false }); - }); - - it('extracts issue number from pull_request_review (body)', () => { - const r = normalizeEvent('pull_request_review', { - pull_request: { body: 'Fixes #10', head: { ref: 'auto-implement-issue-10' } }, - }); - expect(r).toEqual({ eventName: 'pull_request_review', issueNumber: 10, headRef: 'auto-implement-issue-10' }); - }); - - it('extracts issue number from pull_request_review (branch only)', () => { - const r = normalizeEvent('pull_request_review', { - pull_request: { head: { ref: 'auto-implement-issue-7' } }, - }); - expect(r).toEqual({ eventName: 'pull_request_review', issueNumber: 7, headRef: 'auto-implement-issue-7' }); - }); - - it('returns null for unknown event or missing data', () => { - expect(normalizeEvent('push', {})).toBeNull(); - expect(normalizeEvent('issues', {})).toBeNull(); - expect(normalizeEvent('pull_request_review', { pull_request: {} })).toBeNull(); - }); -}); diff --git a/.github/actions/issue-auto-implement/assess/tsconfig.json b/.github/actions/issue-auto-implement/assess/tsconfig.json deleted file mode 100644 index 4eb7a2af..00000000 --- a/.github/actions/issue-auto-implement/assess/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "outDir": "dist", - "rootDir": "." - }, - "include": ["*.ts"], - "exclude": ["node_modules", "dist"] -} diff --git a/.github/actions/issue-auto-implement/assess/vitest.config.ts b/.github/actions/issue-auto-implement/assess/vitest.config.ts deleted file mode 100644 index 7a9682ec..00000000 --- a/.github/actions/issue-auto-implement/assess/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - include: ['test/unit/**/*.test.ts'], - globals: false, - }, -}); diff --git a/.github/actions/issue-auto-implement/assess/vitest.integration.config.ts b/.github/actions/issue-auto-implement/assess/vitest.integration.config.ts deleted file mode 100644 index 91e85b38..00000000 --- a/.github/actions/issue-auto-implement/assess/vitest.integration.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { resolve } from 'path'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - include: ['test/integration/**/*.test.ts'], - globals: false, - testTimeout: 60_000, - hookTimeout: 10_000, - }, -}); diff --git a/.github/actions/issue-auto-implement/scripts/push-and-open-pr.sh b/.github/actions/issue-auto-implement/scripts/push-and-open-pr.sh deleted file mode 100755 index 2f261a2b..00000000 --- a/.github/actions/issue-auto-implement/scripts/push-and-open-pr.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -# From repo root: commit implement output, push branch, open PR if missing. -# Requires: ISSUE_NUMBER set (e.g. export ISSUE_NUMBER=192). -# Requires: gh CLI and GITHUB_TOKEN (or gh auth). -# -# Prefer the TypeScript implementation when running from the assess flow: -# assess/src/push-and-open-pr.ts (used by run-local-assess with APPLY=1). -# This script is still valid for manual "commit and open PR" from repo root -# when you have already run implement and are on the implementation branch. -# -# Usage from repo root: -# ISSUE_NUMBER=192 ./.github/actions/issue-auto-implement/scripts/push-and-open-pr.sh -set -e -if [[ -z "$ISSUE_NUMBER" ]]; then - echo "Set ISSUE_NUMBER (e.g. export ISSUE_NUMBER=192)" >&2 - exit 1 -fi -REPO_ROOT="$(git rev-parse --show-toplevel)" -cd "$REPO_ROOT" -BRANCH="auto-implement-issue-${ISSUE_NUMBER}" -COMMIT_MSG_FILE=".github/actions/issue-auto-implement/.commit_msg" -if [[ ! -f "$COMMIT_MSG_FILE" ]]; then - echo "Missing $COMMIT_MSG_FILE (run implement step first from assess dir)" >&2 - exit 1 -fi -# Create or checkout branch -if git show-ref --verify --quiet refs/heads/"$BRANCH"; then - git checkout "$BRANCH" - git merge origin/main --no-edit 2>/dev/null || true -elif git show-ref --verify --quiet refs/remotes/origin/"$BRANCH"; then - git fetch origin "$BRANCH" - git checkout -b "$BRANCH" origin/"$BRANCH" 2>/dev/null || git checkout "$BRANCH" - git merge origin/main --no-edit 2>/dev/null || true -else - git fetch origin main 2>/dev/null || true - git checkout -b "$BRANCH" origin/main 2>/dev/null || git checkout -b "$BRANCH" main -fi -# Stage all, unstage commit message file, commit, push -git add -A -git reset -- "$COMMIT_MSG_FILE" 2>/dev/null || true -if git diff --staged --quiet; then - echo "No changes to commit." -else - git commit -F "$COMMIT_MSG_FILE" - rm -f "$COMMIT_MSG_FILE" -fi -git push -u origin "$BRANCH" -# Open PR if none exists for this branch -if ! gh pr view --json number 2>/dev/null; then - gh pr create --fill --body "Closes #${ISSUE_NUMBER}" - echo "PR created." -else - echo "PR already exists; branch pushed." -fi diff --git a/.github/actions/issue-auto-implement/scripts/setup-local-env.sh b/.github/actions/issue-auto-implement/scripts/setup-local-env.sh deleted file mode 100755 index 50efccd5..00000000 --- a/.github/actions/issue-auto-implement/scripts/setup-local-env.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -# Create or update .env for local runs. Run from action root: .github/actions/issue-auto-implement/ -# -# Usage: -# ./scripts/setup-local-env.sh # Create .env from .env.example if missing; optionally fill GITHUB_TOKEN from gh -# ./scripts/setup-local-env.sh --with-gh # Same, and run 'gh auth token' to set GITHUB_TOKEN (prompts if not authenticated) -# ./scripts/setup-local-env.sh --template-only # Only create .env from .env.example, do not run gh - -set -e -ACTION_ROOT="$(cd "$(dirname "$0")/.." && pwd)" -cd "$ACTION_ROOT" -ENV_FILE="$ACTION_ROOT/.env" -EXAMPLE_FILE="$ACTION_ROOT/.env.example" - -if [[ "$1" == "--template-only" ]]; then - WITH_GH=false -else - WITH_GH=false - [[ "$1" == "--with-gh" ]] && WITH_GH=true -fi - -if [[ ! -f "$EXAMPLE_FILE" ]]; then - echo "Missing .env.example" >&2 - exit 1 -fi - -if [[ ! -f "$ENV_FILE" ]]; then - cp "$EXAMPLE_FILE" "$ENV_FILE" - echo "Created .env from .env.example" -else - echo ".env already exists; leaving it as-is" -fi - -if [[ "$WITH_GH" == true ]]; then - if command -v gh &>/dev/null; then - TOKEN=$(gh auth token 2>/dev/null) || true - if [[ -n "$TOKEN" ]]; then - if grep -q '^GITHUB_TOKEN=' "$ENV_FILE" 2>/dev/null; then - sed -i.bak "s|^GITHUB_TOKEN=.*|GITHUB_TOKEN=$TOKEN|" "$ENV_FILE" && rm -f "$ENV_FILE.bak" - else - echo "GITHUB_TOKEN=$TOKEN" >> "$ENV_FILE" - fi - echo "Set GITHUB_TOKEN from gh auth token" - else - echo "gh auth token returned empty; run 'gh auth login' if needed. GITHUB_TOKEN not updated in .env" - fi - else - echo "gh not found; install GitHub CLI to fill GITHUB_TOKEN automatically" - fi -fi - -echo "Edit .env to set AUTO_IMPLEMENT_ANTHROPIC_API_KEY (and ISSUE_NUMBER, GITHUB_REPOSITORY when running implement)." diff --git a/.github/workflows/issue-auto-implement-setup.yml b/.github/workflows/issue-auto-implement-setup.yml deleted file mode 100644 index c49edced..00000000 --- a/.github/workflows/issue-auto-implement-setup.yml +++ /dev/null @@ -1,36 +0,0 @@ -# One-time setup: create the labels required by issue-auto-implement. -# Run manually (Actions → Issue auto-implement setup → Run workflow) once per repo. -# After this runs, the "automation/auto-implement" label will exist so you can add it to issues. -name: Issue auto-implement setup - -on: - workflow_dispatch: - -jobs: - create-labels: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - name: Create labels - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - LABEL_PREFIX: automation - run: | - for LABEL in auto-implement needs-info pr-created; do - NAME="${LABEL_PREFIX}/${LABEL}" - CODE=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/labels" \ - -d "{\"name\":\"$NAME\",\"color\":\"ededed\",\"description\":\"Issue auto-implement: $LABEL\"}") - if [ "$CODE" = "201" ]; then - echo "Created label: $NAME" - elif [ "$CODE" = "422" ]; then - echo "Label already exists: $NAME" - else - echo "::warning::Unexpected response $CODE for $NAME" - fi - done - echo "Done. You can now add the label $LABEL_PREFIX/auto-implement to issues." diff --git a/.github/workflows/issue-auto-implement-test.yml b/.github/workflows/issue-auto-implement-test.yml deleted file mode 100644 index 04ae6733..00000000 --- a/.github/workflows/issue-auto-implement-test.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Run issue-auto-implement assess script tests (unit tests, no secrets). -name: Issue auto-implement (assess tests) - -on: - pull_request: - branches: [main] - push: - branches: [main] - -jobs: - assess: - runs-on: ubuntu-latest - env: - GITHUB_WORKSPACE: ${{ github.workspace }} - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: '22' - cache: 'npm' - cache-dependency-path: .github/actions/issue-auto-implement/assess/package-lock.json - - name: Install and test assess script - working-directory: .github/actions/issue-auto-implement/assess - run: | - npm ci - npm test diff --git a/.github/workflows/issue-auto-implement.yml b/.github/workflows/issue-auto-implement.yml deleted file mode 100644 index 348e2036..00000000 --- a/.github/workflows/issue-auto-implement.yml +++ /dev/null @@ -1,46 +0,0 @@ -# Label-triggered issue automation: assess, implement-verify loop, create PR or iterate on PR. -# Triggers: issue labeled (automation/auto-implement), issue comment, PR review. -# Gate: set one of AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION (e.g. push; works with default token) or AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM (org/team; token needs read:org). -name: Issue auto-implement - -on: - issues: - types: [labeled] - issue_comment: - types: [created] - pull_request_review: - types: [submitted] - pull_request_review_comment: - types: [created] - -# Only run the job when the trigger label was added (not when our action adds needs-info/pr-created; those trigger a run but this if skips the job) -jobs: - run: - runs-on: ubuntu-latest - if: | - (github.event_name == 'issues' && github.event.label.name == 'automation/auto-implement') || - github.event_name == 'issue_comment' || - github.event_name == 'pull_request_review' || - github.event_name == 'pull_request_review_comment' - permissions: - contents: write - issues: write - pull-requests: write - # To allow push when implement touches .github/workflows/*: repo Settings → Actions → General → - # "Allow GitHub Actions to create and approve pull requests" (or use a PAT with workflow scope). No workflows: key in workflow syntax. - # read:org only needed if using team check (AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM) - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - name: Issue auto-implement - uses: ./.github/actions/issue-auto-implement - with: - anthropic_api_key: ${{ secrets.AUTO_IMPLEMENT_ANTHROPIC_API_KEY }} - github_token: ${{ secrets.GITHUB_TOKEN }} - push_token: ${{ secrets.AUTO_IMPLEMENT_GITHUB_PUSH_TOKEN }} - github_allowed_trigger_min_permission: ${{ vars.AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION }} - github_allowed_trigger_team: ${{ vars.AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM }} - # Skip acceptance tests in verify (they need HOOKDECK_CLI_TESTING_API_KEY); unit tests use -short - verify_commands: go test -short ./... diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index acdf4dab..e33ca8bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ on: pull_request: branches: - main - # Allow triggering CI on a branch (e.g. after auto-implement creates a PR, so checks appear) + # Allow manually triggering CI on a branch workflow_dispatch: {} jobs: