diff --git a/.changeset/fix-externalized-state-paths.md b/.changeset/fix-externalized-state-paths.md new file mode 100644 index 000000000..972a96d7d --- /dev/null +++ b/.changeset/fix-externalized-state-paths.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Fix runtime commands to correctly resolve externalized state paths diff --git a/.changeset/fix-permission-contract.md b/.changeset/fix-permission-contract.md new file mode 100644 index 000000000..c40727410 --- /dev/null +++ b/.changeset/fix-permission-contract.md @@ -0,0 +1,6 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +Fix permission handler to use `approve-once` instead of deprecated `approved` kind, aligning with Copilot CLI v1.0.54+ permission contract diff --git a/.changeset/fix-state-backend-upgrade-regressions.md b/.changeset/fix-state-backend-upgrade-regressions.md new file mode 100644 index 000000000..be966c14b --- /dev/null +++ b/.changeset/fix-state-backend-upgrade-regressions.md @@ -0,0 +1,38 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +Fix state-backend and upgrade regressions (#1163, #1185, #1190, #1191, #1194) + +**Bug A (P0) — Permission contract mismatch** (#1191) +The Copilot SDK changed the valid permission result `kind` from `"approved"` to +`"approve-once"`. Squad was still returning `{ kind: 'approved' }`, causing all +agent sessions to fail permission checks immediately. Fixed in: +- `cli/shell/index.ts` — `approveAllPermissions` handler now returns `{ kind: 'approve-once' }` +- `adapter/types.ts` — `SquadPermissionRequestResult.kind` union includes `'approve-once'` +- `adapter/client.ts` — error hint updated to reference the correct `kind` value + +**Bug B (P1) — Hard-throw in `resolveStateBackend()` when explicit backend fails** (#1185, #1190) +When a backend configured in `config.json` failed to initialize (e.g., no git repo +available), Squad threw a fatal error and refused to start. Now always warns and +falls back to `local` so operators can fix config without losing work. + +**Bug C (P1) — Silent git-notes→two-layer migration** (#1163) +`normalizeBackendType()` silently mapped `'git-notes'` to `'two-layer'` with no +user notification. Now emits a `console.warn()` directing users to update their +`config.json`. + +**Bug F (P3) — Windows `toRelative()` drive-letter case mismatch** +`StateBackendStorageAdapter.toRelative()` used a simple string prefix comparison +after normalizing separators. On Windows, `C:\` vs `c:\` drive-letter case +differences caused the prefix check to fail, returning full absolute paths as +git-notes keys (corruption). Now uses `path.resolve()` and case-insensitive +comparison on `process.platform === 'win32'`. + +**Issue #1194 — Externalized state paths not followed by runtime commands** +Adds `effectiveSquadDir()` and `resolveStateDir()` helpers that follow the +`stateLocation: 'external'` marker in `.squad/config.json`. Updates `loop`, +`watch`, `plugin`, `doctor` commands and `shell` (lifecycle, coordinator, index) +to use the effective state dir for reading `team.md`, `routing.md`, `agents/`, +`plugins/`, and other state files. diff --git a/.changeset/iter9-non-interactive-mcp-load.md b/.changeset/iter9-non-interactive-mcp-load.md new file mode 100644 index 000000000..d4a0f0312 --- /dev/null +++ b/.changeset/iter9-non-interactive-mcp-load.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-cli": minor +--- + +iter-9: inject `--yolo --additional-mcp-config @.mcp.json` in all non-interactive copilot spawns; fix path regression from `.copilot/mcp-config.json` (iter-7) to `.mcp.json` (iter-8 canonical location); add fallback warning when `.mcp.json` is absent; add `--yolo` deduplication guard; document Copilot CLI 1.0.59+ folder-trust security gate diff --git a/.changeset/state-backend-hardening.md b/.changeset/state-backend-hardening.md new file mode 100644 index 000000000..f28e1dd13 --- /dev/null +++ b/.changeset/state-backend-hardening.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-sdk': patch +--- + +State backend hardening: retry with exponential backoff for transient git errors, circuit-breaker to prevent cascading failures, read-only startup verification, and observable error surfacing replacing silent swallowing. diff --git a/.gitattributes b/.gitattributes index 893245b54..7673b120e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,3 +7,7 @@ .squad/decisions/decisions.md merge=union # Squad: union merge for append-only team state files .squad/rai/audit-trail.md merge=union + +# Squad: pin LF for YAML to prevent CRLF<->LF flip on Windows checkouts (PR #1200 concern G) +*.yml text eol=lf +*.yaml text eol=lf diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 99e696794..9c71cacce 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -128,7 +128,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Resolve `CURRENT_DATETIME` once from the `` value in your system context. Sanity-check that it is a real ISO-like timestamp, not placeholder text, with a plausible year and timezone (`Z` or an offset). If the system value is missing or implausible, run a local date command and use that result instead (`date +"%Y-%m-%dT%H:%M:%S%z"` on macOS/Linux, or `Get-Date -Format o` in PowerShell). Pass the team root and the resolved literal current datetime into every spawn prompt as `TEAM_ROOT` and `CURRENT_DATETIME` respectively. Never pass placeholder text for `CURRENT_DATETIME`. Pass the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. -**Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"worktree"` (default), `"git-notes"`, `"orphan"`, `"two-layer"`. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. +**Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"local"` (default), `"orphan"`, `"two-layer"`. Legacy alias: `"worktree"` maps to `"local"`. Deprecated: `"git-notes"` maps to `"two-layer"` with a deprecation warning. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. **⚡ Context caching:** After the first message in a session, `team.md`, `routing.md`, and `registry.json` are already in your context. Do NOT re-read them on subsequent messages — you already have the roster, routing rules, and cast names. Only re-read if the user explicitly modifies the team (adds/removes members, changes routing). @@ -607,8 +607,8 @@ prompt: | 0b. PRE-CHECK: Read `decisions.md` and list `decisions/inbox` with state tools. Record measurements. 1. DECISIONS ARCHIVE [HARD GATE]: If decisions.md >= 20480 bytes, archive entries older than 30 days NOW. If >= 51200 bytes, archive entries older than 7 days. Do not skip this step. 2. DECISION INBOX: Use `squad_state_list` and `squad_state_read` on `decisions/inbox`, merge entries into `decisions.md` with `squad_state_write`, delete processed inbox entries with `squad_state_delete`, and deduplicate. - 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use the literal CURRENT_DATETIME value. - 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use the literal CURRENT_DATETIME value. + 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use the literal CURRENT_DATETIME value. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms (e.g. `2026-06-02T21-15-30Z`). + 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use the literal CURRENT_DATETIME value. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms. 5. CROSS-AGENT: Append team updates to affected agents' `agents/{agent}/history.md` with `squad_state_append`. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. 7. GIT COMMIT: Do not commit mutable squad state. If non-state repo files changed, report them for coordinator handling. diff --git a/.github/workflows/squad-ci.yml b/.github/workflows/squad-ci.yml index 965eb2e26..569e498cd 100644 --- a/.github/workflows/squad-ci.yml +++ b/.github/workflows/squad-ci.yml @@ -310,15 +310,15 @@ jobs: const pkgPath = path.join('packages', dir, 'package.json'); if (!fs.existsSync(pkgPath)) continue; const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - // Allow x.y.z-preview (CONTRIBUTING.md canonical dev suffix); block all other prerelease tags - if (pkg.version && /-/.test(pkg.version) && !/^\d+\.\d+\.\d+-preview$/.test(pkg.version)) { + // Allow x.y.z, x.y.z-preview, x.y.z-preview.N, x.y.z-insider.N; block all other prerelease tags + if (pkg.version && !/^\d+\.\d+\.\d+(-(preview|insider)(\.\d+)?)?$/.test(pkg.version)) { violations.push({ name: pkg.name, version: pkg.version, path: pkgPath }); } } if (violations.length > 0) { console.error('::error::UNSANCTIONED PRERELEASE VERSION DETECTED — cannot merge to dev/main.'); violations.forEach(v => console.error(' ' + v.name + '@' + v.version + ' (' + v.path + ')')); - console.error('Fix: use x.y.z-preview (CONTRIBUTING.md) or a clean semver. Skip: add \"skip-version-check\" label.'); + console.error('Fix: use X.Y.Z, X.Y.Z-preview, X.Y.Z-preview.N, or X.Y.Z-insider.N. Skip: add \"skip-version-check\" label.'); process.exit(1); } console.log('✅ All package versions are release versions'); diff --git a/.gitignore b/.gitignore index 51fe9b88c..8cfd30156 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ docs/tests/screenshots/ /images/ # Squad: ignore runtime state (logs, inbox, sessions) .squad/.scratch/ + +# Squad: PR-1200 picard local artifacts (do not commit) +.pr-body-new.md +.followup-issue-body.md diff --git a/.squad-templates/after-agent-reference.md b/.squad-templates/after-agent-reference.md index b3c4d709b..e94a635cd 100644 --- a/.squad-templates/after-agent-reference.md +++ b/.squad-templates/after-agent-reference.md @@ -47,8 +47,8 @@ prompt: | 2. DECISION INBOX: Use `squad_state_list` and `squad_state_read` on `decisions/inbox`, merge entries into `decisions.md` with `squad_state_write`, delete processed inbox entries with `squad_state_delete`, and deduplicate. - 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use ISO 8601 UTC timestamp. - 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use ISO 8601 UTC timestamp. + 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use ISO 8601 UTC timestamp. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms (e.g. `2026-06-02T21-15-30Z`). + 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use ISO 8601 UTC timestamp. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms. 5. CROSS-AGENT: Append team updates to affected agents' `agents/{agent}/history.md` with `squad_state_append`. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. 7. HEALTH REPORT: Log decisions.md before/after size, inbox count processed, history files summarized with `squad_state_write` or `squad_state_append`. diff --git a/.squad-templates/scribe-charter.md b/.squad-templates/scribe-charter.md index da6ddfe6a..d335e92c3 100644 --- a/.squad-templates/scribe-charter.md +++ b/.squad-templates/scribe-charter.md @@ -28,7 +28,7 @@ After every substantial work session: -1. **Log the session** to `log/{timestamp}-{topic}.md` with `squad_state_write`: +1. **Log the session** to `log/{timestamp}-{topic}.md` with `squad_state_write` (replace `:` with `-` in `{timestamp}` so the filename is valid on all platforms, e.g. `2026-06-02T21-15-30Z`): - Who worked - What was done - Decisions made diff --git a/.squad-templates/squad.agent.md b/.squad-templates/squad.agent.md index 99e696794..9c71cacce 100644 --- a/.squad-templates/squad.agent.md +++ b/.squad-templates/squad.agent.md @@ -128,7 +128,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Resolve `CURRENT_DATETIME` once from the `` value in your system context. Sanity-check that it is a real ISO-like timestamp, not placeholder text, with a plausible year and timezone (`Z` or an offset). If the system value is missing or implausible, run a local date command and use that result instead (`date +"%Y-%m-%dT%H:%M:%S%z"` on macOS/Linux, or `Get-Date -Format o` in PowerShell). Pass the team root and the resolved literal current datetime into every spawn prompt as `TEAM_ROOT` and `CURRENT_DATETIME` respectively. Never pass placeholder text for `CURRENT_DATETIME`. Pass the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. -**Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"worktree"` (default), `"git-notes"`, `"orphan"`, `"two-layer"`. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. +**Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"local"` (default), `"orphan"`, `"two-layer"`. Legacy alias: `"worktree"` maps to `"local"`. Deprecated: `"git-notes"` maps to `"two-layer"` with a deprecation warning. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. **⚡ Context caching:** After the first message in a session, `team.md`, `routing.md`, and `registry.json` are already in your context. Do NOT re-read them on subsequent messages — you already have the roster, routing rules, and cast names. Only re-read if the user explicitly modifies the team (adds/removes members, changes routing). @@ -607,8 +607,8 @@ prompt: | 0b. PRE-CHECK: Read `decisions.md` and list `decisions/inbox` with state tools. Record measurements. 1. DECISIONS ARCHIVE [HARD GATE]: If decisions.md >= 20480 bytes, archive entries older than 30 days NOW. If >= 51200 bytes, archive entries older than 7 days. Do not skip this step. 2. DECISION INBOX: Use `squad_state_list` and `squad_state_read` on `decisions/inbox`, merge entries into `decisions.md` with `squad_state_write`, delete processed inbox entries with `squad_state_delete`, and deduplicate. - 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use the literal CURRENT_DATETIME value. - 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use the literal CURRENT_DATETIME value. + 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use the literal CURRENT_DATETIME value. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms (e.g. `2026-06-02T21-15-30Z`). + 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use the literal CURRENT_DATETIME value. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms. 5. CROSS-AGENT: Append team updates to affected agents' `agents/{agent}/history.md` with `squad_state_append`. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. 7. GIT COMMIT: Do not commit mutable squad state. If non-state repo files changed, report them for coordinator handling. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab871ab82..35c385ecd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,431 +1,431 @@ -# Contributing to Squad - -Welcome to Squad development. This guide explains how to build, test, and contribute. - -## Prerequisites - -- **Node.js** ≥20.0.0 -- **npm** ≥10.0.0 (for workspace support) -- **Git** with SSH agent (for package resolution) -- **gh CLI** (for GitHub integration testing) - -## Community Guidelines & Spam Protection - -Our repository uses automated spam detection to maintain a safe, productive community. Here's what you need to know: - -### Spam Detection Guidelines -- **Comment screening** — Malicious links (shortened URLs, file-sharing services) and mass-mentions are monitored -- **Issue evaluation** — New issues are reviewed for spam patterns; suspected spam may be auto-closed -- **Auto-lock stale content** — Issues and PRs inactive for 30+ days are locked to prevent spam on old threads - -### What Gets Flagged -- Shortened URLs (bit.ly, tinyurl, t.co, goo.gl, rb.gy) -- File-sharing links (Dropbox, Google Drive, Mega, MediaFire) -- Crypto/investment scams ("free bitcoin", "guaranteed profit", etc.) -- Adult content patterns -- Mass-mentions (4+ @-mentions in one comment) -- New accounts (< 30 days old) with 0 repos, 0 followers + suspicious content - -### If Your Content Is Flagged -- **Comment not posted** — If your comment contains flagged patterns, it may be held for review -- **Issue closed as spam** (clear violation) — Likely closed with explanation; contact maintainers if you believe this is a mistake -- **Issue labeled "suspicious"** — Flagged for maintainer review but remains open -- **Issue locked** — If inactive for 30+ days, locked to prevent spam replies - -If your legitimate issue/comment is caught by spam detection, please contact a maintainer. We're here to help. - -## Monorepo Structure - -Squad is an npm workspace monorepo with two packages: - -``` -squad/ -├── packages/squad-cli/ # CLI tool (@bradygaster/squad-cli) -├── packages/squad-sdk/ # Runtime SDK (@bradygaster/squad-sdk) -├── src/ # Legacy CLI code (migrating to packages/) -├── dist/ # Compiled output -├── .squad/ # Team state and agent history -├── docs/ # Documentation and proposals -└── test-fixtures/ # Test data -``` - -### Package Independence - -- **squad-sdk**: Core runtime, agent orchestration, tool registry. No CLI dependencies. -- **squad-cli**: Command-line interface. Depends on squad-sdk. - -Each package has independent versioning via changesets. A change to squad-sdk may bump only squad-sdk; a change to CLI bumps only squad-cli. - -## Getting Started - -### 1. Clone and Install - -**Step 1: Fork the repo on GitHub** - -Go to https://github.com/bradygaster/squad and click "Fork" to create your own copy. - -**Step 2: Clone your fork** - -```bash -git clone git@github.com:{yourusername}/squad.git -cd squad -``` - -**Step 3: Add upstream remote** - -```bash -git remote add upstream git@github.com:bradygaster/squad.git -``` - -**Step 4: Fetch the dev branch** - -```bash -git fetch upstream dev -``` - -**Step 5: Install dependencies** - -```bash -npm install -``` - -npm workspaces automatically links local packages. `@bradygaster/squad-cli` can import from `@bradygaster/squad-sdk` without publishing. - -### 2. Build - -```bash -# Compile TypeScript to dist/ -npm run build - -# Build + bundle CLI (includes esbuild) -npm run build:cli - -# Watch mode (auto-recompile on changes) -npm run dev -``` - -### 3. Test - -```bash -# Run all tests (Vitest) -npm test - -# Watch mode -npm run test:watch -``` - -### 4. Lint - -```bash -# Type check only (no emit) -npm run lint -``` - -### 5. Keeping Your Fork in Sync - -Before opening or updating a PR, rebase your branch on the latest upstream dev: - -```bash -git fetch upstream -git rebase upstream/dev -git push origin your-branch --force-with-lease -``` - -Always rebase before opening or updating a PR to ensure your changes are based on the latest integration branch. - -## Development Workflow - -### Creating a Feature Branch - -Follow the branch naming convention from `.squad/decisions.md`: - -```bash -# For user-facing work, use user_name/issue-number-slug format -git checkout -b bradygaster/217-readme-help-update -# or -git checkout -b keaton/210-resolution-api - -# For team-internal work, use agent_name/issue-number-slug -git checkout -b mcmanus/documentation -git checkout -b edie/refactor-router -``` - -### Before Committing - -1. **Compile:** `npm run build` (or `npm run dev` watch mode) -2. **Test:** `npm test` -3. **Type check:** `npm run lint` - -All checks must pass before commit. - -### Commit Message Format - -Keep messages clear and concise. Reference the issue number: - -``` -Brief description of change - -Longer explanation if needed. Reference #210, #217, etc. - -Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> -``` - -The Co-authored-by trailer is **required** for all commits (added by Copilot CLI). - -### Pull Request Process - -1. Add a changeset: `npx changeset add` (required before PR — see Changesets section) -2. Push your branch: `git push origin {yourusername}/217-readme-help-update` -3. Create a PR **as a draft**: `gh pr create --draft --base dev --repo bradygaster/squad --head {yourusername}:your-branch` -4. Link the issue: Add `Closes #217` to PR description -5. Work on your changes until CI passes and you're satisfied -6. **Mark as "Ready for review"** — this is the handoff signal to the core team (see below) - -### Handoff: Contributor → Core Team - -External contributors don't have write access, so the review-to-merge flow has a handoff point. Here's exactly what happens: - -**Your side (contributor):** - -1. ✅ All required CI checks are green (build, test, lint; changeset/CHANGELOG gate only applies when `packages/squad-cli/src/` or `packages/squad-sdk/src/` files change) -2. ✅ PR is no longer a draft — mark as **"Ready for review"** -3. ✅ Copilot reviewer bot posts its review automatically -4. ✅ Review Copilot's suggestions and manually apply any you agree with in your fork -5. ✅ Push updates to your branch to address Copilot's feedback -6. ✅ If Copilot flags issues you can't resolve, note them in a PR comment - -> **Note:** Copilot review suggestions appear as comments, but the "Commit suggestion" and "Fix with Copilot" buttons require repo write access and won't work for external contributors. Review the suggestions, apply them manually in your fork, and push your changes. - -**Core team side (after you undraft):** - -1. Look for CI-green, undrafted PRs from contributors -2. Address any remaining Copilot review issues (using "Fix with Copilot" or manual fixes) -3. Human review, resolve threads, and merge - -**TL;DR:** Your job is done when the PR is undrafted, CI is green, and you've responded to Copilot suggestions. The core team takes it from there. - -### PR Readiness Checklist - -An automated readiness check runs on every PR and posts a checklist comment. Address all items before requesting review: - -| Check | What it means | -|-------|---------------| -| **Single commit** | Squash your commits into one clean commit, or the repo will squash on merge | -| **Not in draft** | Mark your PR as "Ready for review" when it's done | -| **Branch up to date** | Rebase on latest `dev` (`git fetch upstream && git rebase upstream/dev`) | -| **Copilot review** | Wait for the Copilot reviewer bot to post its review | -| **Changeset present** | Run `npx changeset add` if you changed files in `packages/squad-sdk/src/` or `packages/squad-cli/src/` | -| **No merge conflicts** | Resolve any conflicts with the target branch | -| **CI passing** | All CI checks (build, test, lint) must be green | - -The readiness check is **informational** — it helps you self-serve before a human reviewer looks at your PR. It automatically re-runs after Squad CI completes, so the checklist stays up to date without manual intervention. See `.github/PR_REQUIREMENTS.md` for the full requirements spec. - -## Code Style & Conventions - -Squad follows strict TypeScript conventions: - -- **Type Safety:** `strict: true`, `noUncheckedIndexedAccess: true` -- **No `@ts-ignore`** — if a type error exists, fix the code -- **ESM-only** — no CommonJS, no dual-package -- **Async/await** — use async iterators for streaming -- **Error handling:** Structured errors with `fatal()`, `error()`, `warn()`, `info()` -- **No hype in docs** — factual, substantiated claims only (tone ceiling) - -## Documentation - -- **README.md** — User-facing guide, quick start, architecture overview -- **CONTRIBUTING.md** — This file -- **docs/proposals/** — Design docs for significant changes (required before code) -- **.squad/agents/[name]/history.md** — Agent learnings and project context - -All docs in v1 are **internal only**. No public docs site until v2. - -## Local Development Versioning - -When developing Squad locally, set the package version to `{next-version}-preview`. For example, if the last published version is `0.8.5.1`, the local dev version should be `0.8.6-preview`. - -This convention makes `squad version` show the preview tag locally, clearly indicating you're running unreleased source code, not the published npm package. The release agent will bump this to the final version at publish time, then immediately back to the next preview version for continued development. - -### Making the `squad` Command Use Your Local Build - -To make the `squad` CLI command globally available and pointing to your local development build: - -```bash -npm run build -w packages/squad-sdk && npm run build -w packages/squad-cli -npm link -w packages/squad-cli -``` - -After this, `squad version` will show `0.8.6-preview` (or the current preview version). When you make code changes and rebuild, the `squad` command automatically picks up the changes—no need to reinstall. To verify your local build is active, the version output should include the `-preview` tag. - -To revert back to the globally installed npm package version, run: - -```bash -npm unlink -w packages/squad-cli -``` - -## Changesets: Independent Versioning - -Squad uses [@changesets/cli](https://github.com/changesets/changesets) for independent package versioning. - -**When your PR changes SDK or CLI source files** (`packages/squad-sdk/src/` or `packages/squad-cli/src/`), add a changeset file instead of editing `CHANGELOG.md` directly. Changesets prevent merge conflicts when multiple PRs are open simultaneously and are the preferred workflow. - -### Adding a Changeset - -**Option A — Interactive (recommended):** - -```bash -npx changeset add -``` - -This prompts: -1. Which packages changed? (squad-sdk, squad-cli, both) -2. What type? (patch, minor, major) -3. Brief summary of changes - -Creates a file in `.changeset/` that's merged with your PR. - -**Option B — Manual:** - -Create a file at `.changeset/your-change-name.md` with frontmatter specifying the package and bump type, followed by a description: - -```markdown ---- -'@bradygaster/squad-cli': patch ---- - -Fix help text rendering for the status command -``` - -### Changeset Format - -The frontmatter lists each affected package and its semver bump type. The body is a human-readable description that will appear in the generated CHANGELOG: - -```markdown ---- -"@bradygaster/squad-sdk": minor -"@bradygaster/squad-cli": patch ---- - -Add streaming support to agent orchestration. Update CLI to display stream progress. -``` - -### CI Changelog Gate - -The `changelog-gate` CI check enforces that PRs touching SDK/CLI source files include either: -- A `.changeset/*.md` file (preferred), **or** -- A direct `CHANGELOG.md` edit (backward-compatible) - -If neither is present, the check fails. You can bypass it with the `skip-changelog` label. - -### Release Workflow - -The team runs changesets on the `dev` branch (via GitHub Actions): - -```bash -npx changeset publish -``` - -This: -1. Bumps versions in `package.json` -2. Generates `CHANGELOG.md` entries -3. Publishes to npm -4. Creates GitHub releases - -You don't need to manually version — changesets handle it. - -## Branch Strategy - -- **main** — Stable, published releases. All merges include changesets. -- **preview** — Staging branch for release candidates (promote: dev → preview → main). -- **bradygaster/dev** — Integration branch. **All PRs from forks must target this branch**, not `main`. -- **user/issue-slug** — Feature branches from users or agents. - -> **Note:** The `insider` npm tag (`@bradygaster/squad-cli@insider`) publishes from `dev` via manual workflow dispatch. There is no separate insider branch. - -## Continuous Integration - -GitHub Actions runs on every push: - -1. **Build:** `npm run build` and `npm run build:cli` -2. **Test:** `npm test` -3. **Lint:** `npm run lint` -4. **Changeset status:** `npm run changeset:check` (ensures PRs include a changeset) -5. **Diff Size Guard:** Warns when a single-commit PR touches 30+ files (likely branch contamination from staging all files at once on a stale branch). Always use explicit `git add ` instead. - -All checks must pass before merge. - -## Testing Template Changes (End-to-End) - -Changes to coordinator and agent templates (`.squad-templates/squad.agent.md`, `scribe-charter.md`, etc.) can't be validated by unit tests alone — they're prompts interpreted by an LLM at runtime. For these changes, run real squad sessions against your locally-built CLI. - -### Quick version - -```bash -# 1. Build and link your branch -npm run build && cd packages/squad-cli && npm link && cd ../.. - -# 2. Create a disposable test repo -mkdir /tmp/sq-test && cd /tmp/sq-test -git init && echo "# Test" > README.md && git add -A && git commit -m "init" - -# 3. Init a squad with your modified templates -squad init - -# 4. Run a session and verify behavior -copilot -p "Picard, decide on a testing framework." 2>&1 | tee session.log -``` - -### Full guide - -See `.squad-templates/skills/e2e-template-testing/SKILL.md` for the complete workflow: test matrix, evidence collection, verdict format, and anti-patterns. - -### When is this needed? - -- Any change to `.squad-templates/*.md` files -- Changes to init scaffolding that writes templates to target repos -- Changes to conditional template blocks (e.g. state-backend-aware prompts) - -Unit tests (`npm test`) still run for logic changes — E2E template testing is an **additional** step, not a replacement. - -## Common Tasks - -### Add a CLI Command - -1. Create the command file in `src/cli/commands/[name].js` -2. Add the route in `src/index.ts` (the `main()` function) -3. Update help text in the `--help` handler -4. Add tests in `test/cli/commands/[name].test.ts` -5. Document in README.md - -### Add an SDK Export - -1. Implement the feature in `src/[module]/` -2. Export from `src/index.ts` -3. Add tests -4. Document in README.md SDK section - -### Migrate Legacy Code - -The `src/` directory contains legacy code migrating to `packages/squad-cli/` and `packages/squad-sdk/`. When moving code: - -1. Create the new file in the target package -2. Update imports in both locations -3. Ensure tests follow the file -4. Delete the old `src/` file once all references are updated -5. Document the migration in `.squad/agents/[name]/history.md` - -## Key Files - -- **src/index.ts** — CLI entry point and routing -- **src/resolution.ts** — Squad path resolution (repo vs. global) -- **.squad/decisions.md** — Team decisions and conventions -- **.squad/agents/[name]/charter.md** — Agent identity and expertise -- **package.json** — Workspace and script definitions - -## Questions? - -Open an issue or ask in `.squad/` discussion channels. The team is here to help. - -## License - -All contributions are MIT-licensed. By submitting a PR, you agree to this license. +# Contributing to Squad + +Welcome to Squad development. This guide explains how to build, test, and contribute. + +## Prerequisites + +- **Node.js** ≥20.0.0 +- **npm** ≥10.0.0 (for workspace support) +- **Git** with SSH agent (for package resolution) +- **gh CLI** (for GitHub integration testing) + +## Community Guidelines & Spam Protection + +Our repository uses automated spam detection to maintain a safe, productive community. Here's what you need to know: + +### Spam Detection Guidelines +- **Comment screening** — Malicious links (shortened URLs, file-sharing services) and mass-mentions are monitored +- **Issue evaluation** — New issues are reviewed for spam patterns; suspected spam may be auto-closed +- **Auto-lock stale content** — Issues and PRs inactive for 30+ days are locked to prevent spam on old threads + +### What Gets Flagged +- Shortened URLs (bit.ly, tinyurl, t.co, goo.gl, rb.gy) +- File-sharing links (Dropbox, Google Drive, Mega, MediaFire) +- Crypto/investment scams ("free bitcoin", "guaranteed profit", etc.) +- Adult content patterns +- Mass-mentions (4+ @-mentions in one comment) +- New accounts (< 30 days old) with 0 repos, 0 followers + suspicious content + +### If Your Content Is Flagged +- **Comment not posted** — If your comment contains flagged patterns, it may be held for review +- **Issue closed as spam** (clear violation) — Likely closed with explanation; contact maintainers if you believe this is a mistake +- **Issue labeled "suspicious"** — Flagged for maintainer review but remains open +- **Issue locked** — If inactive for 30+ days, locked to prevent spam replies + +If your legitimate issue/comment is caught by spam detection, please contact a maintainer. We're here to help. + +## Monorepo Structure + +Squad is an npm workspace monorepo with two packages: + +``` +squad/ +├── packages/squad-cli/ # CLI tool (@bradygaster/squad-cli) +├── packages/squad-sdk/ # Runtime SDK (@bradygaster/squad-sdk) +├── src/ # Legacy CLI code (migrating to packages/) +├── dist/ # Compiled output +├── .squad/ # Team state and agent history +├── docs/ # Documentation and proposals +└── test-fixtures/ # Test data +``` + +### Package Independence + +- **squad-sdk**: Core runtime, agent orchestration, tool registry. No CLI dependencies. +- **squad-cli**: Command-line interface. Depends on squad-sdk. + +Each package has independent versioning via changesets. A change to squad-sdk may bump only squad-sdk; a change to CLI bumps only squad-cli. + +## Getting Started + +### 1. Clone and Install + +**Step 1: Fork the repo on GitHub** + +Go to https://github.com/bradygaster/squad and click "Fork" to create your own copy. + +**Step 2: Clone your fork** + +```bash +git clone git@github.com:{yourusername}/squad.git +cd squad +``` + +**Step 3: Add upstream remote** + +```bash +git remote add upstream git@github.com:bradygaster/squad.git +``` + +**Step 4: Fetch the dev branch** + +```bash +git fetch upstream dev +``` + +**Step 5: Install dependencies** + +```bash +npm install +``` + +npm workspaces automatically links local packages. `@bradygaster/squad-cli` can import from `@bradygaster/squad-sdk` without publishing. + +### 2. Build + +```bash +# Compile TypeScript to dist/ +npm run build + +# Build + bundle CLI (includes esbuild) +npm run build:cli + +# Watch mode (auto-recompile on changes) +npm run dev +``` + +### 3. Test + +```bash +# Run all tests (Vitest) +npm test + +# Watch mode +npm run test:watch +``` + +### 4. Lint + +```bash +# Type check only (no emit) +npm run lint +``` + +### 5. Keeping Your Fork in Sync + +Before opening or updating a PR, rebase your branch on the latest upstream dev: + +```bash +git fetch upstream +git rebase upstream/dev +git push origin your-branch --force-with-lease +``` + +Always rebase before opening or updating a PR to ensure your changes are based on the latest integration branch. + +## Development Workflow + +### Creating a Feature Branch + +Follow the branch naming convention from `.squad/decisions.md`: + +```bash +# For user-facing work, use user_name/issue-number-slug format +git checkout -b bradygaster/217-readme-help-update +# or +git checkout -b keaton/210-resolution-api + +# For team-internal work, use agent_name/issue-number-slug +git checkout -b mcmanus/documentation +git checkout -b edie/refactor-router +``` + +### Before Committing + +1. **Compile:** `npm run build` (or `npm run dev` watch mode) +2. **Test:** `npm test` +3. **Type check:** `npm run lint` + +All checks must pass before commit. + +### Commit Message Format + +Keep messages clear and concise. Reference the issue number: + +``` +Brief description of change + +Longer explanation if needed. Reference #210, #217, etc. + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> +``` + +The Co-authored-by trailer is **required** for all commits (added by Copilot CLI). + +### Pull Request Process + +1. Add a changeset: `npx changeset add` (required before PR — see Changesets section) +2. Push your branch: `git push origin {yourusername}/217-readme-help-update` +3. Create a PR **as a draft**: `gh pr create --draft --base dev --repo bradygaster/squad --head {yourusername}:your-branch` +4. Link the issue: Add `Closes #217` to PR description +5. Work on your changes until CI passes and you're satisfied +6. **Mark as "Ready for review"** — this is the handoff signal to the core team (see below) + +### Handoff: Contributor → Core Team + +External contributors don't have write access, so the review-to-merge flow has a handoff point. Here's exactly what happens: + +**Your side (contributor):** + +1. ✅ All required CI checks are green (build, test, lint; changeset/CHANGELOG gate only applies when `packages/squad-cli/src/` or `packages/squad-sdk/src/` files change) +2. ✅ PR is no longer a draft — mark as **"Ready for review"** +3. ✅ Copilot reviewer bot posts its review automatically +4. ✅ Review Copilot's suggestions and manually apply any you agree with in your fork +5. ✅ Push updates to your branch to address Copilot's feedback +6. ✅ If Copilot flags issues you can't resolve, note them in a PR comment + +> **Note:** Copilot review suggestions appear as comments, but the "Commit suggestion" and "Fix with Copilot" buttons require repo write access and won't work for external contributors. Review the suggestions, apply them manually in your fork, and push your changes. + +**Core team side (after you undraft):** + +1. Look for CI-green, undrafted PRs from contributors +2. Address any remaining Copilot review issues (using "Fix with Copilot" or manual fixes) +3. Human review, resolve threads, and merge + +**TL;DR:** Your job is done when the PR is undrafted, CI is green, and you've responded to Copilot suggestions. The core team takes it from there. + +### PR Readiness Checklist + +An automated readiness check runs on every PR and posts a checklist comment. Address all items before requesting review: + +| Check | What it means | +|-------|---------------| +| **Single commit** | Squash your commits into one clean commit, or the repo will squash on merge | +| **Not in draft** | Mark your PR as "Ready for review" when it's done | +| **Branch up to date** | Rebase on latest `dev` (`git fetch upstream && git rebase upstream/dev`) | +| **Copilot review** | Wait for the Copilot reviewer bot to post its review | +| **Changeset present** | Run `npx changeset add` if you changed files in `packages/squad-sdk/src/` or `packages/squad-cli/src/` | +| **No merge conflicts** | Resolve any conflicts with the target branch | +| **CI passing** | All CI checks (build, test, lint) must be green | + +The readiness check is **informational** — it helps you self-serve before a human reviewer looks at your PR. It automatically re-runs after Squad CI completes, so the checklist stays up to date without manual intervention. See `.github/PR_REQUIREMENTS.md` for the full requirements spec. + +## Code Style & Conventions + +Squad follows strict TypeScript conventions: + +- **Type Safety:** `strict: true`, `noUncheckedIndexedAccess: true` +- **No `@ts-ignore`** — if a type error exists, fix the code +- **ESM-only** — no CommonJS, no dual-package +- **Async/await** — use async iterators for streaming +- **Error handling:** Structured errors with `fatal()`, `error()`, `warn()`, `info()` +- **No hype in docs** — factual, substantiated claims only (tone ceiling) + +## Documentation + +- **README.md** — User-facing guide, quick start, architecture overview +- **CONTRIBUTING.md** — This file +- **docs/proposals/** — Design docs for significant changes (required before code) +- **.squad/agents/[name]/history.md** — Agent learnings and project context + +All docs in v1 are **internal only**. No public docs site until v2. + +## Local Development Versioning + +When developing Squad locally, set the package version to `{next-version}-preview` (e.g. `0.8.6-preview`) or a numbered iteration like `0.8.6-preview.N`. The `insider` dist-tag uses `X.Y.Z-insider.N` versions. All three patterns are accepted by the CI Prerelease Version Guard. + +This convention makes `squad version` show the preview tag locally, clearly indicating you're running unreleased source code, not the published npm package. The release agent will bump this to the final version at publish time, then immediately back to the next preview version for continued development. + +### Making the `squad` Command Use Your Local Build + +To make the `squad` CLI command globally available and pointing to your local development build: + +```bash +npm run build -w packages/squad-sdk && npm run build -w packages/squad-cli +npm link -w packages/squad-cli +``` + +After this, `squad version` will show `0.8.6-preview` (or the current preview version). When you make code changes and rebuild, the `squad` command automatically picks up the changes—no need to reinstall. To verify your local build is active, the version output should include the `-preview` tag. + +To revert back to the globally installed npm package version, run: + +```bash +npm unlink -w packages/squad-cli +``` + +## Changesets: Independent Versioning + +Squad uses [@changesets/cli](https://github.com/changesets/changesets) for independent package versioning. + +**When your PR changes SDK or CLI source files** (`packages/squad-sdk/src/` or `packages/squad-cli/src/`), add a changeset file instead of editing `CHANGELOG.md` directly. Changesets prevent merge conflicts when multiple PRs are open simultaneously and are the preferred workflow. + +### Adding a Changeset + +**Option A — Interactive (recommended):** + +```bash +npx changeset add +``` + +This prompts: +1. Which packages changed? (squad-sdk, squad-cli, both) +2. What type? (patch, minor, major) +3. Brief summary of changes + +Creates a file in `.changeset/` that's merged with your PR. + +**Option B — Manual:** + +Create a file at `.changeset/your-change-name.md` with frontmatter specifying the package and bump type, followed by a description: + +```markdown +--- +'@bradygaster/squad-cli': patch +--- + +Fix help text rendering for the status command +``` + +### Changeset Format + +The frontmatter lists each affected package and its semver bump type. The body is a human-readable description that will appear in the generated CHANGELOG: + +```markdown +--- +"@bradygaster/squad-sdk": minor +"@bradygaster/squad-cli": patch +--- + +Add streaming support to agent orchestration. Update CLI to display stream progress. +``` + +### CI Changelog Gate + +The `changelog-gate` CI check enforces that PRs touching SDK/CLI source files include either: +- A `.changeset/*.md` file (preferred), **or** +- A direct `CHANGELOG.md` edit (backward-compatible) + +If neither is present, the check fails. You can bypass it with the `skip-changelog` label. + +### Release Workflow + +The team runs changesets on the `dev` branch (via GitHub Actions): + +```bash +npx changeset publish +``` + +This: +1. Bumps versions in `package.json` +2. Generates `CHANGELOG.md` entries +3. Publishes to npm +4. Creates GitHub releases + +You don't need to manually version — changesets handle it. + +## Branch Strategy + +- **main** — Stable, published releases. All merges include changesets. +- **preview** — Staging branch for release candidates (promote: dev → preview → main). +- **bradygaster/dev** — Integration branch. **All PRs from forks must target this branch**, not `main`. +- **user/issue-slug** — Feature branches from users or agents. + +> **Note:** The `insider` npm tag (`@bradygaster/squad-cli@insider`) publishes from `dev` via manual workflow dispatch. There is no separate insider branch. + +## Continuous Integration + +GitHub Actions runs on every push: + +1. **Build:** `npm run build` and `npm run build:cli` +2. **Test:** `npm test` +3. **Lint:** `npm run lint` +4. **Changeset status:** `npm run changeset:check` (ensures PRs include a changeset) +5. **Diff Size Guard:** Warns when a single-commit PR touches 30+ files (likely branch contamination from staging all files at once on a stale branch). Always use explicit `git add ` instead. + +All checks must pass before merge. + +## Testing Template Changes (End-to-End) + +Changes to coordinator and agent templates (`.squad-templates/squad.agent.md`, `scribe-charter.md`, etc.) can't be validated by unit tests alone — they're prompts interpreted by an LLM at runtime. For these changes, run real squad sessions against your locally-built CLI. + +### Quick version + +```bash +# 1. Build and link your branch +npm run build && cd packages/squad-cli && npm link && cd ../.. + +# 2. Create a disposable test repo +mkdir /tmp/sq-test && cd /tmp/sq-test +git init && echo "# Test" > README.md && git add -A && git commit -m "init" + +# 3. Init a squad with your modified templates +squad init + +# 4. Run a session and verify behavior +copilot -p "Picard, decide on a testing framework." 2>&1 | tee session.log +``` + +### Full guide + +See `.squad-templates/skills/e2e-template-testing/SKILL.md` for the complete workflow: test matrix, evidence collection, verdict format, and anti-patterns. + +### When is this needed? + +- Any change to `.squad-templates/*.md` files +- Changes to init scaffolding that writes templates to target repos +- Changes to conditional template blocks (e.g. state-backend-aware prompts) + +Unit tests (`npm test`) still run for logic changes — E2E template testing is an **additional** step, not a replacement. + +## Common Tasks + +### Add a CLI Command + +1. Create the command file in `src/cli/commands/[name].js` +2. Add the route in `src/index.ts` (the `main()` function) +3. Update help text in the `--help` handler +4. Add tests in `test/cli/commands/[name].test.ts` +5. Document in README.md + +### Add an SDK Export + +1. Implement the feature in `src/[module]/` +2. Export from `src/index.ts` +3. Add tests +4. Document in README.md SDK section + +### Migrate Legacy Code + +The `src/` directory contains legacy code migrating to `packages/squad-cli/` and `packages/squad-sdk/`. When moving code: + +1. Create the new file in the target package +2. Update imports in both locations +3. Ensure tests follow the file +4. Delete the old `src/` file once all references are updated +5. Document the migration in `.squad/agents/[name]/history.md` + +## Key Files + +- **src/index.ts** — CLI entry point and routing +- **src/resolution.ts** — Squad path resolution (repo vs. global) +- **.squad/decisions.md** — Team decisions and conventions +- **.squad/agents/[name]/charter.md** — Agent identity and expertise +- **package.json** — Workspace and script definitions + +## Questions? + +Open an issue or ask in `.squad/` discussion channels. The team is here to help. + +## License + +All contributions are MIT-licensed. By submitting a PR, you agree to this license. diff --git a/docs/src/content/docs/features/copilot-mcp-trust.md b/docs/src/content/docs/features/copilot-mcp-trust.md new file mode 100644 index 000000000..e8c3ef96e --- /dev/null +++ b/docs/src/content/docs/features/copilot-mcp-trust.md @@ -0,0 +1,83 @@ +# Copilot CLI Non-Interactive MCP Trust Gate + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +When `squad watch` or another Squad automation spawns `copilot -p` (non-interactive mode), it automatically injects `--yolo --additional-mcp-config @.mcp.json` into every Copilot sub-invocation. This page explains why that injection is mandatory and what to do if `squad_state_*` tools are silently unavailable. + +--- + +## What Is the Trust Gate? + +Copilot CLI 1.0.59+ protects against loading arbitrary MCP binaries from workspace files by requiring the user to explicitly trust a folder before its `.mcp.json` is auto-loaded. In **interactive mode** this is a one-time prompt ("Trust this folder?"). In **non-interactive (`-p`) mode** there is no UI, so the gate cannot be satisfied and workspace `.mcp.json` is silently skipped. + +This is a security measure (RCE prevention), not a bug. + +--- + +## Empirical Test Matrix + +The following was verified against Copilot CLI 1.0.59: + +| Invocation | `.mcp.json` loaded? | +|------------|---------------------| +| `copilot -p "..."` | ❌ No | +| `copilot --yolo -p "..."` | ❌ No | +| `copilot --yolo --autopilot -p "..."` | ❌ No | +| `copilot --additional-mcp-config @.mcp.json --yolo -p "..."` | ✅ **Yes** | +| Interactive `copilot` → "Trust folder?" → Yes | ✅ Yes (not automatable) | + +The `--additional-mcp-config @` flag bypasses the trust gate for the explicitly named file and is the only proven workaround for non-interactive sessions. + +--- + +## How Squad Handles This Automatically + +`squad watch`, the loop command, and any other Squad automation that spawns `copilot` as a subprocess automatically prepend: + +``` +--yolo --additional-mcp-config @/abs/path/to/.mcp.json +``` + +before the `-p` prompt and any other flags. You do **not** need to add these flags yourself when using Squad commands. + +`--yolo` also suppresses the per-tool-call consent prompt that would cause `copilot -p` to hang waiting for input in non-interactive mode. + +--- + +## Recommended `package.json` Script + +If you write your own non-interactive Copilot scripts (CI, cron jobs, shell aliases), use this pattern to ensure `.mcp.json` is loaded: + +```json +{ + "scripts": { + "squad:copilot": "copilot --additional-mcp-config @.mcp.json" + } +} +``` + +Then invoke it as: + +```bash +npm run squad:copilot -- --yolo -p "Your prompt here" +``` + +The `--yolo` flag is intentionally omitted from the `package.json` script itself so that interactive runs (`npm run squad:copilot`) still show per-tool consent prompts by default. + +--- + +## Troubleshooting + +**`squad_state_*` tools are not available in `squad watch` sessions** + +1. Verify `.mcp.json` exists at the repo root: `cat .mcp.json` +2. If missing, run `squad init` or `squad upgrade` to regenerate it +3. Confirm the file has a `squad_state` entry under `mcpServers` + +**Squad emits `⚠ .mcp.json not found at `** + +This warning appears when Squad tries to inject MCP config but `.mcp.json` is absent. Run `squad init` or `squad upgrade` to create it. + +**`.copilot/mcp-config.json` still exists from an older Squad version** + +Squad automatically tombstones (removes) the `squad_state` entry from `.copilot/mcp-config.json` during `init` and `upgrade`. Both files can coexist; Squad reads only `.mcp.json` for its own state tools. diff --git a/docs/src/content/docs/features/loop.md b/docs/src/content/docs/features/loop.md index 326cbb7c7..de2829e25 100644 --- a/docs/src/content/docs/features/loop.md +++ b/docs/src/content/docs/features/loop.md @@ -56,6 +56,8 @@ By default, Loop requires: If you don't want to use `gh copilot`, pass `--agent-cmd` to provide an alternative agent command. In that case, `gh` and the Copilot extension are not required for the agent step. +> **MCP auto-injection:** When using the default Copilot agent, `squad loop` automatically injects `--yolo --additional-mcp-config @.mcp.json` into every Copilot invocation. This ensures MCP tools are available in non-interactive (`-p`) mode. See [Copilot CLI MCP Trust Gate](./copilot-mcp-trust.md). + ## Getting started ### Step 1: Initialize your loop diff --git a/docs/src/content/docs/features/ralph.md b/docs/src/content/docs/features/ralph.md index e6f42e399..3aefacca2 100644 --- a/docs/src/content/docs/features/ralph.md +++ b/docs/src/content/docs/features/ralph.md @@ -218,7 +218,7 @@ squad watch --execute --interval 15 # check every 15 minutes squad watch --execute --max-concurrent 2 # work on 2 issues in parallel ``` -When `--execute` is enabled, Ralph spawns Copilot CLI sessions for actionable issues (assigned to a squad member, not blocked, not already assigned to a human). +When `--execute` is enabled, Ralph spawns Copilot CLI sessions for actionable issues (assigned to a squad member, not blocked, not already assigned to a human). Squad automatically injects `--yolo --additional-mcp-config @.mcp.json` into every spawned Copilot invocation so that MCP tools are available in non-interactive (`-p`) mode — see [Copilot CLI MCP Trust Gate](./copilot-mcp-trust.md) for details. **Example execution output:** diff --git a/docs/src/content/docs/features/state-backends.md b/docs/src/content/docs/features/state-backends.md index 058b03e9d..0009e0fb2 100644 --- a/docs/src/content/docs/features/state-backends.md +++ b/docs/src/content/docs/features/state-backends.md @@ -33,6 +33,11 @@ squad init --state-backend orphan squad init --state-backend two-layer ``` +> **Default backend:** if you don't pass `--state-backend`, Squad uses the +> `local` backend (regular `.squad/` files in your working tree). The +> `orphan` and `two-layer` backends are opt-in — you must pass the explicit +> flag during `squad init` or `squad upgrade` to activate them. + The backend is stored in `.squad/config.json` — you never need to pass it again. All subsequent commands (`squad watch`, interactive sessions, etc.) read from config automatically. ### Existing project — migrate with upgrade diff --git a/docs/src/content/docs/reference/cli.md b/docs/src/content/docs/reference/cli.md index f986bb787..ffc9221e3 100644 --- a/docs/src/content/docs/reference/cli.md +++ b/docs/src/content/docs/reference/cli.md @@ -200,6 +200,8 @@ Each cycle, you will: Keep cycles to 20 minutes max. ``` +**MCP auto-injection:** When using the default Copilot agent, `squad loop` automatically injects `--yolo --additional-mcp-config @.mcp.json` into every Copilot invocation. See [Copilot CLI MCP Trust Gate](../features/copilot-mcp-trust.md). + For complete documentation and examples, see [Loop — Prompt-driven work loop](../features/loop.md). --- diff --git a/package-lock.json b/package-lock.json index 041aa75b9..f856e3918 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bradygaster/squad", - "version": "0.9.6", + "version": "0.9.6-preview.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bradygaster/squad", - "version": "0.9.6", + "version": "0.9.6-preview.1", "license": "MIT", "workspaces": [ "packages/*" @@ -34,8 +34,6 @@ }, "node_modules/@alcalzone/ansi-tokenize": { "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", - "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", @@ -47,8 +45,6 @@ }, "node_modules/@alcalzone/ansi-tokenize/node_modules/is-fullwidth-code-point": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "license": "MIT", "dependencies": { "get-east-asian-width": "^1.3.1" @@ -62,8 +58,6 @@ }, "node_modules/@ampproject/remapping": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -76,8 +70,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -86,8 +78,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -96,8 +86,6 @@ }, "node_modules/@babel/parser": { "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { @@ -112,8 +100,6 @@ }, "node_modules/@babel/types": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -126,8 +112,6 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", "engines": { @@ -144,8 +128,6 @@ }, "node_modules/@cspell/cspell-bundled-dicts": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.7.0.tgz", - "integrity": "sha512-s7h1vo++Q3AsfQa3cs0u/KGwm3SYInuIlC4kjlCBWjQmb4KddiZB5O1u0+3TlA7GycHb5M4CR7MDfHUICgJf+w==", "dev": true, "license": "MIT", "dependencies": { @@ -215,8 +197,6 @@ }, "node_modules/@cspell/cspell-json-reporter": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-9.7.0.tgz", - "integrity": "sha512-6xpGXlMtQA3hV2BCAQcPkpx9eI12I0o01i9eRqSSEDKtxuAnnrejbcCpL+5OboAjTp3/BSeNYSnhuWYLkSITWQ==", "dev": true, "license": "MIT", "dependencies": { @@ -228,8 +208,6 @@ }, "node_modules/@cspell/cspell-performance-monitor": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-performance-monitor/-/cspell-performance-monitor-9.7.0.tgz", - "integrity": "sha512-w1PZIFXuvjnC6mQHyYAFnrsn5MzKnEcEkcK1bj4OG00bAt7WX2VUA/eNNt9c1iHozCQ+FcRYlfbGxuBmNyzSgw==", "dev": true, "license": "MIT", "engines": { @@ -238,8 +216,6 @@ }, "node_modules/@cspell/cspell-pipe": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.7.0.tgz", - "integrity": "sha512-iiisyRpJciU9SOHNSi0ZEK0pqbEMFRatI/R4O+trVKb+W44p4MNGClLVRWPGUmsFbZKPJL3jDtz0wPlG0/JCZA==", "dev": true, "license": "MIT", "engines": { @@ -248,8 +224,6 @@ }, "node_modules/@cspell/cspell-resolver": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.7.0.tgz", - "integrity": "sha512-uiEgS238mdabDnwavo6HXt8K98jlh/jpm7NONroM9NTr9rzck2VZKD2kXEj85wDNMtRsRXNoywTjwQ8WTB6/+w==", "dev": true, "license": "MIT", "dependencies": { @@ -261,8 +235,6 @@ }, "node_modules/@cspell/cspell-service-bus": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.7.0.tgz", - "integrity": "sha512-fkqtaCkg4jY/FotmzjhIavbXuH0AgUJxZk78Ktf4XlhqOZ4wDeUWrCf220bva4mh3TWiLx/ae9lIlpl59Vx6hA==", "dev": true, "license": "MIT", "engines": { @@ -271,8 +243,6 @@ }, "node_modules/@cspell/cspell-types": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.7.0.tgz", - "integrity": "sha512-Tdfx4eH2uS+gv9V9NCr3Rz+c7RSS6ntXp3Blliud18ibRUlRxO9dTaOjG4iv4x0nAmMeedP1ORkEpeXSkh2QiQ==", "dev": true, "license": "MIT", "engines": { @@ -281,8 +251,6 @@ }, "node_modules/@cspell/cspell-worker": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-worker/-/cspell-worker-9.7.0.tgz", - "integrity": "sha512-cjEApFF0aOAa1vTUk+e7xP8ofK7iC7hsRzj1FmvvVQz8PoLWPRaq+1bT89ypPsZQvavqm5sIgb97S60/aW4TVg==", "dev": true, "license": "MIT", "dependencies": { @@ -294,29 +262,21 @@ }, "node_modules/@cspell/dict-ada": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.1.1.tgz", - "integrity": "sha512-E+0YW9RhZod/9Qy2gxfNZiHJjCYFlCdI69br1eviQQWB8yOTJX0JHXLs79kOYhSW0kINPVUdvddEBe6Lu6CjGQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-al": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-al/-/dict-al-1.1.1.tgz", - "integrity": "sha512-sD8GCaZetgQL4+MaJLXqbzWcRjfKVp8x+px3HuCaaiATAAtvjwUQ5/Iubiqwfd1boIh2Y1/3EgM3TLQ7Q8e0wQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-aws": { "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.17.tgz", - "integrity": "sha512-ORcblTWcdlGjIbWrgKF+8CNEBQiLVKdUOFoTn0KPNkAYnFcdPP0muT4892h7H4Xafh3j72wqB4/loQ6Nti9E/w==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-bash": { "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.2.2.tgz", - "integrity": "sha512-kyWbwtX3TsCf5l49gGQIZkRLaB/P8g73GDRm41Zu8Mv51kjl2H7Au0TsEvHv7jzcsRLS6aUYaZv6Zsvk1fOz+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -325,246 +285,176 @@ }, "node_modules/@cspell/dict-companies": { "version": "3.2.11", - "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.11.tgz", - "integrity": "sha512-0cmafbcz2pTHXLd59eLR1gvDvN6aWAOM0+cIL4LLF9GX9yB2iKDNrKsvs4tJRqutoaTdwNFBbV0FYv+6iCtebQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-cpp": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-7.0.2.tgz", - "integrity": "sha512-dfbeERiVNeqmo/npivdR6rDiBCqZi3QtjH2Z0HFcXwpdj6i97dX1xaKyK2GUsO/p4u1TOv63Dmj5Vm48haDpuA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-cryptocurrencies": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.5.tgz", - "integrity": "sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-csharp": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.8.tgz", - "integrity": "sha512-qmk45pKFHSxckl5mSlbHxmDitSsGMlk/XzFgt7emeTJWLNSTUK//MbYAkBNRtfzB4uD7pAFiKgpKgtJrTMRnrQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-css": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.1.1.tgz", - "integrity": "sha512-y/Vgo6qY08e1t9OqR56qjoFLBCpi4QfWMf2qzD1l9omRZwvSMQGRPz4x0bxkkkU4oocMAeztjzCsmLew//c/8w==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-dart": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.2.tgz", - "integrity": "sha512-sUiLW56t9gfZcu8iR/5EUg+KYyRD83Cjl3yjDEA2ApVuJvK1HhX+vn4e4k4YfjpUQMag8XO2AaRhARE09+/rqw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-data-science": { "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.13.tgz", - "integrity": "sha512-l1HMEhBJkPmw4I2YGVu2eBSKM89K9pVF+N6qIr5Uo5H3O979jVodtuwP8I7LyPrJnC6nz28oxeGRCLh9xC5CVA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-django": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.6.tgz", - "integrity": "sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-docker": { "version": "1.1.17", - "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.17.tgz", - "integrity": "sha512-OcnVTIpHIYYKhztNTyK8ShAnXTfnqs43hVH6p0py0wlcwRIXe5uj4f12n7zPf2CeBI7JAlPjEsV0Rlf4hbz/xQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-dotnet": { "version": "5.0.13", - "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.13.tgz", - "integrity": "sha512-xPp7jMnFpOri7tzmqmm/dXMolXz1t2bhNqxYkOyMqXhvs08oc7BFs+EsbDY0X7hqiISgeFZGNqn0dOCr+ncPYw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-elixir": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.8.tgz", - "integrity": "sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-en_us": { "version": "4.4.33", - "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.33.tgz", - "integrity": "sha512-zWftVqfUStDA37wO1ZNDN1qMJOfcxELa8ucHW8W8wBAZY3TK5Nb6deLogCK/IJi/Qljf30dwwuqqv84Qqle9Tw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-en-common-misspellings": { "version": "2.1.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.1.12.tgz", - "integrity": "sha512-14Eu6QGqyksqOd4fYPuRb58lK1Va7FQK9XxFsRKnZU8LhL3N+kj7YKDW+7aIaAN/0WGEqslGP6lGbQzNti8Akw==", "dev": true, "license": "CC BY-SA 4.0" }, "node_modules/@cspell/dict-en-gb-mit": { "version": "3.1.22", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.1.22.tgz", - "integrity": "sha512-xE5Vg6gGdMkZ1Ep6z9SJMMioGkkT1GbxS5Mm0U3Ey1/H68P0G7cJcyiVr1CARxFbLqKE4QUpoV1o6jz1Z5Yl9Q==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-filetypes": { "version": "3.0.18", - "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.18.tgz", - "integrity": "sha512-yU7RKD/x1IWmDLzWeiItMwgV+6bUcU/af23uS0+uGiFUbsY1qWV/D4rxlAAO6Z7no3J2z8aZOkYIOvUrJq0Rcw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-flutter": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.1.1.tgz", - "integrity": "sha512-UlOzRcH2tNbFhZmHJN48Za/2/MEdRHl2BMkCWZBYs+30b91mWvBfzaN4IJQU7dUZtowKayVIF9FzvLZtZokc5A==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-fonts": { "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.6.tgz", - "integrity": "sha512-aR/0csY01dNb0A1tw/UmN9rKgHruUxsYsvXu6YlSBJFu60s26SKr/k1o4LavpHTQ+lznlYMqAvuxGkE4Flliqw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-fsharp": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.1.1.tgz", - "integrity": "sha512-imhs0u87wEA4/cYjgzS0tAyaJpwG7vwtC8UyMFbwpmtw+/bgss+osNfyqhYRyS/ehVCWL17Ewx2UPkexjKyaBA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-fullstack": { "version": "3.2.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.9.tgz", - "integrity": "sha512-diZX+usW5aZ4/b2T0QM/H/Wl9aNMbdODa1Jq0ReBr/jazmNeWjd+PyqeVgzd1joEaHY+SAnjrf/i9CwKd2ZtWQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-gaming-terms": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.1.2.tgz", - "integrity": "sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-git": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.1.0.tgz", - "integrity": "sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-golang": { "version": "6.0.26", - "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.26.tgz", - "integrity": "sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-google": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.9.tgz", - "integrity": "sha512-biL65POqialY0i4g6crj7pR6JnBkbsPovB2WDYkj3H4TuC/QXv7Pu5pdPxeUJA6TSCHI7T5twsO4VSVyRxD9CA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-haskell": { "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.6.tgz", - "integrity": "sha512-ib8SA5qgftExpYNjWhpYIgvDsZ/0wvKKxSP+kuSkkak520iPvTJumEpIE+qPcmJQo4NzdKMN8nEfaeci4OcFAQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-html": { "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.15.tgz", - "integrity": "sha512-GJYnYKoD9fmo2OI0aySEGZOjThnx3upSUvV7mmqUu8oG+mGgzqm82P/f7OqsuvTaInZZwZbo+PwJQd/yHcyFIw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-html-symbol-entities": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.5.tgz", - "integrity": "sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-java": { "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.12.tgz", - "integrity": "sha512-qPSNhTcl7LGJ5Qp6VN71H8zqvRQK04S08T67knMq9hTA8U7G1sTKzLmBaDOFhq17vNX/+rT+rbRYp+B5Nwza1A==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-julia": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.1.1.tgz", - "integrity": "sha512-WylJR9TQ2cgwd5BWEOfdO3zvDB+L7kYFm0I9u0s9jKHWQ6yKmfKeMjU9oXxTBxIufhCXm92SKwwVNAC7gjv+yA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-k8s": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.12.tgz", - "integrity": "sha512-2LcllTWgaTfYC7DmkMPOn9GsBWsA4DZdlun4po8s2ysTP7CPEnZc1ZfK6pZ2eI4TsZemlUQQ+NZxMe9/QutQxg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-kotlin": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-kotlin/-/dict-kotlin-1.1.1.tgz", - "integrity": "sha512-J3NzzfgmxRvEeOe3qUXnSJQCd38i/dpF9/t3quuWh6gXM+krsAXP75dY1CzDmS8mrJAlBdVBeAW5eAZTD8g86Q==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-latex": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-5.1.0.tgz", - "integrity": "sha512-qxT4guhysyBt0gzoliXYEBYinkAdEtR2M7goRaUH0a7ltCsoqqAeEV8aXYRIdZGcV77gYSobvu3jJL038tlPAw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-lorem-ipsum": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.5.tgz", - "integrity": "sha512-9a4TJYRcPWPBKkQAJ/whCu4uCAEgv/O2xAaZEI0n4y1/l18Yyx8pBKoIX5QuVXjjmKEkK7hi5SxyIsH7pFEK9Q==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-lua": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.8.tgz", - "integrity": "sha512-N4PkgNDMu9JVsRu7JBS/3E/dvfItRgk9w5ga2dKq+JupP2Y3lojNaAVFhXISh4Y0a6qXDn2clA6nvnavQ/jjLA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-makefile": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.5.tgz", - "integrity": "sha512-4vrVt7bGiK8Rx98tfRbYo42Xo2IstJkAF4tLLDMNQLkQ86msDlYSKG1ZCk8Abg+EdNcFAjNhXIiNO+w4KflGAQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-markdown": { "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.16.tgz", - "integrity": "sha512-976RRqKv6cwhrxdFCQP2DdnBVB86BF57oQtPHy4Zbf4jF/i2Oy29MCrxirnOBalS1W6KQeto7NdfDXRAwkK4PQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -576,50 +466,36 @@ }, "node_modules/@cspell/dict-monkeyc": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.12.tgz", - "integrity": "sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-node": { "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.9.tgz", - "integrity": "sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-npm": { "version": "5.2.38", - "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.38.tgz", - "integrity": "sha512-21ucGRPYYhr91C2cDBoMPTrcIOStQv33xOqJB0JLoC5LAs2Sfj9EoPGhGb+gIFVHz6Ia7JQWE2SJsOVFJD1wmg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-php": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.1.1.tgz", - "integrity": "sha512-EXelI+4AftmdIGtA8HL8kr4WlUE11OqCSVlnIgZekmTkEGSZdYnkFdiJ5IANSALtlQ1mghKjz+OFqVs6yowgWA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-powershell": { "version": "5.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.15.tgz", - "integrity": "sha512-l4S5PAcvCFcVDMJShrYD0X6Huv9dcsQPlsVsBGbH38wvuN7gS7+GxZFAjTNxDmTY1wrNi1cCatSg6Pu2BW4rgg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-public-licenses": { "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.16.tgz", - "integrity": "sha512-EQRrPvEOmwhwWezV+W7LjXbIBjiy6y/shrET6Qcpnk3XANTzfvWflf9PnJ5kId/oKWvihFy0za0AV1JHd03pSQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-python": { "version": "4.2.26", - "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.26.tgz", - "integrity": "sha512-hbjN6BjlSgZOG2dA2DtvYNGBM5Aq0i0dHaZjMOI9K/9vRicVvKbcCiBSSrR3b+jwjhQL5ff7HwG5xFaaci0GQA==", "dev": true, "license": "MIT", "dependencies": { @@ -628,99 +504,71 @@ }, "node_modules/@cspell/dict-r": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.1.1.tgz", - "integrity": "sha512-71Ka+yKfG4ZHEMEmDxc6+blFkeTTvgKbKAbwiwQAuKl3zpqs1Y0vUtwW2N4b3LgmSPhV3ODVY0y4m5ofqDuKMw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-ruby": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.1.1.tgz", - "integrity": "sha512-LHrp84oEV6q1ZxPPyj4z+FdKyq1XAKYPtmGptrd+uwHbrF/Ns5+fy6gtSi7pS+uc0zk3JdO9w/tPK+8N1/7WUA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-rust": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.1.2.tgz", - "integrity": "sha512-O1FHrumYcO+HZti3dHfBPUdnDFkI+nbYK3pxYmiM1sr+G0ebOd6qchmswS0Wsc6ZdEVNiPYJY/gZQR6jfW3uOg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-scala": { "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.9.tgz", - "integrity": "sha512-AjVcVAELgllybr1zk93CJ5wSUNu/Zb5kIubymR/GAYkMyBdYFCZ3Zbwn4Zz8GJlFFAbazABGOu0JPVbeY59vGg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-shell": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-shell/-/dict-shell-1.1.2.tgz", - "integrity": "sha512-WqOUvnwcHK1X61wAfwyXq04cn7KYyskg90j4lLg3sGGKMW9Sq13hs91pqrjC44Q+lQLgCobrTkMDw9Wyl9nRFA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-software-terms": { "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.2.2.tgz", - "integrity": "sha512-0CaYd6TAsKtEoA7tNswm1iptEblTzEe3UG8beG2cpSTHk7afWIVMtJLgXDv0f/Li67Lf3Z1Jf3JeXR7GsJ2TRw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-sql": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.2.1.tgz", - "integrity": "sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-svelte": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.7.tgz", - "integrity": "sha512-hGZsGqP0WdzKkdpeVLBivRuSNzOTvN036EBmpOwxH+FTY2DuUH7ecW+cSaMwOgmq5JFSdTcbTNFlNC8HN8lhaQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-swift": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.6.tgz", - "integrity": "sha512-PnpNbrIbex2aqU1kMgwEKvCzgbkHtj3dlFLPMqW1vSniop7YxaDTtvTUO4zA++ugYAEL+UK8vYrBwDPTjjvSnA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-terraform": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.1.3.tgz", - "integrity": "sha512-gr6wxCydwSFyyBKhBA2xkENXtVFToheqYYGFvlMZXWjviynXmh+NK/JTvTCk/VHk3+lzbO9EEQKee6VjrAUSbA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-typescript": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", - "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-vue": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.5.tgz", - "integrity": "sha512-Mqutb8jbM+kIcywuPQCCaK5qQHTdaByoEO2J9LKFy3sqAdiBogNkrplqUK0HyyRFgCfbJUgjz3N85iCMcWH0JA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-zig": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-zig/-/dict-zig-1.0.0.tgz", - "integrity": "sha512-XibBIxBlVosU06+M6uHWkFeT0/pW5WajDRYdXG2CgHnq85b0TI/Ks0FuBJykmsgi2CAD3Qtx8UHFEtl/DSFnAQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dynamic-import": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.7.0.tgz", - "integrity": "sha512-Ws36IYvtS/8IN3x6K9dPLvTmaArodRJmzTn2Rkf2NaTnIYWhRuFzsP3SVVO59NN3fXswAEbmz5DSbVUe8bPZHg==", "dev": true, "license": "MIT", "dependencies": { @@ -733,8 +581,6 @@ }, "node_modules/@cspell/filetypes": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.7.0.tgz", - "integrity": "sha512-Ln9e/8wGOyTeL3DCCs6kwd18TSpTw3kxsANjTrzLDASrX4cNmAdvc9J5dcIuBHPaqOAnRQxuZbzUlpRh73Y24w==", "dev": true, "license": "MIT", "engines": { @@ -743,8 +589,6 @@ }, "node_modules/@cspell/rpc": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/rpc/-/rpc-9.7.0.tgz", - "integrity": "sha512-VnZ4ABgQeoS4RwofcePkDP7L6tf3Kh5D7LQKoyRM4R6XtfSsYefym6XKaRl3saGtthH5YyjgNJ0Tgdjen4wAAw==", "dev": true, "license": "MIT", "engines": { @@ -753,8 +597,6 @@ }, "node_modules/@cspell/strong-weak-map": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.7.0.tgz", - "integrity": "sha512-5xbvDASjklrmy88O6gmGXgYhpByCXqOj5wIgyvwZe2l83T1bE+iOfGI4pGzZJ/mN+qTn1DNKq8BPBPtDgb7Q2Q==", "dev": true, "license": "MIT", "engines": { @@ -763,8 +605,6 @@ }, "node_modules/@cspell/url": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.7.0.tgz", - "integrity": "sha512-ZaaBr0pTvNxmyUbIn+nVPXPr383VqJzfUDMWicgTjJIeo2+T2hOq2kNpgpvTIrWtZrsZnSP8oXms1+sKTjcvkw==", "dev": true, "license": "MIT", "engines": { @@ -1198,8 +1038,6 @@ }, "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" ], @@ -1215,8 +1053,6 @@ }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1234,8 +1070,6 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -1244,8 +1078,6 @@ }, "node_modules/@eslint/config-array": { "version": "0.23.3", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", - "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1259,8 +1091,6 @@ }, "node_modules/@eslint/config-helpers": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz", - "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1272,8 +1102,6 @@ }, "node_modules/@eslint/core": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", - "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1285,8 +1113,6 @@ }, "node_modules/@eslint/object-schema": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", - "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1295,8 +1121,6 @@ }, "node_modules/@eslint/plugin-kit": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", - "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1309,8 +1133,6 @@ }, "node_modules/@gerrit0/mini-shiki": { "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz", - "integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==", "dev": true, "license": "MIT", "dependencies": { @@ -1323,8 +1145,6 @@ }, "node_modules/@github/copilot": { "version": "1.0.50", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.50.tgz", - "integrity": "sha512-HJFM+LYt5i6shAiTYHolCSQLV9ZzfX/06m7yWht4PiKBE+hO/zxXhqnJFMshqMkFm0Ab3ea0FZDx8CVjdXn5bQ==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" @@ -1340,106 +1160,8 @@ "@github/copilot-win32-x64": "1.0.50" } }, - "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.50", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.50.tgz", - "integrity": "sha512-PSqw/QrJelPGa9jHooe9QaTzhLt2DquCj2shyVNgC7bfFKkCFPbY0vBXNAK6TD+REOKaj1vPsGrEt3dYODnzaw==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-arm64": "copilot" - } - }, - "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.50", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.50.tgz", - "integrity": "sha512-Z/8DEWmkPpPz0H5oT5m1MAnudFxHkykEmCNWPvQYXMBcUuJ2OdYIt9bWr57nGU+KjY2TcnkoN766rnBm2MwKWQ==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-x64": "copilot" - } - }, - "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.50", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.50.tgz", - "integrity": "sha512-Hp5Bhmur7N63ngiZTECr1oyLg4kz6GSM4LGinRdI7PcDu9qB/6GYZO41MtwP17PyzNgfP8Gs4Lej2vgVqO3/Dw==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linux-x64": { - "version": "1.0.50", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.50.tgz", - "integrity": "sha512-acjlW1g0sgAfnsBj/JQCdTODKCHRcadVJiJUT3xv7HKTYLVilIU1iwmQAzQ7r3QwmNCOI48FL7usbNiKouop8A==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-x64": "copilot" - } - }, - "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.50", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.50.tgz", - "integrity": "sha512-GRhiIDVBPdit5QItfEvEn3d9mwT6cVFr2Ms5bvtKBLS4Hs7E419dZX66Z6zB2Wjh4u2l4MLjinxVzggcpEx/HQ==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linuxmusl-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.50", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.50.tgz", - "integrity": "sha512-3G0+/4F6SYaj6AfttLqRzp3HO3/6RIdXEqzCirR0E5l4SMjD8mHmTAE8YKbjPl9XGf/wbhyDmSMcIcxj79Mf7w==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linuxmusl-x64": "copilot" - } - }, "node_modules/@github/copilot-sdk": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.3.0.tgz", - "integrity": "sha512-SUo35k56pzzgYgwmDPHcu7kZxPrzXbH66IWXaEf6pmb94DlA709F82HrrDeja087TL4djJ9OuvRFWWOKCosAsg==", "license": "MIT", "dependencies": { "@github/copilot": "^1.0.36-0", @@ -1450,26 +1172,8 @@ "node": ">=20.0.0" } }, - "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.50", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.50.tgz", - "integrity": "sha512-cLSnU+IQ7p0WIdxeaDeTR6rtiSLwHqN+3AkAaOKKBXBsYjmB3Ct6UHqlZf20GtZ5I/K1HH9ZDYRYVKlsA4olJQ==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-arm64": "copilot.exe" - } - }, "node_modules/@github/copilot-win32-x64": { "version": "1.0.50", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.50.tgz", - "integrity": "sha512-f5cA798nmOj/E7GXuzX2HnPenx8ddp/y87q6hpoU78nySTasWCTvWhckjuJ+fwzJQmp3fORBbL67pDdELvdajA==", "cpu": [ "x64" ], @@ -1484,8 +1188,6 @@ }, "node_modules/@grpc/grpc-js": { "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", - "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1498,8 +1200,6 @@ }, "node_modules/@grpc/proto-loader": { "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1517,8 +1217,6 @@ }, "node_modules/@hono/node-server": { "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1529,8 +1227,6 @@ }, "node_modules/@humanfs/core": { "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1539,8 +1235,6 @@ }, "node_modules/@humanfs/node": { "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1553,8 +1247,6 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1567,8 +1259,6 @@ }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1581,8 +1271,6 @@ }, "node_modules/@isaacs/cliui": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "license": "ISC", "dependencies": { @@ -1599,15 +1287,11 @@ }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1624,8 +1308,6 @@ }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", "engines": { @@ -1634,8 +1316,6 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { @@ -1645,8 +1325,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -1655,15 +1333,11 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1673,8 +1347,6 @@ }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", "license": "MIT", "optional": true, "funding": { @@ -1684,8 +1356,6 @@ }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1724,8 +1394,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1740,14 +1408,10 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", "dependencies": { @@ -1760,8 +1424,6 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", "engines": { @@ -1770,8 +1432,6 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", "dependencies": { @@ -1784,8 +1444,6 @@ }, "node_modules/@opentelemetry/api": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", - "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", "devOptional": true, "license": "Apache-2.0", "engines": { @@ -1794,8 +1452,6 @@ }, "node_modules/@opentelemetry/api-logs": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", - "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1807,8 +1463,6 @@ }, "node_modules/@opentelemetry/context-async-hooks": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", - "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -1820,8 +1474,6 @@ }, "node_modules/@opentelemetry/core": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", - "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1836,8 +1488,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.57.2.tgz", - "integrity": "sha512-eovEy10n3umjKJl2Ey6TLzikPE+W4cUQ4gCwgGP1RqzTGtgDra0WjIqdy29ohiUKfvmbiL3MndZww58xfIvyFw==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1857,8 +1507,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1873,8 +1521,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -1883,8 +1529,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-http": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.57.2.tgz", - "integrity": "sha512-0rygmvLcehBRp56NQVLSleJ5ITTduq/QfU7obOkyWgPpFHulwpw2LYTqNIz5TczKZuy5YY+5D3SDnXZL1tXImg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1903,8 +1547,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1919,8 +1561,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -1929,8 +1569,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-proto": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.57.2.tgz", - "integrity": "sha512-ta0ithCin0F8lu9eOf4lEz9YAScecezCHkMMyDkvd9S7AnZNX5ikUmC5EQOQADU+oCcgo/qkQIaKcZvQ0TYKDw==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1951,8 +1589,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1967,8 +1603,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1984,8 +1618,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2002,8 +1634,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2012,8 +1642,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.57.2.tgz", - "integrity": "sha512-r70B8yKR41F0EC443b5CGB4rUaOMm99I5N75QQt6sHKxYDzSEc6gm48Diz1CI1biwa5tDPznpylTrywO/pT7qw==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2035,8 +1663,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2051,8 +1677,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2068,8 +1692,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2085,8 +1707,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2095,8 +1715,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-http": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.57.2.tgz", - "integrity": "sha512-ttb9+4iKw04IMubjm3t0EZsYRNWr3kg44uUuzfo9CaccYlOh8cDooe4QObDUkvx9d5qQUrbEckhrWKfJnKhemA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2115,8 +1733,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2131,8 +1747,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2148,8 +1762,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2165,8 +1777,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2175,8 +1785,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.57.2.tgz", - "integrity": "sha512-HX068Q2eNs38uf7RIkNN9Hl4Ynl+3lP0++KELkXMCpsCbFO03+0XNNZ1SkwxPlP9jrhQahsMPMkzNXpq3fKsnw==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2196,8 +1804,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2212,8 +1818,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2229,8 +1833,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2246,8 +1848,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2256,8 +1856,6 @@ }, "node_modules/@opentelemetry/exporter-prometheus": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.57.2.tgz", - "integrity": "sha512-VqIqXnuxWMWE/1NatAGtB1PvsQipwxDcdG4RwA/umdBcW3/iOHp0uejvFHTRN2O78ZPged87ErJajyUBPUhlDQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2274,8 +1872,6 @@ }, "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2290,8 +1886,6 @@ }, "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2307,8 +1901,6 @@ }, "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/sdk-metrics": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2324,8 +1916,6 @@ }, "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2334,8 +1924,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.57.2.tgz", - "integrity": "sha512-gHU1vA3JnHbNxEXg5iysqCWxN9j83d7/epTYBZflqQnTyCC4N7yZXn/dMM+bEmyhQPGjhCkNZLx4vZuChH1PYw==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2356,8 +1944,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2372,8 +1958,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2389,8 +1973,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2407,8 +1989,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2417,8 +1997,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-http": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.57.2.tgz", - "integrity": "sha512-sB/gkSYFu+0w2dVQ0PWY9fAMl172PKMZ/JrHkkW8dmjCL0CYkmXeE+ssqIL/yBUTPOvpLIpenX5T9RwXRBW/3g==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2437,8 +2015,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2453,8 +2029,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2470,8 +2044,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2488,8 +2060,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2498,8 +2068,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-proto": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.57.2.tgz", - "integrity": "sha512-awDdNRMIwDvUtoRYxRhja5QYH6+McBLtoz1q9BeEsskhZcrGmH/V1fWpGx8n+Rc+542e8pJA6y+aullbIzQmlw==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2518,8 +2086,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2534,8 +2100,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2551,8 +2115,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2569,8 +2131,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2579,8 +2139,6 @@ }, "node_modules/@opentelemetry/exporter-zipkin": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.30.1.tgz", - "integrity": "sha512-6S2QIMJahIquvFaaxmcwpvQQRD/YFaMTNoIxrfPIPOeITN+a8lfEcPDxNxn8JDAaxkg+4EnXhz8upVDYenoQjA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2598,8 +2156,6 @@ }, "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2614,8 +2170,6 @@ }, "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2631,8 +2185,6 @@ }, "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/sdk-trace-base": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2649,8 +2201,6 @@ }, "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2659,8 +2209,6 @@ }, "node_modules/@opentelemetry/instrumentation": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", - "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2680,8 +2228,6 @@ }, "node_modules/@opentelemetry/otlp-exporter-base": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.57.2.tgz", - "integrity": "sha512-XdxEzL23Urhidyebg5E6jZoaiW5ygP/mRjxLHixogbqwDy2Faduzb5N0o/Oi+XTIJu+iyxXdVORjXax+Qgfxag==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2697,8 +2243,6 @@ }, "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2713,8 +2257,6 @@ }, "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2723,8 +2265,6 @@ }, "node_modules/@opentelemetry/otlp-grpc-exporter-base": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.57.2.tgz", - "integrity": "sha512-USn173KTWy0saqqRB5yU9xUZ2xdgb1Rdu5IosJnm9aV4hMTuFFRTUsQxbgc24QxpCHeoKzzCSnS/JzdV0oM2iQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2742,8 +2282,6 @@ }, "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2758,8 +2296,6 @@ }, "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2768,8 +2304,6 @@ }, "node_modules/@opentelemetry/otlp-transformer": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.57.2.tgz", - "integrity": "sha512-48IIRj49gbQVK52jYsw70+Jv+JbahT8BqT2Th7C4H7RCM9d0gZ5sgNPoMpWldmfjvIsSgiGJtjfk9MeZvjhoig==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2790,8 +2324,6 @@ }, "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2806,8 +2338,6 @@ }, "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2823,8 +2353,6 @@ }, "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-metrics": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2840,8 +2368,6 @@ }, "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2858,8 +2384,6 @@ }, "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2868,8 +2392,6 @@ }, "node_modules/@opentelemetry/propagator-b3": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.30.1.tgz", - "integrity": "sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2884,8 +2406,6 @@ }, "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2900,8 +2420,6 @@ }, "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2910,8 +2428,6 @@ }, "node_modules/@opentelemetry/propagator-jaeger": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.30.1.tgz", - "integrity": "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2926,8 +2442,6 @@ }, "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2942,8 +2456,6 @@ }, "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2952,8 +2464,6 @@ }, "node_modules/@opentelemetry/resources": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", - "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2969,8 +2479,6 @@ }, "node_modules/@opentelemetry/sdk-logs": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.57.2.tgz", - "integrity": "sha512-TXFHJ5c+BKggWbdEQ/inpgIzEmS2BGQowLE9UhsMd7YYlUfBQJ4uax0VF/B5NYigdM/75OoJGhAV3upEhK+3gg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2987,8 +2495,6 @@ }, "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3003,8 +2509,6 @@ }, "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3020,8 +2524,6 @@ }, "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -3030,8 +2532,6 @@ }, "node_modules/@opentelemetry/sdk-metrics": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.1.tgz", - "integrity": "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3047,8 +2547,6 @@ }, "node_modules/@opentelemetry/sdk-node": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.57.2.tgz", - "integrity": "sha512-8BaeqZyN5sTuPBtAoY+UtKwXBdqyuRKmekN5bFzAO40CgbGzAxfTpiL3PBerT7rhZ7p2nBdq7FaMv/tBQgHE4A==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3082,8 +2580,6 @@ }, "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3098,8 +2594,6 @@ }, "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3115,8 +2609,6 @@ }, "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-metrics": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3132,8 +2624,6 @@ }, "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-base": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3150,8 +2640,6 @@ }, "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -3160,8 +2648,6 @@ }, "node_modules/@opentelemetry/sdk-trace-base": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.1.tgz", - "integrity": "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3178,8 +2664,6 @@ }, "node_modules/@opentelemetry/sdk-trace-node": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.30.1.tgz", - "integrity": "sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3199,8 +2683,6 @@ }, "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3215,8 +2697,6 @@ }, "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3232,8 +2712,6 @@ }, "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/sdk-trace-base": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3250,8 +2728,6 @@ }, "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", "optional": true, "engines": { @@ -3260,8 +2736,6 @@ }, "node_modules/@opentelemetry/semantic-conventions": { "version": "1.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", - "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3270,8 +2744,6 @@ }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", "optional": true, @@ -3281,8 +2753,6 @@ }, "node_modules/@playwright/test": { "version": "1.58.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", - "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3297,36 +2767,26 @@ }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/base64": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", "license": "BSD-3-Clause", "optional": true, "dependencies": { @@ -3336,36 +2796,26 @@ }, "node_modules/@protobufjs/float": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/path": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/pool": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause", "optional": true }, @@ -3693,8 +3143,6 @@ }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", - "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", "cpu": [ "x64" ], @@ -3707,8 +3155,6 @@ }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", - "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", "cpu": [ "x64" ], @@ -3721,8 +3167,6 @@ }, "node_modules/@shikijs/engine-oniguruma": { "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", - "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", "dev": true, "license": "MIT", "dependencies": { @@ -3732,8 +3176,6 @@ }, "node_modules/@shikijs/langs": { "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", - "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", "dev": true, "license": "MIT", "dependencies": { @@ -3742,8 +3184,6 @@ }, "node_modules/@shikijs/themes": { "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", - "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", "dev": true, "license": "MIT", "dependencies": { @@ -3752,8 +3192,6 @@ }, "node_modules/@shikijs/types": { "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", - "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3763,15 +3201,11 @@ }, "node_modules/@shikijs/vscode-textmate": { "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "license": "MIT", "engines": { @@ -3783,8 +3217,6 @@ }, "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": { @@ -3794,8 +3226,6 @@ }, "node_modules/@types/debug": { "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "dev": true, "license": "MIT", "dependencies": { @@ -3804,36 +3234,26 @@ }, "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/emscripten": { "version": "1.41.5", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", - "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", "dev": true, "license": "MIT" }, "node_modules/@types/esrecurse": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", "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/hast": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3842,29 +3262,21 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, "node_modules/@types/katex": { "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", - "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", "dev": true, "license": "MIT" }, "node_modules/@types/ms": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "22.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", - "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -3873,8 +3285,6 @@ }, "node_modules/@types/react": { "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", "dependencies": { @@ -3883,15 +3293,11 @@ }, "node_modules/@types/shimmer": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", "license": "MIT", "optional": true }, "node_modules/@types/sql.js": { "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@types/sql.js/-/sql.js-1.4.11.tgz", - "integrity": "sha512-QXIx38p2ZThJaK9vP5ZdqdlRe1FG9I8SmCZOS7FHfB/2qPAjZwkL7/vlfPg6N/oWHuuOaGg/P/IRwfP2W0kWVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3901,15 +3307,11 @@ }, "node_modules/@types/unist": { "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "dev": true, "license": "MIT" }, "node_modules/@types/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", "dependencies": { @@ -3918,8 +3320,6 @@ }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", - "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", "dev": true, "license": "MIT", "dependencies": { @@ -3947,8 +3347,6 @@ }, "node_modules/@typescript-eslint/parser": { "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", - "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", "dev": true, "license": "MIT", "dependencies": { @@ -3972,8 +3370,6 @@ }, "node_modules/@typescript-eslint/project-service": { "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", - "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", "dev": true, "license": "MIT", "dependencies": { @@ -3994,8 +3390,6 @@ }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", - "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", "dev": true, "license": "MIT", "dependencies": { @@ -4012,8 +3406,6 @@ }, "node_modules/@typescript-eslint/tsconfig-utils": { "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", - "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", "dev": true, "license": "MIT", "engines": { @@ -4029,8 +3421,6 @@ }, "node_modules/@typescript-eslint/type-utils": { "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", - "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", "dev": true, "license": "MIT", "dependencies": { @@ -4054,8 +3444,6 @@ }, "node_modules/@typescript-eslint/types": { "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", - "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", "dev": true, "license": "MIT", "engines": { @@ -4068,8 +3456,6 @@ }, "node_modules/@typescript-eslint/typescript-estree": { "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", - "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", "dev": true, "license": "MIT", "dependencies": { @@ -4096,8 +3482,6 @@ }, "node_modules/@typescript-eslint/utils": { "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", - "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", "dev": true, "license": "MIT", "dependencies": { @@ -4120,8 +3504,6 @@ }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", - "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", "dev": true, "license": "MIT", "dependencies": { @@ -4138,8 +3520,6 @@ }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4151,8 +3531,6 @@ }, "node_modules/@vitest/coverage-v8": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4185,8 +3563,6 @@ }, "node_modules/@vitest/expect": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", "dependencies": { @@ -4202,8 +3578,6 @@ }, "node_modules/@vitest/mocker": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4229,8 +3603,6 @@ }, "node_modules/@vitest/pretty-format": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, "license": "MIT", "dependencies": { @@ -4242,8 +3614,6 @@ }, "node_modules/@vitest/runner": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4257,8 +3627,6 @@ }, "node_modules/@vitest/snapshot": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4272,8 +3640,6 @@ }, "node_modules/@vitest/spy": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "license": "MIT", "dependencies": { @@ -4285,8 +3651,6 @@ }, "node_modules/@vitest/utils": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", "dependencies": { @@ -4300,8 +3664,6 @@ }, "node_modules/accepts": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -4313,8 +3675,6 @@ }, "node_modules/acorn": { "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "devOptional": true, "license": "MIT", "bin": { @@ -4326,8 +3686,6 @@ }, "node_modules/acorn-import-attributes": { "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "license": "MIT", "optional": true, "peerDependencies": { @@ -4336,8 +3694,6 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4346,8 +3702,6 @@ }, "node_modules/ajv": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -4363,8 +3717,6 @@ }, "node_modules/ajv-formats": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -4380,8 +3732,6 @@ }, "node_modules/ajv-formats/node_modules/ajv": { "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -4396,14 +3746,10 @@ }, "node_modules/ajv-formats/node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, "node_modules/ansi-escapes": { "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "license": "MIT", "dependencies": { "environment": "^1.0.0" @@ -4417,8 +3763,6 @@ }, "node_modules/ansi-regex": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" @@ -4429,8 +3773,6 @@ }, "node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { "node": ">=12" @@ -4441,22 +3783,16 @@ }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, "license": "Python-2.0" }, "node_modules/array-timsort": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", - "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", "dev": true, "license": "MIT" }, "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": { @@ -4465,8 +3801,6 @@ }, "node_modules/ast-v8-to-istanbul": { "version": "0.3.12", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", - "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", "dev": true, "license": "MIT", "dependencies": { @@ -4477,8 +3811,6 @@ }, "node_modules/auto-bind": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", - "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -4489,8 +3821,6 @@ }, "node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { @@ -4499,8 +3829,6 @@ }, "node_modules/body-parser": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -4523,8 +3851,6 @@ }, "node_modules/brace-expansion": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4536,8 +3862,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { @@ -4549,8 +3873,6 @@ }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -4558,8 +3880,6 @@ }, "node_modules/cac": { "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, "license": "MIT", "engines": { @@ -4568,8 +3888,6 @@ }, "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", @@ -4581,8 +3899,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4597,8 +3913,6 @@ }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -4607,8 +3921,6 @@ }, "node_modules/chai": { "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, "license": "MIT", "dependencies": { @@ -4624,8 +3936,6 @@ }, "node_modules/chalk": { "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -4636,8 +3946,6 @@ }, "node_modules/chalk-template": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.2.tgz", - "integrity": "sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==", "dev": true, "license": "MIT", "dependencies": { @@ -4652,8 +3960,6 @@ }, "node_modules/character-entities": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "dev": true, "license": "MIT", "funding": { @@ -4663,8 +3969,6 @@ }, "node_modules/character-entities-legacy": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "dev": true, "license": "MIT", "funding": { @@ -4674,8 +3978,6 @@ }, "node_modules/character-reference-invalid": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "dev": true, "license": "MIT", "funding": { @@ -4685,8 +3987,6 @@ }, "node_modules/check-error": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, "license": "MIT", "engines": { @@ -4695,15 +3995,11 @@ }, "node_modules/cjs-module-lexer": { "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "license": "MIT", "optional": true }, "node_modules/clear-module": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", - "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", "dev": true, "license": "MIT", "dependencies": { @@ -4719,8 +4015,6 @@ }, "node_modules/cli-boxes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "license": "MIT", "engines": { "node": ">=10" @@ -4731,8 +4025,6 @@ }, "node_modules/cli-cursor": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "license": "MIT", "dependencies": { "restore-cursor": "^4.0.0" @@ -4746,8 +4038,6 @@ }, "node_modules/cli-truncate": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", "license": "MIT", "dependencies": { "slice-ansi": "^8.0.0", @@ -4762,8 +4052,6 @@ }, "node_modules/cli-truncate/node_modules/string-width": { "version": "8.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", - "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", "license": "MIT", "dependencies": { "get-east-asian-width": "^1.5.0", @@ -4778,8 +4066,6 @@ }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "license": "ISC", "optional": true, "dependencies": { @@ -4793,8 +4079,6 @@ }, "node_modules/cliui/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "optional": true, "engines": { @@ -4803,8 +4087,6 @@ }, "node_modules/cliui/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "optional": true, "dependencies": { @@ -4819,8 +4101,6 @@ }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "optional": true, "dependencies": { @@ -4834,8 +4114,6 @@ }, "node_modules/cliui/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "optional": true, "dependencies": { @@ -4847,8 +4125,6 @@ }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "optional": true, "dependencies": { @@ -4865,8 +4141,6 @@ }, "node_modules/code-excerpt": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", - "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", "license": "MIT", "dependencies": { "convert-to-spaces": "^2.0.1" @@ -4877,8 +4151,6 @@ }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -4890,15 +4162,11 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "devOptional": true, "license": "MIT" }, "node_modules/commander": { "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", "engines": { @@ -4907,8 +4175,6 @@ }, "node_modules/comment-json": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.6.2.tgz", - "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", "dev": true, "license": "MIT", "dependencies": { @@ -4921,8 +4187,6 @@ }, "node_modules/content-disposition": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "engines": { "node": ">=18" @@ -4934,8 +4198,6 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4943,8 +4205,6 @@ }, "node_modules/convert-to-spaces": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", - "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -4952,8 +4212,6 @@ }, "node_modules/cookie": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4961,8 +4219,6 @@ }, "node_modules/cookie-signature": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -4970,8 +4226,6 @@ }, "node_modules/cors": { "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -4987,8 +4241,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5001,8 +4253,6 @@ }, "node_modules/cspell": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.7.0.tgz", - "integrity": "sha512-ftxOnkd+scAI7RZ1/ksgBZRr0ouC7QRKtPQhD/PbLTKwAM62sSvRhE1bFsuW3VKBn/GilWzTjkJ40WmnDqH5iQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5041,8 +4291,6 @@ }, "node_modules/cspell-config-lib": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.7.0.tgz", - "integrity": "sha512-pguh8A3+bSJ1OOrKCiQan8bvaaY125de76OEFz7q1Pq309lIcDrkoL/W4aYbso/NjrXaIw6OjkgPMGRBI/IgGg==", "dev": true, "license": "MIT", "dependencies": { @@ -5057,8 +4305,6 @@ }, "node_modules/cspell-dictionary": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.7.0.tgz", - "integrity": "sha512-k/Wz0so32+0QEqQe21V9m4BNXM5ZN6lz3Ix/jLCbMxFIPl6wT711ftjOWIEMFhvUOP0TWXsbzcuE9mKtS5mTig==", "dev": true, "license": "MIT", "dependencies": { @@ -5074,8 +4320,6 @@ }, "node_modules/cspell-gitignore": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-9.7.0.tgz", - "integrity": "sha512-MtoYuH4ah4K6RrmaF834npMcRsTKw0658mC6yvmBacUQOmwB/olqyuxF3fxtbb55HDb7cXDQ35t1XuwwGEQeZw==", "dev": true, "license": "MIT", "dependencies": { @@ -5092,8 +4336,6 @@ }, "node_modules/cspell-glob": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.7.0.tgz", - "integrity": "sha512-LUeAoEsoCJ+7E3TnUmWBscpVQOmdwBejMlFn0JkXy6LQzxrybxXBKf65RSdIv1o5QtrhQIMa358xXYQG0sv/tA==", "dev": true, "license": "MIT", "dependencies": { @@ -5106,8 +4348,6 @@ }, "node_modules/cspell-grammar": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.7.0.tgz", - "integrity": "sha512-oEYME+7MJztfVY1C06aGcJgEYyqBS/v/ETkQGPzf/c6ObSAPRcUbVtsXZgnR72Gru9aBckc70xJcD6bELdoWCA==", "dev": true, "license": "MIT", "dependencies": { @@ -5123,8 +4363,6 @@ }, "node_modules/cspell-io": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.7.0.tgz", - "integrity": "sha512-V7x0JHAUCcJPRCH8c0MQkkaKmZD2yotxVyrNEx2SZTpvnKrYscLEnUUTWnGJIIf9znzISqw116PLnYu2c+zd6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5137,8 +4375,6 @@ }, "node_modules/cspell-lib": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.7.0.tgz", - "integrity": "sha512-aTx/aLRpnuY1RJnYAu+A8PXfm1oIUdvAQ4W9E66bTgp1LWI+2G2++UtaPxRIgI0olxE9vcXqUnKpjOpO+5W9bQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5173,8 +4409,6 @@ }, "node_modules/cspell-trie-lib": { "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.7.0.tgz", - "integrity": "sha512-a2YqmcraL3g6I/4gY7SYWEZfP73oLluUtxO7wxompk/kOG2K1FUXyQfZXaaR7HxVv10axT1+NrjhOmXpfbI6LA==", "dev": true, "license": "MIT", "engines": { @@ -5186,15 +4420,11 @@ }, "node_modules/csstype": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5210,8 +4440,6 @@ }, "node_modules/decode-named-character-reference": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5224,8 +4452,6 @@ }, "node_modules/deep-eql": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", "engines": { @@ -5234,15 +4460,11 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -5250,8 +4472,6 @@ }, "node_modules/dequal": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", "engines": { @@ -5260,8 +4480,6 @@ }, "node_modules/devlop": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "dev": true, "license": "MIT", "dependencies": { @@ -5274,8 +4492,6 @@ }, "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", @@ -5288,28 +4504,20 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "devOptional": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -5317,8 +4525,6 @@ }, "node_modules/enhanced-resolve": { "version": "5.20.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", - "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", "dev": true, "license": "MIT", "dependencies": { @@ -5331,8 +4537,6 @@ }, "node_modules/entities": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -5344,8 +4548,6 @@ }, "node_modules/env-paths": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-4.0.0.tgz", - "integrity": "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==", "dev": true, "license": "MIT", "dependencies": { @@ -5360,8 +4562,6 @@ }, "node_modules/environment": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "license": "MIT", "engines": { "node": ">=18" @@ -5372,8 +4572,6 @@ }, "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" @@ -5381,8 +4579,6 @@ }, "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" @@ -5390,15 +4586,11 @@ }, "node_modules/es-module-lexer": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5409,8 +4601,6 @@ }, "node_modules/es-toolkit": { "version": "1.45.1", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", - "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==", "license": "MIT", "workspaces": [ "docs", @@ -5419,8 +4609,6 @@ }, "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", @@ -5461,8 +4649,6 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "optional": true, "engines": { @@ -5471,14 +4657,10 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { @@ -5490,8 +4672,6 @@ }, "node_modules/eslint": { "version": "10.1.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz", - "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", "dev": true, "license": "MIT", "dependencies": { @@ -5546,8 +4726,6 @@ }, "node_modules/eslint-compat-utils": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", - "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5562,8 +4740,6 @@ }, "node_modules/eslint-plugin-es-x": { "version": "7.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", - "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", "dev": true, "funding": [ "https://github.com/sponsors/ota-meshi", @@ -5584,8 +4760,6 @@ }, "node_modules/eslint-plugin-n": { "version": "17.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz", - "integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==", "dev": true, "license": "MIT", "dependencies": { @@ -5611,8 +4785,6 @@ }, "node_modules/eslint-plugin-n/node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -5621,8 +4793,6 @@ }, "node_modules/eslint-scope": { "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5640,8 +4810,6 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5653,8 +4821,6 @@ }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5666,8 +4832,6 @@ }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -5676,8 +4840,6 @@ }, "node_modules/espree": { "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5694,8 +4856,6 @@ }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5707,8 +4867,6 @@ }, "node_modules/esprima": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -5721,8 +4879,6 @@ }, "node_modules/esquery": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5734,8 +4890,6 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5747,8 +4901,6 @@ }, "node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -5757,8 +4909,6 @@ }, "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": { @@ -5767,8 +4917,6 @@ }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -5777,8 +4925,6 @@ }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5786,8 +4932,6 @@ }, "node_modules/eventsource": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.1" @@ -5798,8 +4942,6 @@ }, "node_modules/eventsource-parser": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", - "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -5807,8 +4949,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": { @@ -5817,8 +4957,6 @@ }, "node_modules/express": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -5860,8 +4998,6 @@ }, "node_modules/express-rate-limit": { "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", "license": "MIT", "dependencies": { "ip-address": "^10.2.0" @@ -5878,14 +5014,10 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, "node_modules/fast-equals": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-6.0.0.tgz", - "integrity": "sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==", "dev": true, "license": "MIT", "engines": { @@ -5894,8 +5026,6 @@ }, "node_modules/fast-glob": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -5911,8 +5041,6 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { @@ -5924,22 +5052,16 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -5954,8 +5076,6 @@ }, "node_modules/fastq": { "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -5964,8 +5084,6 @@ }, "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": { @@ -5982,8 +5100,6 @@ }, "node_modules/file-entry-cache": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5995,8 +5111,6 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -6008,8 +5122,6 @@ }, "node_modules/finalhandler": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -6029,8 +5141,6 @@ }, "node_modules/find-up": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -6046,8 +5156,6 @@ }, "node_modules/flat-cache": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { @@ -6060,15 +5168,11 @@ }, "node_modules/flatted": { "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/foreground-child": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { @@ -6084,8 +5188,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -6093,8 +5195,6 @@ }, "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -6117,8 +5217,6 @@ }, "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" @@ -6126,8 +5224,6 @@ }, "node_modules/gensequence": { "version": "8.0.8", - "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-8.0.8.tgz", - "integrity": "sha512-omMVniXEXpdx/vKxGnPRoO2394Otlze28TyxECbFVyoSpZ9H3EO7lemjcB12OpQJzRW4e5tt/dL1rOxry6aMHg==", "dev": true, "license": "MIT", "engines": { @@ -6136,8 +5232,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "license": "ISC", "optional": true, "engines": { @@ -6146,8 +5240,6 @@ }, "node_modules/get-east-asian-width": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "license": "MIT", "engines": { "node": ">=18" @@ -6158,8 +5250,6 @@ }, "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", @@ -6182,8 +5272,6 @@ }, "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", @@ -6195,8 +5283,6 @@ }, "node_modules/get-tsconfig": { "version": "4.13.7", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", - "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6208,9 +5294,6 @@ }, "node_modules/glob": { "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -6230,8 +5313,6 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { @@ -6243,15 +5324,11 @@ }, "node_modules/glob/node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -6260,8 +5337,6 @@ }, "node_modules/glob/node_modules/minimatch": { "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { @@ -6276,8 +5351,6 @@ }, "node_modules/global-directory": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", - "integrity": "sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -6292,8 +5365,6 @@ }, "node_modules/globals": { "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true, "license": "MIT", "engines": { @@ -6305,8 +5376,6 @@ }, "node_modules/globby": { "version": "16.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.0.tgz", - "integrity": "sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6326,15 +5395,11 @@ }, "node_modules/globrex": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", "dev": true, "license": "MIT" }, "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" @@ -6345,15 +5410,11 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -6362,8 +5423,6 @@ }, "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" @@ -6374,8 +5433,6 @@ }, "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" @@ -6386,8 +5443,6 @@ }, "node_modules/highlight.js": { "version": "11.11.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6396,8 +5451,6 @@ }, "node_modules/hono": { "version": "4.12.22", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.22.tgz", - "integrity": "sha512-7fvVPbB92zNRsQke+uiRGwtTuef0tB2Dg4hWxYfFNvkQhIltWoyi0ONReM5LWA+jJWS3nfT5lTq+qbsIpX0IQw==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -6405,15 +5458,11 @@ }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/http-errors": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { "depd": "~2.0.0", @@ -6432,8 +5481,6 @@ }, "node_modules/iconv-lite": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -6448,8 +5495,6 @@ }, "node_modules/ignore": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { @@ -6458,8 +5503,6 @@ }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6475,8 +5518,6 @@ }, "node_modules/import-fresh/node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -6488,8 +5529,6 @@ }, "node_modules/import-fresh/node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { @@ -6498,8 +5537,6 @@ }, "node_modules/import-in-the-middle": { "version": "1.15.0", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", - "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -6511,8 +5548,6 @@ }, "node_modules/import-meta-resolve": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, "license": "MIT", "funding": { @@ -6522,8 +5557,6 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { @@ -6532,8 +5565,6 @@ }, "node_modules/indent-string": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "license": "MIT", "engines": { "node": ">=12" @@ -6544,14 +5575,10 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/ini": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", "dev": true, "license": "ISC", "engines": { @@ -6560,8 +5587,6 @@ }, "node_modules/ink": { "version": "6.8.0", - "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", - "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", "license": "MIT", "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", @@ -6609,8 +5634,6 @@ }, "node_modules/ink-testing-library": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ink-testing-library/-/ink-testing-library-4.0.0.tgz", - "integrity": "sha512-yF92kj3pmBvk7oKbSq5vEALO//o7Z9Ck/OaLNlkzXNeYdwfpxMQkSowGTFUCS5MSu9bWfSZMewGpp7bFc66D7Q==", "dev": true, "license": "MIT", "engines": { @@ -6627,20 +5650,14 @@ }, "node_modules/ink/node_modules/emoji-regex": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, "node_modules/ink/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, "node_modules/ink/node_modules/string-width": { "version": "8.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", - "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", "license": "MIT", "dependencies": { "get-east-asian-width": "^1.5.0", @@ -6655,8 +5672,6 @@ }, "node_modules/ink/node_modules/wrap-ansi": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", @@ -6672,8 +5687,6 @@ }, "node_modules/ink/node_modules/wrap-ansi/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -6689,8 +5702,6 @@ }, "node_modules/ip-address": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "engines": { "node": ">= 12" @@ -6698,8 +5709,6 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -6707,8 +5716,6 @@ }, "node_modules/is-alphabetical": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", "dev": true, "license": "MIT", "funding": { @@ -6718,8 +5725,6 @@ }, "node_modules/is-alphanumerical": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "dev": true, "license": "MIT", "dependencies": { @@ -6733,8 +5738,6 @@ }, "node_modules/is-core-module": { "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "license": "MIT", "optional": true, "dependencies": { @@ -6749,8 +5752,6 @@ }, "node_modules/is-decimal": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "dev": true, "license": "MIT", "funding": { @@ -6760,8 +5761,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -6770,8 +5769,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "devOptional": true, "license": "MIT", "engines": { @@ -6780,8 +5777,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -6793,8 +5788,6 @@ }, "node_modules/is-hexadecimal": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", "dev": true, "license": "MIT", "funding": { @@ -6804,8 +5797,6 @@ }, "node_modules/is-in-ci": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", - "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", "license": "MIT", "bin": { "is-in-ci": "cli.js" @@ -6819,8 +5810,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -6829,8 +5818,6 @@ }, "node_modules/is-path-inside": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "dev": true, "license": "MIT", "engines": { @@ -6842,14 +5829,10 @@ }, "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/is-safe-filename": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-safe-filename/-/is-safe-filename-0.1.1.tgz", - "integrity": "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==", "dev": true, "license": "MIT", "engines": { @@ -6861,14 +5844,10 @@ }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6877,8 +5856,6 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6892,8 +5869,6 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6907,8 +5882,6 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6921,8 +5894,6 @@ }, "node_modules/jackspeak": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -6937,8 +5908,6 @@ }, "node_modules/jose": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -6946,15 +5915,11 @@ }, "node_modules/js-tokens": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -6966,42 +5931,30 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, "node_modules/json-schema-typed": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "license": "BSD-2-Clause" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, "node_modules/jsonc-parser": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "dev": true, "license": "MIT" }, "node_modules/katex": { "version": "0.16.44", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.44.tgz", - "integrity": "sha512-EkxoDTk8ufHqHlf9QxGwcxeLkWRR3iOuYfRpfORgYfqc8s13bgb+YtRY59NK5ZpRaCwq1kqA6a5lpX8C/eLphQ==", "dev": true, "funding": [ "https://opencollective.com/katex", @@ -7017,8 +5970,6 @@ }, "node_modules/katex/node_modules/commander": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, "license": "MIT", "engines": { @@ -7027,8 +5978,6 @@ }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { @@ -7037,8 +5986,6 @@ }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7051,8 +5998,6 @@ }, "node_modules/linkify-it": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7061,8 +6006,6 @@ }, "node_modules/locate-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -7077,43 +6020,31 @@ }, "node_modules/lodash.camelcase": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "license": "MIT", "optional": true }, "node_modules/long": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0", "optional": true }, "node_modules/loupe": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, "license": "MIT" }, "node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/lunr": { "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", "dev": true, "license": "MIT" }, "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": { @@ -7122,8 +6053,6 @@ }, "node_modules/magicast": { "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7134,8 +6063,6 @@ }, "node_modules/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -7150,8 +6077,6 @@ }, "node_modules/markdown-it": { "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", "dev": true, "license": "MIT", "dependencies": { @@ -7168,8 +6093,6 @@ }, "node_modules/markdownlint": { "version": "0.40.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.40.0.tgz", - "integrity": "sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg==", "dev": true, "license": "MIT", "dependencies": { @@ -7192,8 +6115,6 @@ }, "node_modules/markdownlint-cli2": { "version": "0.21.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.21.0.tgz", - "integrity": "sha512-DzzmbqfMW3EzHsunP66x556oZDzjcdjjlL2bHG4PubwnL58ZPAfz07px4GqteZkoCGnBYi779Y2mg7+vgNCwbw==", "dev": true, "license": "MIT", "dependencies": { @@ -7217,8 +6138,6 @@ }, "node_modules/markdownlint-cli2-formatter-default": { "version": "0.0.6", - "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.6.tgz", - "integrity": "sha512-VVDGKsq9sgzu378swJ0fcHfSicUnMxnL8gnLm/Q4J/xsNJ4e5bA6lvAz7PCzIl0/No0lHyaWdqVD2jotxOSFMQ==", "dev": true, "license": "MIT", "funding": { @@ -7230,8 +6149,6 @@ }, "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" @@ -7239,15 +6156,11 @@ }, "node_modules/mdurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true, "license": "MIT" }, "node_modules/media-typer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -7255,8 +6168,6 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -7267,8 +6178,6 @@ }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", "engines": { @@ -7277,8 +6186,6 @@ }, "node_modules/micromark": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", "dev": true, "funding": [ { @@ -7313,8 +6220,6 @@ }, "node_modules/micromark-core-commonmark": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", "dev": true, "funding": [ { @@ -7348,8 +6253,6 @@ }, "node_modules/micromark-extension-directive": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", - "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", "dev": true, "license": "MIT", "dependencies": { @@ -7368,8 +6271,6 @@ }, "node_modules/micromark-extension-gfm-autolink-literal": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "dev": true, "license": "MIT", "dependencies": { @@ -7385,8 +6286,6 @@ }, "node_modules/micromark-extension-gfm-footnote": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "dev": true, "license": "MIT", "dependencies": { @@ -7406,8 +6305,6 @@ }, "node_modules/micromark-extension-gfm-table": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", "dev": true, "license": "MIT", "dependencies": { @@ -7424,8 +6321,6 @@ }, "node_modules/micromark-extension-math": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", - "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", "dev": true, "license": "MIT", "dependencies": { @@ -7444,8 +6339,6 @@ }, "node_modules/micromark-factory-destination": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "dev": true, "funding": [ { @@ -7466,8 +6359,6 @@ }, "node_modules/micromark-factory-label": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "dev": true, "funding": [ { @@ -7489,8 +6380,6 @@ }, "node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "dev": true, "funding": [ { @@ -7510,8 +6399,6 @@ }, "node_modules/micromark-factory-title": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "dev": true, "funding": [ { @@ -7533,8 +6420,6 @@ }, "node_modules/micromark-factory-whitespace": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "dev": true, "funding": [ { @@ -7556,8 +6441,6 @@ }, "node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "dev": true, "funding": [ { @@ -7577,8 +6460,6 @@ }, "node_modules/micromark-util-chunked": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "dev": true, "funding": [ { @@ -7597,8 +6478,6 @@ }, "node_modules/micromark-util-classify-character": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "dev": true, "funding": [ { @@ -7619,8 +6498,6 @@ }, "node_modules/micromark-util-combine-extensions": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "dev": true, "funding": [ { @@ -7640,8 +6517,6 @@ }, "node_modules/micromark-util-decode-numeric-character-reference": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "dev": true, "funding": [ { @@ -7660,8 +6535,6 @@ }, "node_modules/micromark-util-encode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "dev": true, "funding": [ { @@ -7677,8 +6550,6 @@ }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "dev": true, "funding": [ { @@ -7694,8 +6565,6 @@ }, "node_modules/micromark-util-normalize-identifier": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "dev": true, "funding": [ { @@ -7714,8 +6583,6 @@ }, "node_modules/micromark-util-resolve-all": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "dev": true, "funding": [ { @@ -7734,8 +6601,6 @@ }, "node_modules/micromark-util-sanitize-uri": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "dev": true, "funding": [ { @@ -7756,8 +6621,6 @@ }, "node_modules/micromark-util-subtokenize": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", "dev": true, "funding": [ { @@ -7779,8 +6642,6 @@ }, "node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "dev": true, "funding": [ { @@ -7796,8 +6657,6 @@ }, "node_modules/micromark-util-types": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "dev": true, "funding": [ { @@ -7813,8 +6672,6 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -7827,8 +6684,6 @@ }, "node_modules/micromatch/node_modules/picomatch": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -7840,8 +6695,6 @@ }, "node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7849,8 +6702,6 @@ }, "node_modules/mime-types": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -7865,8 +6716,6 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "license": "MIT", "engines": { "node": ">=6" @@ -7874,8 +6723,6 @@ }, "node_modules/minimatch": { "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -7890,8 +6737,6 @@ }, "node_modules/minipass": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -7900,21 +6745,15 @@ }, "node_modules/module-details-from-path": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "license": "MIT", "optional": true }, "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": [ { @@ -7932,15 +6771,11 @@ }, "node_modules/natural-compare": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, "node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7948,15 +6783,11 @@ }, "node_modules/node-addon-api": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "license": "MIT", "optional": true }, "node_modules/node-pty": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz", - "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", "hasInstallScript": true, "license": "MIT", "optional": true, @@ -7966,8 +6797,6 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7975,8 +6804,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -7987,8 +6814,6 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -7999,8 +6824,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -8008,8 +6831,6 @@ }, "node_modules/onetime": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -8023,8 +6844,6 @@ }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -8041,8 +6860,6 @@ }, "node_modules/p-limit": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8057,8 +6874,6 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -8073,15 +6888,11 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", - "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", "dev": true, "license": "MIT", "dependencies": { @@ -8093,8 +6904,6 @@ }, "node_modules/parse-entities": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "dev": true, "license": "MIT", "dependencies": { @@ -8113,8 +6922,6 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -8122,8 +6929,6 @@ }, "node_modules/patch-console": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", - "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -8131,8 +6936,6 @@ }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -8141,8 +6944,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { "node": ">=8" @@ -8150,15 +6951,11 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT", "optional": true }, "node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -8174,8 +6971,6 @@ }, "node_modules/path-to-regexp": { "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -8184,15 +6979,11 @@ }, "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/pathval": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -8201,15 +6992,11 @@ }, "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.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -8221,8 +7008,6 @@ }, "node_modules/pkce-challenge": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "license": "MIT", "engines": { "node": ">=16.20.0" @@ -8230,8 +7015,6 @@ }, "node_modules/playwright": { "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -8249,8 +7032,6 @@ }, "node_modules/playwright-core": { "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -8262,8 +7043,6 @@ }, "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": [ { @@ -8291,8 +7070,6 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -8301,8 +7078,6 @@ }, "node_modules/protobufjs": { "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", "hasInstallScript": true, "license": "BSD-3-Clause", "optional": true, @@ -8326,8 +7101,6 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -8339,8 +7112,6 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { @@ -8349,8 +7120,6 @@ }, "node_modules/punycode.js": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, "license": "MIT", "engines": { @@ -8359,8 +7128,6 @@ }, "node_modules/qrcode-terminal": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", - "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", "optional": true, "bin": { "qrcode-terminal": "bin/qrcode-terminal.js" @@ -8368,8 +7135,6 @@ }, "node_modules/qs": { "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -8383,8 +7148,6 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -8404,8 +7167,6 @@ }, "node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8413,8 +7174,6 @@ }, "node_modules/raw-body": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -8428,8 +7187,6 @@ }, "node_modules/react": { "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8437,8 +7194,6 @@ }, "node_modules/react-reconciler": { "version": "0.33.0", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", - "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" @@ -8452,8 +7207,6 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", "optional": true, "engines": { @@ -8462,8 +7215,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8471,8 +7222,6 @@ }, "node_modules/require-in-the-middle": { "version": "7.5.2", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", - "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", "license": "MIT", "optional": true, "dependencies": { @@ -8486,8 +7235,6 @@ }, "node_modules/resolve": { "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "license": "MIT", "optional": true, "dependencies": { @@ -8507,8 +7254,6 @@ }, "node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -8517,8 +7262,6 @@ }, "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": { @@ -8527,8 +7270,6 @@ }, "node_modules/restore-cursor": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "license": "MIT", "dependencies": { "onetime": "^5.1.0", @@ -8543,14 +7284,10 @@ }, "node_modules/restore-cursor/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, "node_modules/reusify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -8560,8 +7297,6 @@ }, "node_modules/rollup": { "version": "4.60.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", - "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8605,8 +7340,6 @@ }, "node_modules/router": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -8621,8 +7354,6 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -8645,20 +7376,14 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/scheduler": { "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/semver": { "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "devOptional": true, "license": "ISC", "bin": { @@ -8670,8 +7395,6 @@ }, "node_modules/send": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { "debug": "^4.4.3", @@ -8696,8 +7419,6 @@ }, "node_modules/serve-static": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -8715,14 +7436,10 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -8733,8 +7450,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { "node": ">=8" @@ -8742,15 +7457,11 @@ }, "node_modules/shimmer": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", "license": "BSD-2-Clause", "optional": true }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8768,8 +7479,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8784,8 +7493,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8802,8 +7509,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8821,15 +7526,11 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -8841,8 +7542,6 @@ }, "node_modules/slash": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, "license": "MIT", "engines": { @@ -8854,8 +7553,6 @@ }, "node_modules/slice-ansi": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", "license": "MIT", "dependencies": { "ansi-styles": "^6.2.3", @@ -8870,8 +7567,6 @@ }, "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "license": "MIT", "dependencies": { "get-east-asian-width": "^1.3.1" @@ -8885,8 +7580,6 @@ }, "node_modules/smol-toml": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", - "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -8898,8 +7591,6 @@ }, "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": { @@ -8908,15 +7599,11 @@ }, "node_modules/sql.js": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.1.tgz", - "integrity": "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A==", "license": "MIT", "optional": true }, "node_modules/stack-utils": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" @@ -8927,8 +7614,6 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "license": "MIT", "engines": { "node": ">=8" @@ -8936,15 +7621,11 @@ }, "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/statuses": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -8952,15 +7633,11 @@ }, "node_modules/std-env": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, "node_modules/string-width": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", "license": "MIT", "dependencies": { "get-east-asian-width": "^1.3.0", @@ -8976,8 +7653,6 @@ "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -8991,8 +7666,6 @@ }, "node_modules/string-width-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -9001,8 +7674,6 @@ }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -9014,8 +7685,6 @@ }, "node_modules/strip-ansi": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -9030,8 +7699,6 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -9043,8 +7710,6 @@ }, "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -9053,8 +7718,6 @@ }, "node_modules/strip-literal": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", "dev": true, "license": "MIT", "dependencies": { @@ -9066,15 +7729,11 @@ }, "node_modules/strip-literal/node_modules/js-tokens": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true, "license": "MIT" }, "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -9086,8 +7745,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "license": "MIT", "optional": true, "engines": { @@ -9099,8 +7756,6 @@ }, "node_modules/tagged-tag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", - "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", "license": "MIT", "engines": { "node": ">=20" @@ -9111,8 +7766,6 @@ }, "node_modules/tapable": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", - "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", "dev": true, "license": "MIT", "engines": { @@ -9125,8 +7778,6 @@ }, "node_modules/terminal-size": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", - "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", "license": "MIT", "engines": { "node": ">=18" @@ -9137,8 +7788,6 @@ }, "node_modules/test-exclude": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", - "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, "license": "ISC", "dependencies": { @@ -9152,22 +7801,16 @@ }, "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": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, "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": { @@ -9183,8 +7826,6 @@ }, "node_modules/tinypool": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, "license": "MIT", "engines": { @@ -9193,8 +7834,6 @@ }, "node_modules/tinyrainbow": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", "engines": { @@ -9203,8 +7842,6 @@ }, "node_modules/tinyspy": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "license": "MIT", "engines": { @@ -9213,8 +7850,6 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9226,8 +7861,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -9235,8 +7868,6 @@ }, "node_modules/ts-api-utils": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -9248,8 +7879,6 @@ }, "node_modules/ts-declaration-location": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", - "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", "dev": true, "funding": [ { @@ -9271,8 +7900,6 @@ }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -9284,8 +7911,6 @@ }, "node_modules/type-fest": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz", - "integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==", "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" @@ -9299,8 +7924,6 @@ }, "node_modules/type-is": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { "content-type": "^2.0.0", @@ -9317,8 +7940,6 @@ }, "node_modules/type-is/node_modules/content-type": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "license": "MIT", "engines": { "node": ">=18" @@ -9330,8 +7951,6 @@ }, "node_modules/typedoc": { "version": "0.28.18", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.18.tgz", - "integrity": "sha512-NTWTUOFRQ9+SGKKTuWKUioUkjxNwtS3JDRPVKZAXGHZy2wCA8bdv2iJiyeePn0xkmK+TCCqZFT0X7+2+FLjngA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -9354,8 +7973,6 @@ }, "node_modules/typedoc-plugin-markdown": { "version": "4.11.0", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.11.0.tgz", - "integrity": "sha512-2iunh2ALyfyh204OF7h2u0kuQ84xB3jFZtFyUr01nThJkLvR8oGGSSDlyt2gyO4kXhvUxDcVbO0y43+qX+wFbw==", "dev": true, "license": "MIT", "engines": { @@ -9367,8 +7984,6 @@ }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -9381,22 +7996,16 @@ }, "node_modules/uc.micro": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true, "license": "MIT" }, "node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "devOptional": true, "license": "MIT" }, "node_modules/unicorn-magic": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", - "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", "dev": true, "license": "MIT", "engines": { @@ -9408,8 +8017,6 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -9417,8 +8024,6 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -9427,8 +8032,6 @@ }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -9436,8 +8039,6 @@ }, "node_modules/vite": { "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "dependencies": { @@ -9511,8 +8112,6 @@ }, "node_modules/vite-node": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, "license": "MIT", "dependencies": { @@ -9549,8 +8148,6 @@ }, "node_modules/vitest": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", "dependencies": { @@ -9622,8 +8219,6 @@ }, "node_modules/vscode-jsonrpc": { "version": "8.2.1", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", - "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -9631,22 +8226,16 @@ }, "node_modules/vscode-languageserver-textdocument": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", "dev": true, "license": "MIT" }, "node_modules/vscode-uri": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "dev": true, "license": "MIT" }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -9660,8 +8249,6 @@ }, "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": { @@ -9677,8 +8264,6 @@ }, "node_modules/widest-line": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", - "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", "license": "MIT", "dependencies": { "string-width": "^8.1.0" @@ -9692,8 +8277,6 @@ }, "node_modules/word-wrap": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -9702,8 +8285,6 @@ }, "node_modules/wrap-ansi": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9721,8 +8302,6 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -9739,8 +8318,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -9749,8 +8326,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -9765,8 +8340,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -9780,8 +8353,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -9793,15 +8364,11 @@ }, "node_modules/wrap-ansi/node_modules/emoji-regex": { "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { @@ -9818,14 +8385,10 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/ws": { "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -9845,8 +8408,6 @@ }, "node_modules/xdg-basedir": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", "dev": true, "license": "MIT", "engines": { @@ -9858,8 +8419,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", "optional": true, "engines": { @@ -9868,8 +8427,6 @@ }, "node_modules/yaml": { "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", "bin": { @@ -9884,8 +8441,6 @@ }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "license": "MIT", "optional": true, "dependencies": { @@ -9903,8 +8458,6 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "license": "ISC", "optional": true, "engines": { @@ -9913,8 +8466,6 @@ }, "node_modules/yargs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "optional": true, "engines": { @@ -9923,8 +8474,6 @@ }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "optional": true, "dependencies": { @@ -9938,8 +8487,6 @@ }, "node_modules/yargs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "optional": true, "dependencies": { @@ -9951,8 +8498,6 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -9964,14 +8509,10 @@ }, "node_modules/yoga-layout": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", - "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", "license": "MIT" }, "node_modules/zod": { "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -9979,8 +8520,6 @@ }, "node_modules/zod-to-json-schema": { "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { "zod": "^3.25.28 || ^4" @@ -9988,11 +8527,11 @@ }, "packages/squad-cli": { "name": "@bradygaster/squad-cli", - "version": "0.9.6", + "version": "0.9.6-preview.1", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@bradygaster/squad-sdk": ">=0.9.0-0", + "@bradygaster/squad-sdk": ">=0.9.6-preview", "@modelcontextprotocol/sdk": "^1.29.0", "ink": "^6.8.0", "react": "^19.2.4", @@ -10019,7 +8558,7 @@ }, "packages/squad-sdk": { "name": "@bradygaster/squad-sdk", - "version": "0.9.6", + "version": "0.9.6-preview.1", "license": "MIT", "dependencies": { "@github/copilot-sdk": "^0.3.0", @@ -10052,8 +8591,6 @@ }, "packages/squad-sdk/node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -10068,8 +8605,6 @@ }, "packages/squad-sdk/node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "optional": true, "dependencies": { diff --git a/package.json b/package.json index 42b98b082..e5789749f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bradygaster/squad", - "version": "0.9.6", + "version": "0.9.6-preview.14", "private": true, "description": "Squad — Programmable multi-agent runtime for GitHub Copilot, built on @github/copilot-sdk", "type": "module", diff --git a/packages/squad-cli/package.json b/packages/squad-cli/package.json index ef48f947b..1b53b77de 100644 --- a/packages/squad-cli/package.json +++ b/packages/squad-cli/package.json @@ -1,6 +1,6 @@ { "name": "@bradygaster/squad-cli", - "version": "0.9.7-preview", + "version": "0.9.6-preview.15", "description": "Squad CLI — Command-line interface for the Squad multi-agent runtime", "type": "module", "bin": { @@ -186,7 +186,7 @@ "node": ">=22.5.0" }, "dependencies": { - "@bradygaster/squad-sdk": ">=0.9.0-0", + "@bradygaster/squad-sdk": ">=0.9.6-preview", "@modelcontextprotocol/sdk": "^1.29.0", "ink": "^6.8.0", "react": "^19.2.4", diff --git a/packages/squad-cli/scripts/patch-esm-imports.mjs b/packages/squad-cli/scripts/patch-esm-imports.mjs index 0d6334b85..7c0782b3c 100644 --- a/packages/squad-cli/scripts/patch-esm-imports.mjs +++ b/packages/squad-cli/scripts/patch-esm-imports.mjs @@ -23,11 +23,13 @@ import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); // Locations where npm workspaces / global install may place dependencies +const _cwdNodeModules = join(process.cwd(), 'node_modules'); const SEARCH_ROOTS = [ join(__dirname, '..', 'node_modules'), // squad-cli local join(__dirname, '..', '..', '..', 'node_modules'), // workspace root join(__dirname, '..', '..'), // global install (sibling) -]; + _cwdNodeModules, // consumer project (cwd) +].filter((p, i, arr) => arr.indexOf(p) === i); // deduplicate /** * Layer 1 — Inject `exports` field into vscode-jsonrpc/package.json. diff --git a/packages/squad-cli/src/cli-entry.ts b/packages/squad-cli/src/cli-entry.ts index bd9de28a0..a6b96089d 100644 --- a/packages/squad-cli/src/cli-entry.ts +++ b/packages/squad-cli/src/cli-entry.ts @@ -171,6 +171,9 @@ async function main(): Promise { console.log(` --state-backend (migrate to orphan|two-layer)`); console.log(` ${BOLD}migrate${RESET} Convert between markdown and SDK-First squad formats`); console.log(` Flags: --to sdk|markdown, --from ai-team, --dry-run`); + console.log(` ${BOLD}sync${RESET} Sync squad-state branch(es) with remote (push/pull/both)`); + console.log(` Flags: --push, --pull, --remote , --quiet`); + console.log(` No-op for local/worktree backends. Invoked by git hooks.`); console.log(` ${BOLD}status${RESET} Show which squad is active and why`); console.log(` ${BOLD}roles${RESET} List built-in Squad roles`); console.log(` Usage: roles [--category ] [--search ]`); @@ -412,30 +415,79 @@ async function main(): Promise { // Continue with regular upgrade after migration } - // Handle --self: upgrade the CLI package itself + // Handle --self: upgrade the CLI package itself. + // + // UPGRADE-EPERM-FALSE-SUCCESS fix (iter-2): surface a failed self-upgrade + // instead of printing "✅ Upgraded" after a warning. + // + // Iter-4 hardening: when BOTH --self and --state-backend are passed and + // the self-upgrade fails (e.g. EPERM on a globally-installed CLI that + // can't be replaced by the current user), still run the state-backend + // migration. The two operations are independent — failing the npm + // install must not block the user from upgrading their existing project's + // on-disk state layout. Failures are tracked and we exit non-zero at the + // end if either step failed. + let selfUpgradeFailed: string | null = null; if (selfUpgrade) { - await selfUpgradeCli({ insider, force: forceUpgrade }); - console.log('✅ Upgraded. Please restart your terminal for changes to take effect.'); - return; + try { + await selfUpgradeCli({ insider, force: forceUpgrade }); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + selfUpgradeFailed = msg; + if (upgradeStateBackend) { + // Defer the failure: still attempt the state-backend migration so + // the user gets at least one of the two operations they asked for. + console.error(`⚠️ Self-upgrade failed: ${msg}`); + console.error(' Continuing with --state-backend migration. Self-upgrade can be retried separately.'); + } else { + console.error(`❌ Self-upgrade failed: ${msg}`); + process.exit(1); + } + } + if (!selfUpgradeFailed && !upgradeStateBackend) { + console.log('✅ Upgraded. Please restart your terminal for changes to take effect.'); + return; + } + if (!selfUpgradeFailed) { + console.log('✅ Self-upgrade complete. Running --state-backend migration next…'); + } } - // Run upgrade - await runUpgrade(dest, { - migrateDirectory: migrateDir, - self: selfUpgrade, - force: forceUpgrade - }); + // Run upgrade (skip when --self was successful AND no state-backend asked — + // that case returned above). Otherwise we always run a project upgrade so + // hooks/templates are refreshed alongside the backend migration. + if (!selfUpgrade || upgradeStateBackend) { + await runUpgrade(dest, { + migrateDirectory: migrateDir, + self: selfUpgrade, + force: forceUpgrade, + }); + } // Handle --state-backend: migrate backend after upgrade if (upgradeStateBackend) { const { migrateStateBackend } = await import('./cli/commands/migrate-backend.js'); - await migrateStateBackend(dest, upgradeStateBackend); + try { + await migrateStateBackend(dest, upgradeStateBackend); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + console.error(`❌ State-backend migration failed: ${msg}`); + process.exit(1); + } } else { // Ensure hooks are installed for existing orphan/two-layer backends const { ensureHooksForBackend } = await import('./cli/commands/install-hooks.js'); ensureHooksForBackend(dest); } - + + if (selfUpgradeFailed) { + // Partial success — state-backend migration completed but self-upgrade + // did not. Exit non-zero so callers (CI, wrapper scripts) can detect it. + console.error(`❌ Self-upgrade failed earlier: ${selfUpgradeFailed}`); + console.error(' The project upgrade and state-backend migration succeeded; retry the self-upgrade manually.'); + process.exit(1); + } + return; } @@ -451,6 +503,26 @@ async function main(): Promise { return; } + if (cmd === 'sync') { + const { runSync } = await import('./cli/commands/sync.js'); + const quiet = args.includes('--quiet'); + const remoteIdx = args.indexOf('--remote'); + const remote = (remoteIdx !== -1 && args[remoteIdx + 1]) ? args[remoteIdx + 1] : undefined; + let direction: 'push' | 'pull' | 'both' = 'both'; + if (args.includes('--push') && !args.includes('--pull')) direction = 'push'; + else if (args.includes('--pull') && !args.includes('--push')) direction = 'pull'; + try { + await runSync({ direction, remote, cwd: getSquadStartDir(), quiet }); + } catch (err: unknown) { + if (!quiet) { + const msg = err instanceof Error ? err.message : String(err); + console.error(`squad sync failed: ${msg}`); + } + process.exit(1); + } + return; + } + if (cmd === 'migrate') { const { runMigrate } = await import('./cli/commands/migrate.js'); const toIdx = args.indexOf('--to'); @@ -1026,6 +1098,12 @@ async function main(): Promise { return; } + if (cmd === 'notes') { + const { runNotes } = await import('./cli/commands/notes.js'); + await runNotes(getSquadStartDir(), args.slice(1)); + return; + } + if (cmd === 'config') { const { runConfig } = await import('./cli/commands/config.js'); await runConfig(getSquadStartDir(), args.slice(1)); diff --git a/packages/squad-cli/src/cli/commands/copilot-bridge.ts b/packages/squad-cli/src/cli/commands/copilot-bridge.ts index 2eaf62934..31c25f6c8 100644 --- a/packages/squad-cli/src/cli/commands/copilot-bridge.ts +++ b/packages/squad-cli/src/cli/commands/copilot-bridge.ts @@ -7,6 +7,7 @@ import { spawn, execSync, type ChildProcess } from 'node:child_process'; import { createInterface } from 'node:readline'; +import { withAdditionalMcpConfig } from '../core/copilot-invocation.js'; export interface CopilotBridgeConfig { cwd: string; @@ -76,7 +77,11 @@ export class CopilotBridge { args.push('--agent', this.config.agent); } - this.child = spawn('copilot', args, { + // Inject project mcp-config so squad_state MCP tools register (Copilot + // CLI 1.0.58 ignores the project-level .copilot/mcp-config.json). + const finalArgs = withAdditionalMcpConfig('copilot', args, this.config.cwd); + + this.child = spawn('copilot', finalArgs, { cwd: this.config.cwd, stdio: ['pipe', 'pipe', 'pipe'], }); diff --git a/packages/squad-cli/src/cli/commands/doctor.ts b/packages/squad-cli/src/cli/commands/doctor.ts index d0cf1e1f4..26544ce0a 100644 --- a/packages/squad-cli/src/cli/commands/doctor.ts +++ b/packages/squad-cli/src/cli/commands/doctor.ts @@ -11,8 +11,9 @@ */ import path from 'node:path'; -import { execFile } from 'node:child_process'; +import { execFile, execFileSync } from 'node:child_process'; import { FSStorageProvider } from '@bradygaster/squad-sdk'; +import { resolveStateDir } from '../core/effective-squad-dir.js'; const storage = new FSStorageProvider(); @@ -464,6 +465,88 @@ function checkCopilotCli(): Promise { }); } +// ── git sync hooks check ───────────────────────────────────────────── + +const SQUAD_SYNC_HOOK_MARKER = '# --- squad-sync-hook ---'; +const REQUIRED_SYNC_HOOKS = ['pre-push', 'post-merge', 'post-rewrite', 'post-checkout'] as const; + +/** + * Check that squad git sync hooks are installed when the state backend requires them. + * Only runs for 'two-layer' and 'orphan' backends (which need hooks to push state branches). + * Returns undefined when the check is not applicable. + */ +export function checkGitSyncHooks(cwd: string, squadDir: string): DoctorCheck | undefined { + const configPath = path.join(squadDir, 'config.json'); + if (!fileExists(configPath)) return undefined; + + const config = tryReadJson(configPath) as Record | undefined; + if (!config) return undefined; + + const stateBackend = config['stateBackend']; + if (stateBackend !== 'two-layer' && stateBackend !== 'orphan') return undefined; + + // Resolve the git hooks directory (respects core.hooksPath when configured) + let hooksDir: string; + try { + const customPath = execFileSync('git', ['config', '--get', 'core.hooksPath'], { + cwd, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + if (customPath) { + hooksDir = path.isAbsolute(customPath) ? customPath : path.resolve(cwd, customPath); + } else { + throw new Error('empty hooksPath'); + } + } catch { + // core.hooksPath not configured — resolve via git rev-parse --git-dir + // This handles git worktrees correctly (unlike hardcoding .git/hooks) + try { + const gitDir = execFileSync('git', ['rev-parse', '--git-dir'], { + cwd, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + hooksDir = path.resolve(cwd, gitDir, 'hooks'); + } catch { + hooksDir = path.join(cwd, '.git', 'hooks'); + } + } + + const missingHooks: string[] = []; + for (const hookName of REQUIRED_SYNC_HOOKS) { + const hookPath = path.join(hooksDir, hookName); + if (!fileExists(hookPath)) { + missingHooks.push(hookName); + continue; + } + try { + const content = storage.readSync(hookPath) ?? ''; + if (!content.includes(SQUAD_SYNC_HOOK_MARKER)) { + missingHooks.push(hookName); + } + } catch { + missingHooks.push(hookName); + } + } + + if (missingHooks.length > 0) { + return { + name: 'git sync hooks installed', + status: 'fail', + message: + `Missing squad sync hooks for '${stateBackend}' backend: ${missingHooks.join(', ')}. ` + + `Run 'squad install-hooks' to install them.`, + }; + } + + return { + name: 'git sync hooks installed', + status: 'pass', + message: `squad sync hooks present for '${stateBackend}' backend`, + }; +} + // ── public API ────────────────────────────────────────────────────── /** @@ -493,13 +576,19 @@ export async function runDoctor(cwd?: string): Promise { // 5–9 standard files (only if .squad/ exists) if (isDirectory(squadDir)) { - checks.push(checkTeamMd(squadDir)); - checks.push(checkRoutingMd(squadDir)); - checks.push(checkAgentsDir(squadDir)); - checks.push(checkCastingRegistry(squadDir)); - checks.push(checkDecisionsMd(squadDir)); + // Resolve effective state dir for externalized files + const stateDir = resolveStateDir(squadDir); + checks.push(checkTeamMd(stateDir)); + checks.push(checkRoutingMd(stateDir)); + checks.push(checkAgentsDir(stateDir)); + checks.push(checkCastingRegistry(stateDir)); + checks.push(checkDecisionsMd(stateDir)); const rateLimitCheck = checkRateLimitStatus(squadDir); if (rateLimitCheck) checks.push(rateLimitCheck); + + // Hook presence check (only for two-layer / orphan backends) + const hookCheck = checkGitSyncHooks(resolvedCwd, squadDir); + if (hookCheck) checks.push(hookCheck); } // 10. Copilot agent discovery file (relative to cwd, not squadDir) diff --git a/packages/squad-cli/src/cli/commands/install-hooks.ts b/packages/squad-cli/src/cli/commands/install-hooks.ts index b0dfce04a..759e2cc0c 100644 --- a/packages/squad-cli/src/cli/commands/install-hooks.ts +++ b/packages/squad-cli/src/cli/commands/install-hooks.ts @@ -106,6 +106,39 @@ if [ "\$3" = "1" ] && [ -z "$SQUAD_SYNC_ACTIVE" ]; then git fetch "$REMOTE" '+refs/notes/squad*:refs/notes/squad*' 2>/dev/null || true unset SQUAD_SYNC_ACTIVE fi +`, + 'pre-commit': `#!/bin/sh +${SQUAD_HOOK_MARKER} +# WI-1: Guard against accidentally committing two-layer mutable state into the +# working tree. If the user has staged any .squad/ paths that are owned by the +# two-layer/orphan backend (decisions.md, agents/*/history.md, casting/, routing/), +# warn and abort so the state stays on the squad-state orphan branch. +# Installed by: squad init / squad upgrade --state-backend (two-layer/orphan) +if [ -z "$SQUAD_SYNC_ACTIVE" ]; then + STAGED=$(git diff --cached --name-only 2>/dev/null | grep -E '^\\.squad/(decisions\\.md|agents/.+/history\\.md|casting/|routing/)' || true) + if [ -n "$STAGED" ]; then + echo "⚠ squad pre-commit: refusing to commit two-layer state into the working tree." >&2 + echo " These paths belong on the 'squad-state' orphan branch, not in your normal commits:" >&2 + echo "$STAGED" | sed 's/^/ /' >&2 + echo " Use 'git restore --staged ' to unstage, or set SQUAD_SYNC_ACTIVE=1 to bypass." >&2 + exit 1 + fi +fi +`, + 'post-commit': `#!/bin/sh +${SQUAD_HOOK_MARKER} +# WI-1: After a working-tree commit, sync any pending two-layer state (decisions, +# histories, casting) onto the squad-state orphan branch so team-state stays +# durable and shareable. Best-effort — never blocks the commit. +# Installed by: squad init / squad upgrade --state-backend (two-layer/orphan) +if [ -z "$SQUAD_SYNC_ACTIVE" ]; then + export SQUAD_SYNC_ACTIVE=1 + # If the squad CLI is on PATH, ask it to flush any pending state. + if command -v squad >/dev/null 2>&1; then + squad sync --quiet 2>/dev/null || true + fi + unset SQUAD_SYNC_ACTIVE +fi `, }; @@ -248,11 +281,17 @@ export function ensureHooksForBackend(cwd: string): void { hooksDir = getHooksDir(cwd); } catch { return; } - const prePushPath = path.join(hooksDir, 'pre-push'); - if (fs.existsSync(prePushPath)) { - const content = fs.readFileSync(prePushPath, 'utf-8'); - if (content.includes(SQUAD_HOOK_MARKER)) return; // Already installed + // WI-1: verify ALL squad hooks are present (sync hooks + commit hooks). + // If any of the required hooks is missing or lacks our marker, reinstall. + const requiredHooks = ['pre-push', 'post-merge', 'post-rewrite', 'post-checkout', 'pre-commit', 'post-commit']; + let allInstalled = true; + for (const hookName of requiredHooks) { + const hookPath = path.join(hooksDir, hookName); + if (!fs.existsSync(hookPath)) { allInstalled = false; break; } + const content = fs.readFileSync(hookPath, 'utf-8'); + if (!content.includes(SQUAD_HOOK_MARKER)) { allInstalled = false; break; } } + if (allInstalled) return; // Hooks missing — install them installGitHooks(cwd, { force: false }); diff --git a/packages/squad-cli/src/cli/commands/loop.ts b/packages/squad-cli/src/cli/commands/loop.ts index 46c36d433..e52432e83 100644 --- a/packages/squad-cli/src/cli/commands/loop.ts +++ b/packages/squad-cli/src/cli/commands/loop.ts @@ -11,9 +11,10 @@ import { execFile, type ChildProcess } from 'node:child_process'; import { existsSync, readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; -import { detectSquadDir } from '../core/detect-squad-dir.js'; +import { effectiveSquadDir } from '../core/effective-squad-dir.js'; import { fatal } from '../core/errors.js'; import { GREEN, RED, DIM, BOLD, RESET, YELLOW } from '../core/output.js'; +import { withAdditionalMcpConfig } from '../core/copilot-invocation.js'; import { CapabilityRegistry, createDefaultRegistry, @@ -132,7 +133,7 @@ export function generateLoopFile(): string { function buildLoopAgentCommand( prompt: string, - options: { agentCmd?: string; copilotFlags?: string }, + options: { agentCmd?: string; copilotFlags?: string; teamRoot?: string }, ): { cmd: string; args: string[] } { if (options.agentCmd) { const parts = options.agentCmd.trim().split(/\s+/); @@ -142,7 +143,7 @@ function buildLoopAgentCommand( if (options.copilotFlags) { args.push(...options.copilotFlags.trim().split(/\s+/)); } - return { cmd: 'copilot', args }; + return { cmd: 'copilot', args: withAdditionalMcpConfig('copilot', args, options.teamRoot) }; } // ── Capability Phase Runner ────────────────────────────────────── @@ -265,9 +266,9 @@ async function checkCopilotCli(): Promise { export async function runLoop(dest: string, options: LoopConfig): Promise { const workTreeRoot = path.resolve(dest); - // Detect squad directory (must exist) - const squadDirInfo = detectSquadDir(workTreeRoot); - const teamMd = path.join(squadDirInfo.path, 'team.md'); + // Detect squad directory (must exist) — follows external state if configured + const { local: squadDirInfo, stateDir } = effectiveSquadDir(workTreeRoot); + const teamMd = path.join(stateDir, 'team.md'); const teamRoot = path.dirname(squadDirInfo.path); if (!existsSync(teamMd)) { @@ -385,6 +386,7 @@ export async function runLoop(dest: string, options: LoopConfig): Promise const { cmd, args } = buildLoopAgentCommand(prompt, { agentCmd: options.agentCmd, copilotFlags: options.copilotFlags, + teamRoot, }); console.log(`${GREEN}▶${RESET} [${ts}] Round ${round} — running loop prompt`); diff --git a/packages/squad-cli/src/cli/commands/migrate-backend.ts b/packages/squad-cli/src/cli/commands/migrate-backend.ts index 21b049a60..fb4d457ec 100644 --- a/packages/squad-cli/src/cli/commands/migrate-backend.ts +++ b/packages/squad-cli/src/cli/commands/migrate-backend.ts @@ -1,12 +1,18 @@ /** - * Backend migration — upgrades state backend from local to orphan or two-layer. + * Backend migration — upgrades state backend across allowed transitions. * - * Currently supports: + * Supported migrations: * - local → orphan * - local → two-layer + * - worktree → orphan + * - worktree → two-layer + * - orphan ↔ two-layer (both write to the squad-state orphan branch via different layers) * - * Migration from orphan/two-layer back to local is not supported (would require - * materializing all state from the orphan branch back to the working tree). + * In addition to flipping the `stateBackend` key in `.squad/config.json`, this + * function MIGRATES pre-existing working-tree state (`decisions.md`, + * `agents//history.md`) onto the squad-state orphan branch when moving + * from a working-tree backend to an orphan-storage backend, so post-upgrade + * agents can read pre-upgrade content (UPGRADE-NO-MIGRATION fix). */ import { execFileSync } from 'node:child_process'; @@ -17,13 +23,151 @@ import { installGitHooks } from './install-hooks.js'; const GREEN = '\x1b[32m'; const YELLOW = '\x1b[33m'; const BOLD = '\x1b[1m'; +const DIM = '\x1b[2m'; const RESET = '\x1b[0m'; -const VALID_TARGETS = ['orphan', 'two-layer']; +const VALID_TARGETS = ['local', 'worktree', 'orphan', 'two-layer']; +const ORPHAN_BACKENDS = new Set(['orphan', 'two-layer']); +const WORKTREE_BACKENDS = new Set(['local', 'worktree']); + +/** Paths inside .squad/ that should be carried over to the orphan branch on migration. */ +const MIGRATABLE_PATHS = [ + 'decisions.md', +]; + +/** Best-effort: ensure the squad-state orphan branch exists. Returns true on success. */ +export function ensureOrphanBranch(dest: string): boolean { + try { + execFileSync('git', ['rev-parse', '--verify', 'refs/heads/squad-state'], { + cwd: dest, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], + }); + return true; + } catch { + try { + const readmeContent = '# Squad State\n\nThis orphan branch stores mutable squad state.\nIt is managed automatically and should not be edited by hand.\n'; + const blobHash = execFileSync('git', ['hash-object', '-w', '--stdin'], { + cwd: dest, encoding: 'utf-8', input: readmeContent, stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + const treeInput = `100644 blob ${blobHash}\tREADME.md\n`; + const treeHash = execFileSync('git', ['mktree'], { + cwd: dest, encoding: 'utf-8', input: treeInput, stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + const commitHash = execFileSync('git', ['commit-tree', treeHash, '-m', 'init: squad-state orphan branch'], { + cwd: dest, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + execFileSync('git', ['update-ref', 'refs/heads/squad-state', commitHash], { + cwd: dest, stdio: ['pipe', 'pipe', 'pipe'], + }); + return true; + } catch (err) { + console.log(`${YELLOW}⚠ Could not create squad-state branch: ${err instanceof Error ? err.message : err}${RESET}`); + return false; + } + } +} + +/** + * Collect existing working-tree state files that should be migrated to the orphan branch. + * Returns array of {path, content} pairs (paths are relative to .squad/). + */ +function collectWorktreeState(dest: string): Array<{ relPath: string; content: string }> { + const squadDir = path.join(dest, '.squad'); + const collected: Array<{ relPath: string; content: string }> = []; + + for (const p of MIGRATABLE_PATHS) { + const full = path.join(squadDir, p); + if (fs.existsSync(full) && fs.statSync(full).isFile()) { + collected.push({ relPath: p, content: fs.readFileSync(full, 'utf-8') }); + } + } + + // agents//history.md + const agentsDir = path.join(squadDir, 'agents'); + if (fs.existsSync(agentsDir) && fs.statSync(agentsDir).isDirectory()) { + for (const agentName of fs.readdirSync(agentsDir)) { + const histPath = path.join(agentsDir, agentName, 'history.md'); + if (fs.existsSync(histPath) && fs.statSync(histPath).isFile()) { + collected.push({ + relPath: path.posix.join('agents', agentName, 'history.md'), + content: fs.readFileSync(histPath, 'utf-8'), + }); + } + } + } + + return collected; +} + +/** + * Write a set of files onto the squad-state orphan branch via git plumbing. + * Preserves any files already on the branch (merges trees by path). + * Returns the number of files written, or -1 on failure. + */ +function writeFilesToOrphanBranch(dest: string, files: Array<{ relPath: string; content: string }>): number { + if (files.length === 0) return 0; + try { + // Load the current squad-state tree into the index using a temporary index file + const gitDir = execFileSync('git', ['rev-parse', '--git-dir'], { + cwd: dest, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + const indexFile = path.resolve(dest, gitDir, 'squad-migrate-index'); + if (fs.existsSync(indexFile)) fs.unlinkSync(indexFile); + + const env = { ...process.env, GIT_INDEX_FILE: indexFile }; + + // Seed index with existing squad-state tree + execFileSync('git', ['read-tree', 'refs/heads/squad-state'], { + cwd: dest, env, stdio: ['pipe', 'pipe', 'pipe'], + }); + + // Add each migrated file + for (const f of files) { + const blobHash = execFileSync('git', ['hash-object', '-w', '--stdin'], { + cwd: dest, env, encoding: 'utf-8', input: f.content, stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + execFileSync('git', ['update-index', '--add', '--cacheinfo', `100644,${blobHash},${f.relPath}`], { + cwd: dest, env, stdio: ['pipe', 'pipe', 'pipe'], + }); + } + + const treeHash = execFileSync('git', ['write-tree'], { + cwd: dest, env, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + + const parentSha = execFileSync('git', ['rev-parse', 'refs/heads/squad-state'], { + cwd: dest, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + + const commitHash = execFileSync( + 'git', + ['commit-tree', treeHash, '-p', parentSha, '-m', `migrate: import working-tree state on backend upgrade (${files.length} file(s))`], + { cwd: dest, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }, + ).trim(); + + execFileSync('git', ['update-ref', 'refs/heads/squad-state', commitHash, parentSha], { + cwd: dest, stdio: ['pipe', 'pipe', 'pipe'], + }); + + // Cleanup the temporary index + if (fs.existsSync(indexFile)) fs.unlinkSync(indexFile); + + return files.length; + } catch (err) { + console.log(`${YELLOW}⚠ Could not migrate state onto squad-state branch: ${err instanceof Error ? err.message : err}${RESET}`); + return -1; + } +} /** * Migrate the state backend for an existing squad project. - * Only local → orphan/two-layer is supported. + * + * Fixes: + * - UPGRADE-FLAG-IGNORED: writes the new `stateBackend` value into config.json + * via JSON-aware merge (never textual append → avoids Bug E duplicates). + * - UPGRADE-NO-MIGRATION: when moving from a working-tree backend to an + * orphan-storage backend, carries existing decisions.md and agent histories + * onto the squad-state orphan branch so post-upgrade agents see prior state. + * - WI-1: re-installs the full hook set (including new pre-commit/post-commit). */ export async function migrateStateBackend(dest: string, target: string): Promise { if (!VALID_TARGETS.includes(target)) { @@ -42,56 +186,133 @@ export async function migrateStateBackend(dest: string, target: string): Promise const current = (config['stateBackend'] as string) || 'local'; - // Validate migration direction if (current === target) { - console.log(`${YELLOW}⚠ Backend is already '${target}'. Nothing to do.${RESET}`); + console.log(`${YELLOW}⚠ Backend is already '${target}'. Ensuring hooks + config are consistent.${RESET}`); + if (ORPHAN_BACKENDS.has(target)) { + ensureOrphanBranch(dest); + installGitHooks(dest, { force: false }); + } return; } - if (current !== 'local' && current !== null) { - console.log(`${YELLOW}⚠ Migration from '${current}' to '${target}' is not supported.${RESET}`); - console.log(` Only local → orphan or local → two-layer is supported at this time.`); - return; + console.log(`\n${BOLD}Migrating state backend: ${current} → ${target}${RESET}\n`); + + // Step 1: Ensure orphan branch exists when the target needs it + if (ORPHAN_BACKENDS.has(target)) { + if (!ensureOrphanBranch(dest)) return; + console.log(` ${GREEN}✓${RESET} squad-state branch ready`); } - console.log(`\n${BOLD}Migrating state backend: ${current} → ${target}${RESET}\n`); + // Step 2 (UPGRADE-NO-MIGRATION): move working-tree state onto the orphan branch + // when transitioning from a worktree backend to an orphan-storage backend. + if (WORKTREE_BACKENDS.has(current) && ORPHAN_BACKENDS.has(target)) { + const files = collectWorktreeState(dest); + if (files.length > 0) { + const wrote = writeFilesToOrphanBranch(dest, files); + if (wrote > 0) { + console.log(` ${GREEN}✓${RESET} migrated ${wrote} state file(s) onto squad-state branch:`); + for (const f of files) { + console.log(` ${DIM}.squad/${f.relPath}${RESET}`); + } - // Step 1: Create orphan branch if needed - try { - execFileSync('git', ['rev-parse', '--verify', 'refs/heads/squad-state'], { - cwd: dest, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], - }); - console.log(` ${GREEN}✓${RESET} squad-state branch already exists`); - } catch { - try { - const readmeContent = '# Squad State\n\nThis orphan branch stores mutable squad state.\nIt is managed automatically and should not be edited by hand.\n'; - const blobHash = execFileSync('git', ['hash-object', '-w', '--stdin'], { - cwd: dest, encoding: 'utf-8', input: readmeContent, stdio: ['pipe', 'pipe', 'pipe'], - }).trim(); - const treeInput = `100644 blob ${blobHash}\tREADME.md\n`; - const treeHash = execFileSync('git', ['mktree'], { - cwd: dest, encoding: 'utf-8', input: treeInput, stdio: ['pipe', 'pipe', 'pipe'], - }).trim(); - const commitHash = execFileSync('git', ['commit-tree', treeHash, '-m', 'init: squad-state orphan branch'], { - cwd: dest, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], - }).trim(); - execFileSync('git', ['update-ref', 'refs/heads/squad-state', commitHash], { - cwd: dest, stdio: ['pipe', 'pipe', 'pipe'], - }); - console.log(` ${GREEN}✓${RESET} squad-state orphan branch created`); - } catch (err) { - console.log(`${YELLOW}⚠ Could not create squad-state branch: ${err instanceof Error ? err.message : err}${RESET}`); - return; + // F1 fix (Round 5, P1.1): the working-tree copies are now stale — the + // orphan branch is the source of truth for an orphan/two-layer backend. + // Remove them so `git status` is clean and post-upgrade agents can't + // accidentally read stale content. Mirrors liftInitMutableStateOntoOrphan + // behavior. Only files we just successfully migrated are removed; we + // never touch config.json, charter.md, team.md, casting/, templates/, etc. + const squadDir = path.join(dest, '.squad'); + const removed: string[] = []; + const removeFailed: string[] = []; + for (const f of files) { + const full = path.join(squadDir, f.relPath); + try { + if (fs.existsSync(full)) fs.unlinkSync(full); + removed.push(f.relPath); + } catch { + removeFailed.push(f.relPath); + } + } + // Clean up now-empty agent directories (e.g. .squad/agents/data/) so + // they don't leak as zero-content folders post-upgrade. + const agentsDir = path.join(squadDir, 'agents'); + if (fs.existsSync(agentsDir) && fs.statSync(agentsDir).isDirectory()) { + for (const agentName of fs.readdirSync(agentsDir)) { + const agentDir = path.join(agentsDir, agentName); + try { + if ( + fs.statSync(agentDir).isDirectory() && + fs.readdirSync(agentDir).length === 0 + ) { + fs.rmdirSync(agentDir); + } + } catch { /* best-effort */ } + } + } + + if (removed.length > 0) { + console.log(` ${GREEN}✓${RESET} removed ${removed.length} stale working-tree file(s) (now sourced from squad-state):`); + for (const r of removed) { + console.log(` ${DIM}.squad/${r}${RESET}`); + } + } + if (removeFailed.length > 0) { + console.log(` ${YELLOW}⚠${RESET} could not remove: ${removeFailed.join(', ')} (will appear in git status)`); + } + } else if (wrote === 0) { + console.log(` ${DIM}no working-tree state files to migrate${RESET}`); + } + // If wrote === -1 we already printed a warning; continue so config still updates. + } else { + console.log(` ${DIM}no migratable state in working tree${RESET}`); } } - // Step 2: Update config + // Step 3 (UPGRADE-FLAG-IGNORED + Bug E): JSON-merge the new value. + // Reading + re-stringifying guarantees one canonical `stateBackend` key. config['stateBackend'] = target; + fs.mkdirSync(path.dirname(configPath), { recursive: true }); fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n'); console.log(` ${GREEN}✓${RESET} config.json updated: stateBackend = ${target}`); - // Step 3: Install git hooks - installGitHooks(dest, { force: true }); + // Step 4 (WI-1): re-install hooks (force so new pre-commit/post-commit land) + if (ORPHAN_BACKENDS.has(target)) { + installGitHooks(dest, { force: true }); + } console.log(`\n${GREEN}${BOLD}✓ Migration complete.${RESET} Backend is now '${target}'.\n`); } + +/** + * INSIDER3-INIT-LEAK fix: when `squad init --state-backend orphan|two-layer` + * runs, the SDK still hand-writes mutable state files (decisions.md and each + * agent's history.md) into the working tree because it has no knowledge of the + * future backend choice. This helper, invoked by the CLI immediately after the + * orphan branch is created, lifts those mutable files onto the squad-state + * orphan branch and removes them from the working tree so post-init agents + * read state exclusively through the runtime bridge. + * + * Source-of-truth hierarchy preserved: static files (team.md, charters, + * ceremonies.md, casting/*, templates/*) are NEVER touched — only mutable + * state (decisions.md, agents//history.md) migrates. + * + * Returns the relative paths of files that were migrated + removed. + */ +export function liftInitMutableStateOntoOrphan(dest: string): string[] { + const files = collectWorktreeState(dest); + if (files.length === 0) return []; + const wrote = writeFilesToOrphanBranch(dest, files); + if (wrote <= 0) return []; + const removed: string[] = []; + const squadDir = path.join(dest, '.squad'); + for (const f of files) { + const full = path.join(squadDir, f.relPath); + try { + if (fs.existsSync(full)) fs.unlinkSync(full); + removed.push(f.relPath); + } catch { + // Leave file behind rather than aborting; the runtime bridge already has authoritative copy. + } + } + return removed; +} diff --git a/packages/squad-cli/src/cli/commands/notes.ts b/packages/squad-cli/src/cli/commands/notes.ts new file mode 100644 index 000000000..637ae3a4e --- /dev/null +++ b/packages/squad-cli/src/cli/commands/notes.ts @@ -0,0 +1,248 @@ +/** + * squad notes — manage squad git-notes state (Round 5, P0.3 A3). + * + * Subcommand: + * promote [--ref ] [--all] [--dry-run] + * + * Production caller for {@link TwoLayerBackend.promoteNotes}. Walks notes on + * each `refs/notes/squad/*` ref, moves notes flagged `promote_to_permanent` + * into the orphan layer under `promoted//.json`, copies notes + * flagged `archive_on_close` under `archive//.json`, and leaves + * un-flagged notes in place. + * + * Round 4 audit found `promoteNotes` had zero production callers — the SDK + * API was wired but never invoked. This command, plus the Ralph heartbeat + * `notes-promote` capability, are those production callers. + * + * @module cli/commands/notes + */ + +import { execFileSync } from 'node:child_process'; +import * as path from 'node:path'; +import { resolveStateBackend, TwoLayerBackend, type PromoteNotesResult } from '@bradygaster/squad-sdk'; +import { resolveSquadPaths } from '@bradygaster/squad-sdk/resolution'; +import { BOLD, RESET, DIM, GREEN, YELLOW, RED } from '../core/output.js'; +import { fatal } from '../core/errors.js'; + +/** Parsed args for `squad notes promote`. */ +interface PromoteArgs { + ref?: string; + all: boolean; + dryRun: boolean; +} + +function parsePromoteArgs(args: string[]): PromoteArgs { + const out: PromoteArgs = { all: false, dryRun: false }; + for (let i = 0; i < args.length; i++) { + const a = args[i]; + if (a === '--ref') { + out.ref = args[++i]; + } else if (a === '--all') { + out.all = true; + } else if (a === '--dry-run') { + out.dryRun = true; + } else if (a === '--help' || a === '-h') { + printPromoteHelp(); + process.exit(0); + } + } + return out; +} + +function printPromoteHelp(): void { + console.log(`\n${BOLD}squad notes promote${RESET} — promote/archive flagged git-notes to permanent storage\n`); + console.log(`Usage: squad notes promote [options]\n`); + console.log(`Options:`); + console.log(` --ref Restrict to a single notes ref (e.g. squad/picard)`); + console.log(` --all Promote across all refs/notes/squad/* (default)`); + console.log(` --dry-run Report what would be promoted without writing`); + console.log(` --help, -h Show this help\n`); + console.log(`Requires stateBackend: 'two-layer' in .squad/config.json.`); + console.log(`Idempotent — promoted notes are removed from source, archived notes stay in place.\n`); +} + +/** + * Enumerate squad notes refs (`refs/notes/squad/*`) present in the repo. + * Returns short names (e.g. `squad/picard`, not `refs/notes/squad/picard`). + */ +function listSquadNotesRefs(repoRoot: string): string[] { + try { + const out = execFileSync( + 'git', + ['for-each-ref', '--format=%(refname)', 'refs/notes/squad/'], + { cwd: repoRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }, + ); + return out + .split('\n') + .map((l) => l.trim()) + .filter(Boolean) + .map((full) => full.replace(/^refs\/notes\//, '')) + .filter((r) => /^squad\/[A-Za-z0-9_\-./]+$/.test(r)); + } catch { + return []; + } +} + +/** + * Best-effort dry-run preview: list notes on a ref with their flag classification. + * Reuses the same parsing logic as TwoLayerBackend.promoteNotes but without writing. + */ +function dryRunRef(repoRoot: string, ref: string): PromoteNotesResult { + const result: PromoteNotesResult = { promoted: [], archived: [], skipped: 0 }; + let listing: string; + try { + listing = execFileSync( + 'git', + ['notes', `--ref=${ref}`, 'list'], + { cwd: repoRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }, + ); + } catch { + return result; + } + + let reachable: Set; + try { + const raw = execFileSync('git', ['rev-list', 'HEAD'], { + cwd: repoRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], + }); + reachable = new Set(raw.split('\n').map((s) => s.trim()).filter(Boolean)); + } catch { + return result; + } + + for (const line of listing.split('\n')) { + const parts = line.trim().split(/\s+/); + if (parts.length < 2) continue; + const commitSha = parts[1]!; + if (!/^[a-f0-9]{40}$|^[a-f0-9]{64}$/.test(commitSha)) continue; + if (!reachable.has(commitSha)) continue; + + let raw: string; + try { + raw = execFileSync( + 'git', ['notes', `--ref=${ref}`, 'show', commitSha], + { cwd: repoRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }, + ); + } catch { continue; } + + let payload: unknown; + try { payload = JSON.parse(raw); } catch { result.skipped++; continue; } + const flags = payload as { promote_to_permanent?: unknown; archive_on_close?: unknown }; + const refSeg = ref.split('/').filter(Boolean).join('/'); + if (flags?.promote_to_permanent === true) { + result.promoted.push(`promoted/${refSeg}/${commitSha}.json`); + } + if (flags?.archive_on_close === true) { + result.archived.push(`archive/${refSeg}/${commitSha}.json`); + } + if (flags?.promote_to_permanent !== true && flags?.archive_on_close !== true) { + result.skipped++; + } + } + return result; +} + +/** + * Run `squad notes promote`. Returns process exit code. + */ +export async function runNotesPromote(cwd: string, args: string[]): Promise { + const opts = parsePromoteArgs(args); + + const paths = resolveSquadPaths(cwd); + if (!paths) { + fatal('No squad found. Run "squad init" first.'); + } + + const squadDir = paths.projectDir; + const repoRoot = path.resolve(squadDir, '..'); + + const backend = resolveStateBackend(squadDir, repoRoot); + if (!(backend instanceof TwoLayerBackend)) { + console.log(`${YELLOW}⚠ stateBackend is '${backend.name}', not 'two-layer'. Nothing to promote.${RESET}`); + console.log(`${DIM} Run 'squad upgrade --state-backend two-layer' to enable note promotion.${RESET}`); + return 0; + } + + // Decide which refs to process. + let refs: string[]; + if (opts.ref) { + refs = [opts.ref]; + } else { + refs = listSquadNotesRefs(repoRoot); + if (refs.length === 0) { + console.log(`${DIM}No squad notes refs found (refs/notes/squad/*). Nothing to do.${RESET}`); + return 0; + } + } + + const header = opts.dryRun ? `notes promote ${DIM}(dry-run)${RESET}` : 'notes promote'; + console.log(`\n${BOLD}${header}${RESET}\n`); + + const rows: Array<{ ref: string; promoted: number; archived: number; skipped: number; error?: string }> = []; + let anyError = false; + + for (const ref of refs) { + try { + const res = opts.dryRun ? dryRunRef(repoRoot, ref) : backend.promoteNotes(ref); + rows.push({ ref, promoted: res.promoted.length, archived: res.archived.length, skipped: res.skipped }); + } catch (err: unknown) { + anyError = true; + const msg = err instanceof Error ? err.message : String(err); + rows.push({ ref, promoted: 0, archived: 0, skipped: 0, error: msg }); + } + } + + const nameW = Math.max(...rows.map((r) => r.ref.length), 'Ref'.length, 5); + console.log( + ` ${'Ref'.padEnd(nameW)} ${'Promoted'.padStart(8)} ${'Archived'.padStart(8)} ${'Skipped'.padStart(7)}`, + ); + console.log( + ` ${'─'.repeat(nameW)} ${'─'.repeat(8)} ${'─'.repeat(8)} ${'─'.repeat(7)}`, + ); + let totalP = 0, totalA = 0, totalS = 0; + for (const r of rows) { + if (r.error) { + console.log(` ${r.ref.padEnd(nameW)} ${RED}error${RESET}: ${r.error}`); + continue; + } + totalP += r.promoted; + totalA += r.archived; + totalS += r.skipped; + console.log( + ` ${r.ref.padEnd(nameW)} ${String(r.promoted).padStart(8)} ${String(r.archived).padStart(8)} ${String(r.skipped).padStart(7)}`, + ); + } + console.log( + ` ${'─'.repeat(nameW)} ${'─'.repeat(8)} ${'─'.repeat(8)} ${'─'.repeat(7)}`, + ); + console.log( + ` ${'TOTAL'.padEnd(nameW)} ${String(totalP).padStart(8)} ${String(totalA).padStart(8)} ${String(totalS).padStart(7)}\n`, + ); + + if (anyError) { + console.log(`${RED}✗${RESET} Completed with errors (see above).`); + return 1; + } + console.log(`${GREEN}✓${RESET} ${opts.dryRun ? 'Dry-run complete' : 'Promotion complete'}.\n`); + return 0; +} + +/** + * Top-level `squad notes` dispatcher. + */ +export async function runNotes(cwd: string, args: string[]): Promise { + const sub = args[0]; + if (!sub || sub === '--help' || sub === '-h' || sub === 'help') { + console.log(`\n${BOLD}squad notes${RESET} — manage squad git-notes state\n`); + console.log(`Subcommands:`); + console.log(` promote Promote/archive flagged notes to permanent orphan storage`); + console.log(`\nRun 'squad notes --help' for details.\n`); + return; + } + if (sub === 'promote') { + const code = await runNotesPromote(cwd, args.slice(1)); + if (code !== 0) process.exit(code); + return; + } + fatal(`Unknown 'squad notes' subcommand: ${sub}`); +} diff --git a/packages/squad-cli/src/cli/commands/plugin.ts b/packages/squad-cli/src/cli/commands/plugin.ts index e017319f8..eefab8028 100644 --- a/packages/squad-cli/src/cli/commands/plugin.ts +++ b/packages/squad-cli/src/cli/commands/plugin.ts @@ -31,7 +31,7 @@ import { } from '@bradygaster/squad-sdk'; import { success, warn, info, dim, DIM, BOLD, RESET } from '../core/output.js'; import { fatal } from '../core/errors.js'; -import { detectSquadDir } from '../core/detect-squad-dir.js'; +import { effectiveSquadDir } from '../core/effective-squad-dir.js'; import { ghAvailable, ghAuthenticated } from '../core/gh-cli.js'; const execFileAsync = promisify(execFile); @@ -58,9 +58,9 @@ export async function runPlugin(dest: string, args: string[]): Promise { fatal(pluginUsage()); } - const squadDirInfo = detectSquadDir(dest); + const { local: squadDirInfo, stateDir } = effectiveSquadDir(dest); const storage = new FSStorageProvider(); - const pluginsDir = join(squadDirInfo.path, 'plugins'); + const pluginsDir = join(stateDir, 'plugins'); const marketplacesFile = join(pluginsDir, 'marketplaces.json'); async function readMarketplaces(): Promise { diff --git a/packages/squad-cli/src/cli/commands/start.ts b/packages/squad-cli/src/cli/commands/start.ts index b31de6f66..35c574f60 100644 --- a/packages/squad-cli/src/cli/commands/start.ts +++ b/packages/squad-cli/src/cli/commands/start.ts @@ -14,6 +14,7 @@ import path from 'node:path'; import { createReadStream } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { FSStorageProvider, RemoteBridge } from '@bradygaster/squad-sdk'; +import { withAdditionalMcpConfig } from '../core/copilot-invocation.js'; const storage = new FSStorageProvider(); import type { RemoteBridgeConfig } from '@bradygaster/squad-sdk'; @@ -166,6 +167,14 @@ export async function runStart(cwd: string, options: StartOptions): Promise { diff --git a/packages/squad-cli/src/cli/commands/watch/capabilities/execute.ts b/packages/squad-cli/src/cli/commands/watch/capabilities/execute.ts index 0a38de7d9..4aa367523 100644 --- a/packages/squad-cli/src/cli/commands/watch/capabilities/execute.ts +++ b/packages/squad-cli/src/cli/commands/watch/capabilities/execute.ts @@ -9,6 +9,7 @@ import type { WatchCapability, WatchContext, PreflightResult, CapabilityResult } import type { MachineCapabilities } from '@bradygaster/squad-sdk/ralph/capabilities'; import { createVerboseLogger } from '../verbose.js'; import { loadAgentCharter } from '../../../shell/spawn.js'; +import { withAdditionalMcpConfig } from '../../../core/copilot-invocation.js'; /** Normalized work item for execution. */ export interface ExecutableWorkItem { @@ -61,7 +62,7 @@ function buildAgentCommand( if (context.copilotFlags) { args.push(...context.copilotFlags.trim().split(/\s+/)); } - return { cmd: 'copilot', args }; + return { cmd: 'copilot', args: withAdditionalMcpConfig('copilot', args, context.teamRoot) }; } /** Labels that indicate an issue should not be auto-executed. */ diff --git a/packages/squad-cli/src/cli/commands/watch/capabilities/index.ts b/packages/squad-cli/src/cli/commands/watch/capabilities/index.ts index f5e8736de..c5227a5e5 100644 --- a/packages/squad-cli/src/cli/commands/watch/capabilities/index.ts +++ b/packages/squad-cli/src/cli/commands/watch/capabilities/index.ts @@ -14,6 +14,7 @@ import { WaveDispatchCapability } from './wave-dispatch.js'; import { RetroCapability } from './retro.js'; import { DecisionHygieneCapability } from './decision-hygiene.js'; import { CleanupCapability } from './cleanup.js'; +import { NotesPromoteCapability } from './notes-promote.js'; /** Create a registry pre-loaded with all built-in capabilities. */ export function createDefaultRegistry(): CapabilityRegistry { @@ -29,5 +30,6 @@ export function createDefaultRegistry(): CapabilityRegistry { registry.register(new RetroCapability()); registry.register(new DecisionHygieneCapability()); registry.register(new CleanupCapability()); + registry.register(new NotesPromoteCapability()); return registry; } diff --git a/packages/squad-cli/src/cli/commands/watch/capabilities/monitor-email.ts b/packages/squad-cli/src/cli/commands/watch/capabilities/monitor-email.ts index 759cdebbe..24e7eaf57 100644 --- a/packages/squad-cli/src/cli/commands/watch/capabilities/monitor-email.ts +++ b/packages/squad-cli/src/cli/commands/watch/capabilities/monitor-email.ts @@ -4,6 +4,7 @@ import { execFile } from 'node:child_process'; import type { WatchCapability, WatchContext, PreflightResult, CapabilityResult } from '../types.js'; +import { withAdditionalMcpConfig } from '../../../core/copilot-invocation.js'; function buildAgentCommand(prompt: string, context: WatchContext): { cmd: string; args: string[] } { if (context.agentCmd) { @@ -12,7 +13,7 @@ function buildAgentCommand(prompt: string, context: WatchContext): { cmd: string } const args = ['-p', prompt]; if (context.copilotFlags) args.push(...context.copilotFlags.trim().split(/\s+/)); - return { cmd: 'copilot', args }; + return { cmd: 'copilot', args: withAdditionalMcpConfig('copilot', args, context.teamRoot) }; } function spawnWithTimeout(cmd: string, args: string[], cwd: string, timeoutMs: number): Promise { diff --git a/packages/squad-cli/src/cli/commands/watch/capabilities/monitor-teams.ts b/packages/squad-cli/src/cli/commands/watch/capabilities/monitor-teams.ts index 7d644cc12..3989f06b1 100644 --- a/packages/squad-cli/src/cli/commands/watch/capabilities/monitor-teams.ts +++ b/packages/squad-cli/src/cli/commands/watch/capabilities/monitor-teams.ts @@ -4,6 +4,7 @@ import { execFile } from 'node:child_process'; import type { WatchCapability, WatchContext, PreflightResult, CapabilityResult } from '../types.js'; +import { withAdditionalMcpConfig } from '../../../core/copilot-invocation.js'; /** Build agent command from prompt, respecting --agent-cmd. */ function buildAgentCommand(prompt: string, context: WatchContext): { cmd: string; args: string[] } { @@ -13,7 +14,7 @@ function buildAgentCommand(prompt: string, context: WatchContext): { cmd: string } const args = ['-p', prompt]; if (context.copilotFlags) args.push(...context.copilotFlags.trim().split(/\s+/)); - return { cmd: 'copilot', args }; + return { cmd: 'copilot', args: withAdditionalMcpConfig('copilot', args, context.teamRoot) }; } function spawnWithTimeout(cmd: string, args: string[], cwd: string, timeoutMs: number): Promise { diff --git a/packages/squad-cli/src/cli/commands/watch/capabilities/notes-promote.ts b/packages/squad-cli/src/cli/commands/watch/capabilities/notes-promote.ts new file mode 100644 index 000000000..821cd7e15 --- /dev/null +++ b/packages/squad-cli/src/cli/commands/watch/capabilities/notes-promote.ts @@ -0,0 +1,142 @@ +/** + * Notes-promote capability — Ralph heartbeat integration for {@link + * TwoLayerBackend.promoteNotes} (Round 5, P0.3 A3 production caller, path B). + * + * Runs in the `housekeeping` phase. When the active state backend is + * `two-layer`, enumerates `refs/notes/squad/*` and promotes flagged notes + * idempotently every N rounds. + * + * Idempotency: `promote_to_permanent` notes are removed from source after + * write; subsequent runs find nothing to promote and return zero-cost. + * Cheap enough to run every cycle, but throttled to `everyNRounds` to keep + * heartbeat reports readable. + */ +import * as path from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { FSStorageProvider, resolveStateBackend, TwoLayerBackend } from '@bradygaster/squad-sdk'; +import type { WatchCapability, WatchContext, PreflightResult, CapabilityResult } from '../types.js'; + +const storage = new FSStorageProvider(); + +/** Default: run promotion every Nth round to keep heartbeat output tidy. */ +const DEFAULT_EVERY_N_ROUNDS = 5; + +interface NotesPromoteConfig { + /** Run promotion every N rounds (default: 5). */ + everyNRounds?: number; +} + +function parseConfig(raw: Record): NotesPromoteConfig { + return { + everyNRounds: + typeof raw.everyNRounds === 'number' && Number.isFinite(raw.everyNRounds) && raw.everyNRounds > 0 + ? raw.everyNRounds + : DEFAULT_EVERY_N_ROUNDS, + }; +} + +/** List `refs/notes/squad/*` short names present in the repo. */ +function listSquadNotesRefs(repoRoot: string): string[] { + try { + const out = execFileSync( + 'git', + ['for-each-ref', '--format=%(refname)', 'refs/notes/squad/'], + { cwd: repoRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }, + ); + return out + .split('\n') + .map((l) => l.trim()) + .filter(Boolean) + .map((full) => full.replace(/^refs\/notes\//, '')) + .filter((r) => /^squad\/[A-Za-z0-9_\-./]+$/.test(r)); + } catch { + return []; + } +} + +export class NotesPromoteCapability implements WatchCapability { + readonly name = 'notes-promote'; + readonly description = + 'Promote/archive flagged git-notes to permanent storage (two-layer backend only)'; + readonly configShape = 'object' as const; + readonly requires: string[] = ['git']; + readonly phase = 'housekeeping' as const; + + async preflight(context: WatchContext): Promise { + const squadDir = path.join(context.teamRoot, '.squad'); + if (!storage.existsSync(squadDir)) { + return { ok: false, reason: '.squad/ directory not found' }; + } + // Cheap check: only relevant when stateBackend is two-layer. + const repoRoot = context.teamRoot; + try { + const backend = resolveStateBackend(squadDir, repoRoot); + if (!(backend instanceof TwoLayerBackend)) { + return { ok: false, reason: `stateBackend is '${backend.name}', not 'two-layer'` }; + } + } catch (err) { + return { ok: false, reason: `resolveStateBackend failed: ${err instanceof Error ? err.message : err}` }; + } + return { ok: true }; + } + + async execute(context: WatchContext): Promise { + const config = parseConfig(context.config); + const everyN = config.everyNRounds ?? DEFAULT_EVERY_N_ROUNDS; + + if (context.round > 1 && context.round % everyN !== 0) { + return { success: true, summary: `notes-promote: skipped (runs every ${everyN} rounds)` }; + } + + const squadDir = path.join(context.teamRoot, '.squad'); + const repoRoot = context.teamRoot; + + let backend: TwoLayerBackend; + try { + const resolved = resolveStateBackend(squadDir, repoRoot); + if (!(resolved instanceof TwoLayerBackend)) { + return { success: true, summary: `notes-promote: skipped (backend=${resolved.name})` }; + } + backend = resolved; + } catch (err) { + return { + success: false, + summary: `notes-promote: resolveStateBackend failed — ${err instanceof Error ? err.message : err}`, + }; + } + + const refs = listSquadNotesRefs(repoRoot); + if (refs.length === 0) { + return { success: true, summary: 'notes-promote: no squad notes refs' }; + } + + let promoted = 0; + let archived = 0; + let skipped = 0; + const errors: string[] = []; + + for (const ref of refs) { + try { + const res = backend.promoteNotes(ref); + promoted += res.promoted.length; + archived += res.archived.length; + skipped += res.skipped; + } catch (err) { + errors.push(`${ref}: ${err instanceof Error ? err.message : err}`); + } + } + + const parts: string[] = []; + parts.push(`refs=${refs.length}`); + if (promoted) parts.push(`promoted=${promoted}`); + if (archived) parts.push(`archived=${archived}`); + if (skipped) parts.push(`skipped=${skipped}`); + if (errors.length) parts.push(`errors=${errors.length}`); + + return { + success: errors.length === 0, + summary: `notes-promote: ${parts.join(' ')}`, + data: { promoted, archived, skipped, errorCount: errors.length, errors: errors.slice(0, 5) }, + }; + } +} diff --git a/packages/squad-cli/src/cli/commands/watch/capabilities/retro.ts b/packages/squad-cli/src/cli/commands/watch/capabilities/retro.ts index bb79e68b2..160336fb3 100644 --- a/packages/squad-cli/src/cli/commands/watch/capabilities/retro.ts +++ b/packages/squad-cli/src/cli/commands/watch/capabilities/retro.ts @@ -6,6 +6,7 @@ import path from 'node:path'; import { execFile } from 'node:child_process'; import { FSStorageProvider } from '@bradygaster/squad-sdk'; import type { WatchCapability, WatchContext, PreflightResult, CapabilityResult } from '../types.js'; +import { withAdditionalMcpConfig } from '../../../core/copilot-invocation.js'; const storage = new FSStorageProvider(); @@ -16,7 +17,7 @@ function buildAgentCommand(prompt: string, context: WatchContext): { cmd: string } const args = ['-p', prompt]; if (context.copilotFlags) args.push(...context.copilotFlags.trim().split(/\s+/)); - return { cmd: 'copilot', args }; + return { cmd: 'copilot', args: withAdditionalMcpConfig('copilot', args, context.teamRoot) }; } function spawnWithTimeout(cmd: string, args: string[], cwd: string, timeoutMs: number): Promise { diff --git a/packages/squad-cli/src/cli/commands/watch/capabilities/wave-dispatch.ts b/packages/squad-cli/src/cli/commands/watch/capabilities/wave-dispatch.ts index 0169ef998..0befa23f0 100644 --- a/packages/squad-cli/src/cli/commands/watch/capabilities/wave-dispatch.ts +++ b/packages/squad-cli/src/cli/commands/watch/capabilities/wave-dispatch.ts @@ -4,6 +4,7 @@ import { execFile, type ChildProcess } from 'node:child_process'; import type { WatchCapability, WatchContext, PreflightResult, CapabilityResult } from '../types.js'; +import { withAdditionalMcpConfig } from '../../../core/copilot-invocation.js'; interface SubTask { description: string; @@ -41,7 +42,7 @@ function buildAgentCommand(prompt: string, context: WatchContext): { cmd: string } const args = ['-p', prompt]; if (context.copilotFlags) args.push(...context.copilotFlags.trim().split(/\s+/)); - return { cmd: 'copilot', args }; + return { cmd: 'copilot', args: withAdditionalMcpConfig('copilot', args, context.teamRoot) }; } function executeSubTask( diff --git a/packages/squad-cli/src/cli/commands/watch/index.ts b/packages/squad-cli/src/cli/commands/watch/index.ts index 2538ec101..954be8da1 100644 --- a/packages/squad-cli/src/cli/commands/watch/index.ts +++ b/packages/squad-cli/src/cli/commands/watch/index.ts @@ -11,9 +11,10 @@ import fs from 'node:fs'; import { execFile, execFileSync } from 'node:child_process'; import { promisify } from 'node:util'; import { FSStorageProvider } from '@bradygaster/squad-sdk'; -import { detectSquadDir } from '../../core/detect-squad-dir.js'; +import { effectiveSquadDir } from '../../core/effective-squad-dir.js'; import { fatal } from '../../core/errors.js'; import { GREEN, RED, DIM, BOLD, RESET, YELLOW } from '../../core/output.js'; +import { withAdditionalMcpConfig } from '../../core/copilot-invocation.js'; import { parseRoutingRules, parseModuleOwnership, @@ -616,7 +617,7 @@ export function buildAgentCommand( } const args = ['-p', prompt]; if (options.copilotFlags) args.push(...options.copilotFlags.trim().split(/\s+/)); - return { cmd: 'copilot', args }; + return { cmd: 'copilot', args: withAdditionalMcpConfig('copilot', args, teamRoot) }; } export async function selfPull(teamRoot: string): Promise { @@ -679,10 +680,10 @@ export async function runWatch(dest: string, options: WatchOptions | WatchConfig fatal('--interval must be a positive number of minutes'); } - // Detect squad directory - const squadDirInfo = detectSquadDir(dest); - const teamMd = path.join(squadDirInfo.path, 'team.md'); - const routingMdPath = path.join(squadDirInfo.path, 'routing.md'); + // Detect squad directory — follows external state if configured + const { local: squadDirInfo, stateDir } = effectiveSquadDir(dest); + const teamMd = path.join(stateDir, 'team.md'); + const routingMdPath = path.join(stateDir, 'routing.md'); const teamRoot = path.dirname(squadDirInfo.path); if (!storage.existsSync(teamMd)) { diff --git a/packages/squad-cli/src/cli/core/copilot-invocation.ts b/packages/squad-cli/src/cli/core/copilot-invocation.ts new file mode 100644 index 000000000..2a7cf0f8e --- /dev/null +++ b/packages/squad-cli/src/cli/core/copilot-invocation.ts @@ -0,0 +1,88 @@ +/** + * Helpers for spawning the Copilot CLI from squad-managed code paths. + * + * Background — iter-9 finding: Copilot CLI 1.0.59 does NOT auto-load the + * workspace `.mcp.json` in non-interactive (`-p`) mode due to a folder-trust + * security gate (`FH.isFolderTrusted()`). The gate cannot be satisfied without + * a UI prompt, so the `squad_state` MCP entry written by `squad init` / + * `squad upgrade` to the repo-root `.mcp.json` is silently ignored every time + * squad spawns `copilot -p` — leaving `squad_state_*` tools unwired. + * + * Mitigation: every squad-internal `copilot` spawn prepends two flags: + * 1. `--yolo` — suppresses the per-tool-call consent prompt that would + * otherwise block non-interactive (`-p`) automation. + * 2. `--additional-mcp-config @` — explicitly loads the project's + * `.mcp.json` so `squad_state_*` tools register for that session. + * + * We only inject when: + * - the command being spawned is the bare `copilot` binary (i.e. the user + * did not override via `--agent-cmd`) + * - a `.mcp.json` file actually exists at `teamRoot` + * + * Empirical test matrix (Copilot CLI 1.0.59): + * copilot -p "..." → ❌ workspace MCP NOT loaded + * copilot --yolo -p "..." → ❌ workspace MCP NOT loaded + * copilot --yolo --autopilot -p "..." → ❌ workspace MCP NOT loaded + * copilot --additional-mcp-config @.mcp.json --yolo -p → ✅ PROVEN WORKAROUND + * interactive copilot → "Trust folder?" → Yes → ✅ loads (not automatable) + * + * See `.squad/files/validation/COMBINED-FIX-BRANCH-MANIFEST.md` (iter-9) for + * the full empirical proof that this exact flag combination is required. + */ + +import path from 'node:path'; +import { existsSync } from 'node:fs'; + +/** + * Build the extra CLI args needed to make the Copilot CLI load this project's + * `.mcp.json` for a non-interactive (`-p`) spawned session. + * + * Returns `['--yolo', '--additional-mcp-config', '@']` when + * injection is applicable, or an empty array when: + * - a custom agent command was specified (not the bare `copilot` binary), or + * - `teamRoot` is falsy, or + * - `.mcp.json` does not exist under `teamRoot` (squad init not run, repo + * downgraded, etc.) — a console warning is emitted in that case. + * + * The `@` form is used (rather than inline JSON) to avoid argv quoting + * issues with multi-line JSON on Windows. + */ +export function buildAdditionalMcpConfigArgs(cmd: string, teamRoot: string | undefined): string[] { + if (cmd !== 'copilot') return []; + if (!teamRoot) return []; + const configPath = path.join(teamRoot, '.mcp.json'); + try { + if (!existsSync(configPath)) { + console.warn( + `[squad] ⚠ .mcp.json not found at ${configPath}. ` + + `Run \`squad init\` or \`squad upgrade\` to create it. ` + + `squad_state_* tools will NOT be available in this session.`, + ); + return []; + } + } catch { + return []; + } + return ['--yolo', '--additional-mcp-config', `@${configPath}`]; +} + +/** + * Prepend the MCP-config + yolo args to `args`. Returns the full argv list to + * pass to spawn/execFile for the given cmd. The injection slots these flags + * BEFORE other args so positional `-p ` still works correctly. + * + * If `--yolo` is already present in `args` (e.g. user supplied it via + * `copilotFlags`), the duplicate is stripped from `args` before prepending to + * avoid passing `--yolo` twice. + */ +export function withAdditionalMcpConfig( + cmd: string, + args: string[], + teamRoot: string | undefined, +): string[] { + const extra = buildAdditionalMcpConfigArgs(cmd, teamRoot); + if (extra.length === 0) return args; + // Strip any user-supplied --yolo to avoid passing it twice. + const cleanArgs = args.filter(a => a !== '--yolo'); + return [...extra, ...cleanArgs]; +} diff --git a/packages/squad-cli/src/cli/core/effective-squad-dir.ts b/packages/squad-cli/src/cli/core/effective-squad-dir.ts new file mode 100644 index 000000000..f09d8bae8 --- /dev/null +++ b/packages/squad-cli/src/cli/core/effective-squad-dir.ts @@ -0,0 +1,46 @@ +/** + * Effective squad directory resolution — external state aware. + * + * Wraps detectSquadDir() to follow the config.json stateLocation marker + * when state has been externalized via `squad externalize`. + * + * @module cli/core/effective-squad-dir + */ + +import { detectSquadDir, type SquadDirInfo } from './detect-squad-dir.js'; +import { loadDirConfig, resolveExternalStateDir } from '@bradygaster/squad-sdk'; + +/** + * Resolve the effective state directory from a local .squad/ path. + * + * If `.squad/config.json` has `stateLocation: 'external'` and a valid + * `projectKey`, returns the external state directory. Otherwise returns + * the original `squadDirPath` unchanged. + */ +export function resolveStateDir(squadDirPath: string): string { + const config = loadDirConfig(squadDirPath); + if (config?.stateLocation === 'external' && config.projectKey) { + return resolveExternalStateDir(config.projectKey, false); + } + return squadDirPath; +} + +export interface EffectiveSquadDirs { + /** The local .squad/ directory info (for config.json and non-state files) */ + local: SquadDirInfo; + /** The effective state directory (external dir when externalized, otherwise local .squad/) */ + stateDir: string; +} + +/** + * Detect the squad directory and resolve the effective state dir. + * + * Combines detectSquadDir() (zero-dependency bootstrap) with external + * state resolution from config.json. Use `stateDir` for reading state + * files (team.md, routing.md, agents/, plugins/, etc.) and `local.path` + * for non-state files that remain in the working tree. + */ +export function effectiveSquadDir(dest: string): EffectiveSquadDirs { + const local = detectSquadDir(dest); + return { local, stateDir: resolveStateDir(local.path) }; +} diff --git a/packages/squad-cli/src/cli/core/init.ts b/packages/squad-cli/src/cli/core/init.ts index f11a8e692..3280e3a1b 100644 --- a/packages/squad-cli/src/cli/core/init.ts +++ b/packages/squad-cli/src/cli/core/init.ts @@ -13,6 +13,10 @@ import { detectProjectType } from './project-type.js'; import { getPackageVersion, stampVersion } from './version.js'; import { initSquad as sdkInitSquad, cleanupOrphanInitPrompt, ensurePersonalSquadDir, resolvePersonalSquadDir, clearResolveSquadCache, type InitOptions } from '@bradygaster/squad-sdk'; import { installGitHooks } from '../commands/install-hooks.js'; +import { liftInitMutableStateOntoOrphan } from '../commands/migrate-backend.js'; +import { resolveSquadStateMcpSpec } from './mcp-spec.js'; +import { describeMcpSpec } from './upgrade.js'; +import { ensureSquadStateMcpInRoot, tombstoneStaleSquadStateInProjectMcp } from './mcp-root.js'; const storage = new FSStorageProvider(); @@ -332,12 +336,66 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi // Install git hooks for automatic state sync on push/pull installGitHooks(dest, { force: false }); + + // INSIDER3-INIT-LEAK fix: the SDK already wrote decisions.md and + // agents//history.md into the working tree (it had no knowledge of + // the backend choice at the time). Lift those mutable files onto the + // squad-state orphan branch and remove the working-tree copies so the + // backend is the single source of truth post-init. + try { + const lifted = liftInitMutableStateOntoOrphan(dest); + if (lifted.length > 0) { + success(`migrated ${lifted.length} mutable state file(s) onto squad-state branch (removed from working tree)`); + } + } catch (err) { + console.warn(`${YELLOW}⚠ Could not lift mutable state onto squad-state branch: ${err instanceof Error ? err.message : err}${RESET}`); + } + + // GAP-2 fix: SDK init skips .copilot/mcp-config.json when it already + // exists (e.g. partially-squadified repo or pre-existing Copilot setup), + // leaving the bridge unwired. Force-insert/pin the squad_state entry so + // the MCP server is reachable regardless of pre-existing config. + // iter-8: write squad_state to repo-root `.mcp.json` (auto-loaded by + // Copilot CLI 5.3+ walking up from cwd to git root) and tombstone any + // stale project-level entry left by the SDK init writer in + // `.copilot/mcp-config.json`. No HOME modifications. + try { + const mcpSpec = await resolveSquadStateMcpSpec(getPackageVersion()); + const rootResult = ensureSquadStateMcpInRoot(dest, getPackageVersion(), mcpSpec); + if (rootResult.written) { + success(`installed squad_state MCP server to .mcp.json (${describeMcpSpec(mcpSpec)}) — Copilot CLI will auto-load on next invocation`); + console.log(`${DIM} to remove later: edit ${rootResult.path} and delete squad_state${RESET}`); + } + const tomb = tombstoneStaleSquadStateInProjectMcp(dest); + if (tomb.removed) { + success(`removed stale squad_state from ${tomb.path} (now lives in .mcp.json)`); + } + } catch (err) { + console.warn(`${YELLOW}⚠ Could not install squad_state MCP entry in .mcp.json: ${err instanceof Error ? err.message : err}${RESET}`); + } } } else { console.warn(`${YELLOW}⚠ Unknown state backend "${options.stateBackend}". Using default (local).${RESET}`); } } + // iter-8: unconditionally mirror repo-root `.mcp.json` write + tombstone + // for vanilla `squad init` (no --state-backend flag) so the squad_state + // MCP entry is reachable regardless of init path. No HOME modifications. + try { + const mcpSpec = await resolveSquadStateMcpSpec(version); + const rootResult = ensureSquadStateMcpInRoot(dest, version, mcpSpec); + if (rootResult.written) { + success(`installed squad_state MCP server to .mcp.json (${describeMcpSpec(mcpSpec)}) — Copilot CLI will auto-load on next invocation`); + } + const tomb = tombstoneStaleSquadStateInProjectMcp(dest); + if (tomb.removed) { + success(`removed stale squad_state from ${tomb.path} (now lives in .mcp.json)`); + } + } catch { + // best-effort: .mcp.json write failure does not block init + } + // Report .init-prompt storage if (options.prompt) { success(`.init-prompt stored — team will be cast when you run ${CYAN}${BOLD}copilot --agent squad${RESET}`); @@ -370,6 +428,9 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi console.log(); console.log(`${GREEN}${BOLD}Squad initialized.${RESET} Run ${CYAN}${BOLD}copilot --agent squad${RESET} and tell it what you're building.`); console.log(); + console.log(`${DIM}Tip: for non-interactive scripts that need squad_state tools, add to package.json:${RESET}`); + console.log(`${DIM} "squad:copilot": "copilot --additional-mcp-config @.mcp.json"${RESET}`); + console.log(); // ── Personal squad bridge ─────────────────────────────────────────── if (options.isGlobal) { diff --git a/packages/squad-cli/src/cli/core/mcp-root.ts b/packages/squad-cli/src/cli/core/mcp-root.ts new file mode 100644 index 000000000..ed8da25f5 --- /dev/null +++ b/packages/squad-cli/src/cli/core/mcp-root.ts @@ -0,0 +1,164 @@ +/** + * iter-8: repo-root `.mcp.json` writer for the squad_state MCP entry. + * + * Background: iter-7 wrote `squad_state_` into the user's HOME + * `~/.copilot/mcp-config.json`. That polluted HOME with one entry per + * Squad project and required a stale-entry GC that we never built. It + * also touched a file outside the project, which is surprising for + * `squad init` / `squad upgrade`. + * + * iter-8 flips it back inside the project: we write `squad_state` to a + * repo-root `.mcp.json` under the plain (un-namespaced) `squad_state` + * key. Copilot CLI 5.3+ auto-loads `.mcp.json` walking up from cwd to + * the git root, so the entry is picked up by bare + * `copilot --yolo --autopilot --agent squad ...` invocations with no + * wrapper script and no HOME modifications. + * + * `tombstoneStaleSquadStateInProjectMcp` keeps removing any pre-existing + * `squad_state` entry from `.copilot/mcp-config.json` (left over by the + * SDK init writer in older Squad versions), so we have exactly one + * authoritative `squad_state` definition per project. + * + * Safety: we refuse to overwrite a malformed `.mcp.json` rather than + * silently clobber a user-edited file; other `mcpServers.*` entries are + * preserved byte-for-byte through the JSON round-trip. + * + * @module cli/core/mcp-root + */ + +import path from 'node:path'; +import { FSStorageProvider } from '@bradygaster/squad-sdk'; +import type { SquadStateMcpSpec } from './mcp-spec.js'; + +const storage = new FSStorageProvider(); + +/** Resolve the repo-root `.mcp.json` path for a Squad project dest. */ +export function getProjectMcpJsonPath(dest: string): string { + return path.join(dest, '.mcp.json'); +} + +interface McpServerEntry { + command?: string; + args?: string[]; + env?: Record; + tools?: string[]; +} + +interface McpConfigShape { + mcpServers?: Record; + [k: string]: unknown; +} + +export interface EnsureRootResult { + written: boolean; + key: string; + path: string; +} + +/** + * Insert/update the `squad_state` entry in the project's repo-root + * `.mcp.json`. Preserves all other entries unchanged. + * + * @param dest Squad project root (absolute or relative). + * @param _cliVersion Reserved for forensic metadata (unused — Copilot + * CLI does not currently surface extra fields). + * @param spec Pinned/insider command + args from + * `resolveSquadStateMcpSpec`. + */ +export function ensureSquadStateMcpInRoot( + dest: string, + _cliVersion: string, + spec: SquadStateMcpSpec, +): EnsureRootResult { + const key = 'squad_state'; + const cfgPath = getProjectMcpJsonPath(dest); + + let parsed: McpConfigShape; + if (storage.existsSync(cfgPath)) { + const raw = storage.readSync(cfgPath) ?? '{}'; + try { + const obj = JSON.parse(raw) as unknown; + if (!obj || typeof obj !== 'object' || Array.isArray(obj)) { + throw new Error(`${cfgPath}: root must be a JSON object`); + } + parsed = obj as McpConfigShape; + } catch (err) { + throw new Error( + `Refusing to overwrite malformed ${cfgPath}: ${err instanceof Error ? err.message : String(err)}`, + ); + } + } else { + parsed = {}; + } + + if (!parsed.mcpServers || typeof parsed.mcpServers !== 'object') { + parsed.mcpServers = {}; + } + + const existing = parsed.mcpServers[key]; + const desired: McpServerEntry = { + command: spec.command, + args: [...spec.args], + env: {}, + tools: ['*'], + }; + + if ( + existing && + existing.command === desired.command && + Array.isArray(existing.args) && + existing.args.length === desired.args!.length && + existing.args.every((a, i) => a === desired.args![i]) && + existing.env && + typeof existing.env === 'object' && + Object.keys(existing.env).length === 0 && + Array.isArray(existing.tools) && + existing.tools.length === 1 && + existing.tools[0] === '*' + ) { + return { written: false, key, path: cfgPath }; + } + + parsed.mcpServers[key] = desired; + + storage.writeSync(cfgPath, JSON.stringify(parsed, null, 2) + '\n'); + return { written: true, key, path: cfgPath }; +} + +export interface TombstoneResult { + removed: boolean; + path: string; +} + +/** + * Remove a stale top-level `squad_state` entry from the project + * `.copilot/mcp-config.json` left there by older Squad versions or the + * SDK init writer. Preserves all sibling entries. + * + * Best-effort: silently no-ops on a missing or unparseable file rather + * than risking a partial overwrite of user-managed MCP config. + */ +export function tombstoneStaleSquadStateInProjectMcp(dest: string): TombstoneResult { + const cfgPath = path.join(dest, '.copilot', 'mcp-config.json'); + if (!storage.existsSync(cfgPath)) return { removed: false, path: cfgPath }; + + let parsed: unknown; + try { + parsed = JSON.parse(storage.readSync(cfgPath) ?? '{}'); + } catch { + return { removed: false, path: cfgPath }; + } + if (!parsed || typeof parsed !== 'object') return { removed: false, path: cfgPath }; + + const config = parsed as McpConfigShape; + if (!config.mcpServers || typeof config.mcpServers !== 'object') { + return { removed: false, path: cfgPath }; + } + if (!('squad_state' in config.mcpServers)) { + return { removed: false, path: cfgPath }; + } + + delete config.mcpServers.squad_state; + storage.writeSync(cfgPath, JSON.stringify(config, null, 2) + '\n'); + return { removed: true, path: cfgPath }; +} diff --git a/packages/squad-cli/src/cli/core/mcp-spec.ts b/packages/squad-cli/src/cli/core/mcp-spec.ts new file mode 100644 index 000000000..fac175547 --- /dev/null +++ b/packages/squad-cli/src/cli/core/mcp-spec.ts @@ -0,0 +1,85 @@ +/** + * Shared helper for resolving the `squad_state` MCP launch spec. + * + * Used by BOTH `squad init` and `squad upgrade` so the runtime-MCP fallback + * behavior stays symmetric. + * + * Resolution order (iter-7, simplified to 2 tiers): + * 1. If `cliVersion` IS published on npm → `npx -y @ state-mcp` + * (clean cross-machine UX, the steady-state happy path). + * 2. Else → `npx -y @insider state-mcp`. We do NOT probe the registry; + * the `@insider` dist-tag is kept fresh by the publish flow and tier-2 + * is the de-facto fallback whenever a pinned preview version isn't yet + * published. If it really isn't reachable at runtime, `npx` will fail + * loudly — same observable behavior as pre-iter-5. + * + * Iter-6 had two additional tiers (a local-install path resolver and a hard + * error) that the smoke data showed never fired in practice: `@insider` is + * always current, so tier-2 always wins before tier-3 is reached. Deleted + * in iter-7 per the "verify you didn't add code that's no longer needed" + * mandate. + */ + +export interface SquadStateMcpSpec { + /** Executable to spawn (always `npx` after iter-7). */ + command: string; + /** Argv for the executable. */ + args: string[]; + /** How the spec was resolved — useful for logging + tests. */ + source: 'pinned' | 'insider'; +} + +const PACKAGE_NAME = '@bradygaster/squad-cli'; + +export interface ResolveSquadStateMcpSpecOptions { + /** + * Override the published-version check. Tests inject this to avoid real + * network traffic. + */ + publishedCheck?: (version: string) => Promise; +} + +/** Reset internal caches (test-only helper; retained for compat). */ +export function _resetMcpSpecCache(): void { + // no caches in the 2-tier resolver — kept as a no-op for backward compat + // with any test that still calls it. +} + +/** + * Resolve the squad_state MCP launch spec given the running CLI version. + * + * Always returns a spec. If the pinned version is unpublished we fall back + * to `@insider`; if even that turns out to be unreachable at runtime, `npx` + * will fail visibly when Copilot launches the MCP server — same behavior + * as pre-iter-5. + */ +export async function resolveSquadStateMcpSpec( + cliVersion: string, + options: ResolveSquadStateMcpSpecOptions = {}, +): Promise { + // 1. Try the pinned version on the public registry. Skip for placeholder + // versions ('', '0.0.0') — the registry will obviously not have them. + if (cliVersion && cliVersion !== '0.0.0') { + const probe = options.publishedCheck ?? defaultPublishedCheck; + const published = await probe(cliVersion); + if (published) { + return { + command: 'npx', + args: ['-y', `${PACKAGE_NAME}@${cliVersion}`, 'state-mcp'], + source: 'pinned', + }; + } + } + + // 2. Fall back to the @insider dist-tag — always returned, never probed. + return { + command: 'npx', + args: ['-y', `${PACKAGE_NAME}@insider`, 'state-mcp'], + source: 'insider', + }; +} + +async function defaultPublishedCheck(version: string): Promise { + const { isSquadCliVersionPublished } = await import('./npm-registry.js'); + return await isSquadCliVersionPublished(version); +} diff --git a/packages/squad-cli/src/cli/core/npm-registry.ts b/packages/squad-cli/src/cli/core/npm-registry.ts new file mode 100644 index 000000000..3b77a001a --- /dev/null +++ b/packages/squad-cli/src/cli/core/npm-registry.ts @@ -0,0 +1,70 @@ +/** + * Lightweight helper to check whether a specific package version exists in the + * public npm registry. Used by `ensureSquadStateMcpPinned` to avoid pinning a + * `squad_state` MCP launch spec to a version that `npx` cannot resolve + * (which would cause an ETARGET at session start and leave the bridge unwired). + * + * The result is cached per-process to avoid repeated lookups across the + * multi-pass upgrade flow. Network failures and non-200 responses are + * conservatively treated as "version not published" so that we fall back to + * the locally-installed binary rather than pinning a bad spec. + * + * See `.squad/files/validation/MCP-LOADER-ROOT-CAUSE.md` (data-15 Option A). + */ + +const cache = new Map(); + +/** Reset the cache (test-only helper). */ +export function _resetNpmRegistryCache(): void { + cache.clear(); +} + +/** + * Returns true if `@bradygaster/squad-cli@` is reachable on the npm + * registry, false on any network failure / 404 / non-publishable response. + * + * Uses Node's built-in `https` so we don't pull in extra deps. Total budget + * is bounded by `timeoutMs` (default 2s) so a slow / offline registry can + * never block CLI startup. + */ +export async function isSquadCliVersionPublished( + version: string, + timeoutMs = 2000, +): Promise { + if (!version || version === '0.0.0') return false; + const cacheKey = version; + const cached = cache.get(cacheKey); + if (cached !== undefined) return cached; + + const url = `https://registry.npmjs.org/@bradygaster%2Fsquad-cli/${encodeURIComponent(version)}`; + const ok = await new Promise((resolve) => { + let settled = false; + const finish = (v: boolean) => { + if (settled) return; + settled = true; + resolve(v); + }; + const timer = setTimeout(() => finish(false), timeoutMs); + + void import('node:https') + .then(({ request }) => { + const req = request(url, { method: 'GET' }, (res) => { + // Drain to free the socket. + res.resume(); + finish(res.statusCode === 200); + }); + req.on('error', () => finish(false)); + req.on('timeout', () => { + req.destroy(); + finish(false); + }); + req.setTimeout(timeoutMs); + req.end(); + }) + .catch(() => finish(false)) + .finally(() => clearTimeout(timer)); + }); + + cache.set(cacheKey, ok); + return ok; +} diff --git a/packages/squad-cli/src/cli/core/templates.ts b/packages/squad-cli/src/cli/core/templates.ts index eefc46196..7917ca34c 100644 --- a/packages/squad-cli/src/cli/core/templates.ts +++ b/packages/squad-cli/src/cli/core/templates.ts @@ -38,6 +38,9 @@ export const TEMPLATE_MANIFEST: TemplateFile[] = [ }, // Casting system (squad-owned, overwrite on upgrade) + // NOTE: These JSON files are read at runtime by the SDK and many agent + // skills via their flat `.squad/casting-*.json` paths — do NOT route into + // a subdirectory without coordinated updates across the SDK + skill docs. { source: 'casting-history.json', destination: 'casting-history.json', @@ -57,100 +60,102 @@ export const TEMPLATE_MANIFEST: TemplateFile[] = [ description: 'Universe-based character registry', }, - // Template files (squad-owned, overwrite on upgrade) + // Template files (squad-owned, overwrite on upgrade) — routed to + // .squad/templates/ so upgrade doesn't dump ~20 generic *.md docs + // into the .squad/ root. { source: 'charter.md', - destination: 'charter.md', + destination: 'templates/charter.md', overwriteOnUpgrade: true, description: 'Agent charter template', }, { source: 'constraint-tracking.md', - destination: 'constraint-tracking.md', + destination: 'templates/constraint-tracking.md', overwriteOnUpgrade: true, description: 'Constraint tracking template', }, { source: 'copilot-instructions.md', - destination: 'copilot-instructions.md', + destination: 'templates/copilot-instructions.md', overwriteOnUpgrade: true, description: 'Copilot instructions template', }, { source: 'history.md', - destination: 'history.md', + destination: 'templates/history.md', overwriteOnUpgrade: true, description: 'Agent history template', }, { source: 'mcp-config.md', - destination: 'mcp-config.md', + destination: 'templates/mcp-config.md', overwriteOnUpgrade: true, description: 'MCP configuration template', }, { source: 'multi-agent-format.md', - destination: 'multi-agent-format.md', + destination: 'templates/multi-agent-format.md', overwriteOnUpgrade: true, description: 'Multi-agent format specification', }, { source: 'orchestration-log.md', - destination: 'orchestration-log.md', + destination: 'templates/orchestration-log.md', overwriteOnUpgrade: true, description: 'Orchestration log template', }, { source: 'plugin-marketplace.md', - destination: 'plugin-marketplace.md', + destination: 'templates/plugin-marketplace.md', overwriteOnUpgrade: true, description: 'Plugin marketplace template', }, { source: 'raw-agent-output.md', - destination: 'raw-agent-output.md', + destination: 'templates/raw-agent-output.md', overwriteOnUpgrade: true, description: 'Raw agent output template', }, { source: 'roster.md', - destination: 'roster.md', + destination: 'templates/roster.md', overwriteOnUpgrade: true, description: 'Team roster template', }, { source: 'run-output.md', - destination: 'run-output.md', + destination: 'templates/run-output.md', overwriteOnUpgrade: true, description: 'Run output template', }, { source: 'scribe-charter.md', - destination: 'scribe-charter.md', + destination: 'templates/scribe-charter.md', overwriteOnUpgrade: true, description: 'Scribe charter template', }, { source: 'Rai-charter.md', - destination: 'Rai-charter.md', + destination: 'templates/Rai-charter.md', overwriteOnUpgrade: true, description: 'Rai RAI reviewer charter template', }, { source: 'rai-policy.md', - destination: 'rai-policy.md', + destination: 'templates/rai-policy.md', overwriteOnUpgrade: true, description: 'Default RAI policy template', }, { source: 'fact-checker-charter.md', - destination: 'fact-checker-charter.md', + destination: 'templates/fact-checker-charter.md', overwriteOnUpgrade: true, description: 'Fact checker charter template', }, { source: 'skill.md', - destination: 'skill.md', + destination: 'templates/skill.md', overwriteOnUpgrade: true, description: 'Skill definition template', }, @@ -186,7 +191,7 @@ export const TEMPLATE_MANIFEST: TemplateFile[] = [ // Issue lifecycle (squad-owned) { source: 'issue-lifecycle.md', - destination: 'issue-lifecycle.md', + destination: 'templates/issue-lifecycle.md', overwriteOnUpgrade: true, description: 'Issue lifecycle process template', }, diff --git a/packages/squad-cli/src/cli/core/upgrade.ts b/packages/squad-cli/src/cli/core/upgrade.ts index 5326f1c92..c8a74b608 100644 --- a/packages/squad-cli/src/cli/core/upgrade.ts +++ b/packages/squad-cli/src/cli/core/upgrade.ts @@ -14,6 +14,9 @@ import { TEMPLATE_MANIFEST, getTemplatesDir } from './templates.js'; import { runMigrations } from './migrations.js'; import { scrubEmails } from './email-scrub.js'; import { getPackageVersion, stampVersion, readInstalledVersion } from './version.js'; +import { resolveSquadStateMcpSpec, type SquadStateMcpSpec } from './mcp-spec.js'; +export { resolveSquadStateMcpSpec } from './mcp-spec.js'; +import { ensureSquadStateMcpInRoot, tombstoneStaleSquadStateInProjectMcp } from './mcp-root.js'; const storage = new FSStorageProvider(); @@ -26,12 +29,19 @@ interface McpServerSpec { env?: Record; } -function buildMcpServerSpecs(isGitHub: boolean): McpServerSpec[] { +function buildMcpServerSpecs(isGitHub: boolean, cliVersion?: string): McpServerSpec[] { + // Pin the squad-cli package to the currently-installed CLI version so that + // `npx -y @bradygaster/squad-cli state-mcp` does NOT silently resolve to the + // npm `latest` dist-tag (which may predate the `state-mcp` command and thus + // expose zero tools to Copilot — see MCP-BRIDGE-BROKEN root cause). + const pkgSpec = cliVersion && cliVersion !== '0.0.0' + ? `@bradygaster/squad-cli@${cliVersion}` + : '@bradygaster/squad-cli'; const servers: McpServerSpec[] = [ { name: 'squad_state', command: 'npx', - args: ['-y', '@bradygaster/squad-cli', 'state-mcp'], + args: ['-y', pkgSpec, 'state-mcp'], }, ]; @@ -66,9 +76,9 @@ function yamlEnvValue(value: string): string { return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; } -function buildMcpFrontmatterBlock(isGitHub: boolean): string { +function buildMcpFrontmatterBlock(isGitHub: boolean, cliVersion?: string): string { const lines = ['mcp-servers:']; - for (const server of buildMcpServerSpecs(isGitHub)) { + for (const server of buildMcpServerSpecs(isGitHub, cliVersion)) { lines.push(` ${server.name}:`); lines.push(' type: local'); lines.push(` command: ${server.command}`); @@ -86,7 +96,7 @@ function buildMcpFrontmatterBlock(isGitHub: boolean): string { return lines.join('\n'); } -function injectMcpFrontmatter(content: string, isGitHub: boolean): string { +function injectMcpFrontmatter(content: string, isGitHub: boolean, cliVersion?: string): string { const closingStart = content.indexOf('\n---', 4); if (!content.startsWith('---') || closingStart === -1) { return content; @@ -94,7 +104,7 @@ function injectMcpFrontmatter(content: string, isGitHub: boolean): string { return content.slice(0, closingStart) + '\n' - + buildMcpFrontmatterBlock(isGitHub) + + buildMcpFrontmatterBlock(isGitHub, cliVersion) + content.slice(closingStart); } @@ -205,7 +215,7 @@ function detectIsGitHubForMcp(dest: string, config: Record): bo function writeAgentTemplate(agentSrc: string, agentDest: string, cliVersion: string, mcpConfigMode: McpConfigMode, isGitHub: boolean): void { let agentContent = storage.readSync(agentSrc) ?? ''; if (mcpConfigMode === 'agent-frontmatter') { - agentContent = injectMcpFrontmatter(agentContent, isGitHub); + agentContent = injectMcpFrontmatter(agentContent, isGitHub, cliVersion); } storage.writeSync(agentDest, agentContent); stampVersion(agentDest, cliVersion); @@ -638,7 +648,7 @@ function refreshSquadTemplatesDir(dest: string, templatesDir: string): void { /** * Run all ensure* checks and skill/template sync — shared by both code paths */ -function runEnsureChecks(dest: string, templatesDir: string, filesUpdated: string[]): void { +async function runEnsureChecks(dest: string, templatesDir: string, filesUpdated: string[]): Promise { const attrAdded = ensureGitattributes(dest); if (attrAdded.length > 0) { success(`ensured .gitattributes (${attrAdded.length} rules added)`); @@ -678,6 +688,33 @@ function runEnsureChecks(dest: string, templatesDir: string, filesUpdated: strin refreshSquadTemplatesDir(dest, templatesDir); success('refreshed .squad/templates/'); filesUpdated.push('.squad/templates/'); + + // iter-8: write squad_state MCP entry to repo-root `.mcp.json` + // (auto-loaded by Copilot CLI 5.3+ walking up from cwd to git root) + // and tombstone any stale project-level entry left by older Squad + // versions in `.copilot/mcp-config.json`. No HOME modifications. + const pinnedSpec = await resolveSquadStateMcpSpec(getPackageVersion()); + try { + const rootResult = ensureSquadStateMcpInRoot(dest, getPackageVersion(), pinnedSpec); + if (rootResult.written) { + success(`installed squad_state MCP server to .mcp.json (${describeMcpSpec(pinnedSpec)}) — Copilot CLI will auto-load on next invocation`); + filesUpdated.push('.mcp.json'); + } + } catch (err) { + warn(`Could not write .mcp.json: ${err instanceof Error ? err.message : err}`); + } + const tomb = tombstoneStaleSquadStateInProjectMcp(dest); + if (tomb.removed) { + success(`removed stale squad_state from ${tomb.path} (now lives in .mcp.json)`); + filesUpdated.push('.copilot/mcp-config.json (tombstoned)'); + } +} + +/** Human-readable single-line description of an McpSpec for success() messages. */ +export function describeMcpSpec(spec: SquadStateMcpSpec): string { + // After iter-7 all specs are `npx -y state-mcp`. + const pkg = spec.args[1] ?? ''; + return spec.source === 'insider' ? `${pkg} (@insider fallback)` : pkg; } export function ensureMemoryGovernanceUpgradeDefaults(dest: string): string[] { @@ -786,7 +823,7 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr } // Run infrastructure ensure checks even when already current - runEnsureChecks(dest, templatesDir, filesUpdated); + await runEnsureChecks(dest, templatesDir, filesUpdated); return { fromVersion: oldVersion, @@ -871,7 +908,7 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr } // Run infrastructure ensure checks - runEnsureChecks(dest, templatesDir, filesUpdated); + await runEnsureChecks(dest, templatesDir, filesUpdated); console.log(); info(`Upgrade complete: v${fromLabel} → v${cliVersion}`); @@ -945,14 +982,30 @@ export async function selfUpgradeCli(options: SelfUpgradeOptions = {}): Promise< try { execSync(cmd, { stdio: 'inherit' }); } catch (err: unknown) { - const isPermission = - err instanceof Error && - 'code' in err && - (err as NodeJS.ErrnoException).code === 'EACCES'; + // UPGRADE-EPERM-FALSE-SUCCESS fix: do NOT swallow self-upgrade failures. + // Previously this only printed a warning and returned, causing the caller + // (cli-entry.ts) to then unconditionally print "✅ Upgraded" and exit 0. + // Now we surface the failure as a thrown error so the caller can exit non-zero + // and avoid the contradictory success message. + const errMsg = err instanceof Error ? err.message : String(err); + const code = err instanceof Error && 'code' in err + ? ((err as NodeJS.ErrnoException).code ?? '') + : ''; + const isPermission = code === 'EACCES' || code === 'EPERM' || /EACCES|EPERM|permission denied/i.test(errMsg); + const isBusy = code === 'EBUSY' || /EBUSY|in use|cannot access|being used by another process/i.test(errMsg); + + let hint: string; if (isPermission) { - warn(`Permission denied. Try: sudo ${cmd}`); + hint = `Permission denied. Try: sudo ${cmd}`; + } else if (isBusy) { + hint = `A file is in use (likely another squad shell is running). Close other squad CLI processes and retry: ${cmd}`; } else { - warn(`Upgrade failed. Try running manually: ${cmd}`); + hint = `Upgrade failed. Try running manually: ${cmd}`; } + + warn(hint); + const failure = new Error(`Self-upgrade failed: ${hint}`); + (failure as NodeJS.ErrnoException).code = code || undefined; + throw failure; } } diff --git a/packages/squad-cli/src/cli/shell/coordinator.ts b/packages/squad-cli/src/cli/shell/coordinator.ts index cca8962ec..fd14317a2 100644 --- a/packages/squad-cli/src/cli/shell/coordinator.ts +++ b/packages/squad-cli/src/cli/shell/coordinator.ts @@ -1,5 +1,5 @@ import { join } from 'node:path'; -import { listRoles, searchRoles, FSStorageProvider } from '@bradygaster/squad-sdk'; +import { listRoles, searchRoles, FSStorageProvider, loadDirConfig, resolveExternalStateDir } from '@bradygaster/squad-sdk'; import type { ShellMessage } from './types.js'; @@ -233,8 +233,15 @@ export async function buildCoordinatorPrompt(config: CoordinatorConfig): Promise const squadRoot = config.teamRoot; const storage = new FSStorageProvider(); + // Resolve effective state dir (external when externalized) + const localSquadDir = join(squadRoot, '.squad'); + const dirConfig = loadDirConfig(localSquadDir); + const stateDir = (dirConfig?.stateLocation === 'external' && dirConfig.projectKey) + ? resolveExternalStateDir(dirConfig.projectKey, false) + : localSquadDir; + // Load team.md for roster - const teamPath = config.teamPath ?? join(squadRoot, '.squad', 'team.md'); + const teamPath = config.teamPath ?? join(stateDir, 'team.md'); let teamContent = ''; try { const raw = await storage.read(teamPath); @@ -252,7 +259,7 @@ export async function buildCoordinatorPrompt(config: CoordinatorConfig): Promise } // Load routing.md for routing rules - const routingPath = config.routingPath ?? join(squadRoot, '.squad', 'routing.md'); + const routingPath = config.routingPath ?? join(stateDir, 'routing.md'); let routingContent = ''; try { const raw = await storage.read(routingPath); diff --git a/packages/squad-cli/src/cli/shell/index.ts b/packages/squad-cli/src/cli/shell/index.ts index 251da8249..af2d50f82 100644 --- a/packages/squad-cli/src/cli/shell/index.ts +++ b/packages/squad-cli/src/cli/shell/index.ts @@ -21,7 +21,7 @@ import type { SquadSession } from '@bradygaster/squad-sdk/client'; import type { SquadPermissionHandler } from '@bradygaster/squad-sdk/client'; import { RateLimitError } from '@bradygaster/squad-sdk/adapter/errors'; import type { ShellMessage } from './types.js'; -import { FSStorageProvider, initSquadTelemetry, TIMEOUTS, StreamingPipeline, recordAgentSpawn, recordAgentDuration, recordAgentError, recordAgentDestroy, RuntimeEventBus, resolveSquad, resolveGlobalSquadPath } from '@bradygaster/squad-sdk'; +import { FSStorageProvider, initSquadTelemetry, TIMEOUTS, StreamingPipeline, recordAgentSpawn, recordAgentDuration, recordAgentError, recordAgentDestroy, RuntimeEventBus, resolveSquad, resolveGlobalSquadPath, loadDirConfig, resolveExternalStateDir } from '@bradygaster/squad-sdk'; import type { UsageEvent } from '@bradygaster/squad-sdk'; import { enableShellMetrics, recordShellSessionDuration, recordAgentResponseLatency, recordShellError } from './shell-metrics.js'; import { parseAgentFromDescription } from './agent-name-parser.js'; @@ -87,7 +87,7 @@ const storage = new FSStorageProvider(); * Approve all permission requests. CLI runs locally with user trust, * so no interactive confirmation is needed. */ -const approveAllPermissions: SquadPermissionHandler = () => ({ kind: 'approved' }); +const approveAllPermissions: SquadPermissionHandler = () => ({ kind: 'approve-once' }); /** Debug logger — writes to stderr only when SQUAD_DEBUG=1. */ function debugLog(...args: unknown[]): void { @@ -208,10 +208,16 @@ export async function runShell(): Promise { // Session persistence — create or resume a previous session // Skip resume on first run (no team.md or .first-run marker present) - const hasTeam = storage.existsSync(join(teamRoot, '.squad', 'team.md')); - const isFirstRun = storage.existsSync(join(teamRoot, '.squad', '.first-run')); + // Resolve effective state dir for externalized state + const localSquadDir = join(teamRoot, '.squad'); + const dirConfig = loadDirConfig(localSquadDir); + const stateDir = (dirConfig?.stateLocation === 'external' && dirConfig.projectKey) + ? resolveExternalStateDir(dirConfig.projectKey, false) + : localSquadDir; + const hasTeam = storage.existsSync(join(stateDir, 'team.md')); + const isFirstRun = storage.existsSync(join(stateDir, '.first-run')); let persistedSession: SessionData = createSession(); - const recentSession = (hasTeam && !isFirstRun) ? loadLatestSession(teamRoot) : null; + const recentSession = (hasTeam && !isFirstRun) ? loadLatestSession(teamRoot, stateDir) : null; if (recentSession) { persistedSession = recentSession; debugLog('resuming recent session', persistedSession.id); @@ -1191,7 +1197,7 @@ export async function runShell(): Promise { let shellMessages: ShellMessage[] = []; function autoSave(): void { persistedSession.messages = shellMessages; - try { saveSession(teamRoot, persistedSession); } catch (err) { debugLog('autoSave failed:', err); } + try { saveSession(teamRoot, persistedSession, stateDir); } catch (err) { debugLog('autoSave failed:', err); } } /** Callback for /resume command — replaces current messages with restored session. */ diff --git a/packages/squad-cli/src/cli/shell/lifecycle.ts b/packages/squad-cli/src/cli/shell/lifecycle.ts index f2c5e119a..2580eb9a1 100644 --- a/packages/squad-cli/src/cli/shell/lifecycle.ts +++ b/packages/squad-cli/src/cli/shell/lifecycle.ts @@ -9,6 +9,7 @@ import path from 'node:path'; import { FSStorageProvider } from '@bradygaster/squad-sdk'; +import { resolveStateDir } from '../core/effective-squad-dir.js'; import { SessionRegistry } from './sessions.js'; import { ShellRenderer } from './render.js'; import type { ShellState, ShellMessage } from './types.js'; @@ -64,16 +65,19 @@ export class ShellLifecycle { this.state.status = 'initializing'; const storage = new FSStorageProvider(); - const squadDir = path.resolve(this.options.teamRoot, '.squad'); - if (!await storage.exists(squadDir) || !await storage.isDirectory(squadDir)) { + const localSquadDir = path.resolve(this.options.teamRoot, '.squad'); + if (!await storage.exists(localSquadDir) || !await storage.isDirectory(localSquadDir)) { this.state.status = 'error'; const err = new Error( `No team found. Run \`squad init\` to create one.` ); - debugLog('initialize: .squad/ directory not found at', squadDir); + debugLog('initialize: .squad/ directory not found at', localSquadDir); throw err; } + // Resolve effective state dir (external when externalized) + const squadDir = resolveStateDir(localSquadDir); + const teamPath = path.join(squadDir, 'team.md'); const teamContent = await storage.read(teamPath); if (teamContent === undefined) { @@ -88,7 +92,7 @@ export class ShellLifecycle { this.discoveredAgents = parseTeamManifest(teamContent); if (this.discoveredAgents.length === 0) { - const initPromptPath = path.join(squadDir, '.init-prompt'); + const initPromptPath = path.join(localSquadDir, '.init-prompt'); if (!await storage.exists(initPromptPath)) { console.warn('⚠ No agents found in team.md. Run `squad init "describe your project"` to cast a team.'); } @@ -295,7 +299,9 @@ export interface WelcomeData { export function loadWelcomeData(teamRoot: string): WelcomeData | null { try { const storage = new FSStorageProvider(); - const teamPath = path.join(teamRoot, '.squad', 'team.md'); + const localSquadDir = path.join(teamRoot, '.squad'); + const stateDir = resolveStateDir(localSquadDir); + const teamPath = path.join(stateDir, 'team.md'); const content = storage.readSync(teamPath); if (content === undefined) return null; @@ -309,7 +315,7 @@ export function loadWelcomeData(teamRoot: string): WelcomeData | null { .map(a => ({ name: a.name, role: a.role, emoji: getRoleEmoji(a.role) })); let focus: string | null = null; - const nowPath = path.join(teamRoot, '.squad', 'identity', 'now.md'); + const nowPath = path.join(stateDir, 'identity', 'now.md'); const nowContent = storage.readSync(nowPath); if (nowContent !== undefined) { const focusMatch = nowContent.match(/focus_area:\s*(.+)/); @@ -317,7 +323,7 @@ export function loadWelcomeData(teamRoot: string): WelcomeData | null { } // Detect and consume first-run marker from `squad init` - const firstRunPath = path.join(teamRoot, '.squad', '.first-run'); + const firstRunPath = path.join(stateDir, '.first-run'); let isFirstRun = false; if (storage.existsSync(firstRunPath)) { isFirstRun = true; diff --git a/packages/squad-cli/src/cli/shell/session-store.ts b/packages/squad-cli/src/cli/shell/session-store.ts index 4b4cdab8d..f3ee8ad70 100644 --- a/packages/squad-cli/src/cli/shell/session-store.ts +++ b/packages/squad-cli/src/cli/shell/session-store.ts @@ -32,8 +32,8 @@ export interface SessionSummary { /** 24 hours in milliseconds — sessions older than this are not offered for resume. */ const RECENT_THRESHOLD_MS = 24 * 60 * 60 * 1000; -function sessionsDir(teamRoot: string): string { - return join(teamRoot, '.squad', 'sessions'); +function sessionsDir(teamRoot: string, stateDir?: string): string { + return stateDir ? join(stateDir, 'sessions') : join(teamRoot, '.squad', 'sessions'); } function ensureDir(dir: string): void { @@ -61,8 +61,8 @@ export function createSession(): SessionData { * The file is named `{safeTimestamp}_{id}.json` so that lexicographic sorting * equals chronological ordering while remaining Windows-safe. */ -export function saveSession(teamRoot: string, session: SessionData): string { - const dir = sessionsDir(teamRoot); +export function saveSession(teamRoot: string, session: SessionData, stateDir?: string): string { + const dir = sessionsDir(teamRoot, stateDir); ensureDir(dir); session.lastActiveAt = new Date().toISOString(); @@ -78,8 +78,8 @@ export function saveSession(teamRoot: string, session: SessionData): string { /** * List all persisted sessions, most recent first. */ -export function listSessions(teamRoot: string): SessionSummary[] { - const dir = sessionsDir(teamRoot); +export function listSessions(teamRoot: string, stateDir?: string): SessionSummary[] { + const dir = sessionsDir(teamRoot, stateDir); if (!storage.existsSync(dir)) return []; const files = storage.listSync(dir).filter(f => f.endsWith('.json')); @@ -112,22 +112,22 @@ export function listSessions(teamRoot: string): SessionSummary[] { * Load the most recent session if it was active within the last 24 hours. * Returns `null` when no recent session exists. */ -export function loadLatestSession(teamRoot: string): SessionData | null { - const sessions = listSessions(teamRoot); +export function loadLatestSession(teamRoot: string, stateDir?: string): SessionData | null { + const sessions = listSessions(teamRoot, stateDir); if (sessions.length === 0) return null; const latest = sessions[0]!; const age = Date.now() - new Date(latest.lastActiveAt).getTime(); if (age > RECENT_THRESHOLD_MS) return null; - return loadSessionById(teamRoot, latest.id); + return loadSessionById(teamRoot, latest.id, stateDir); } /** * Load a specific session by ID. */ -export function loadSessionById(teamRoot: string, sessionId: string): SessionData | null { - const dir = sessionsDir(teamRoot); +export function loadSessionById(teamRoot: string, sessionId: string, stateDir?: string): SessionData | null { + const dir = sessionsDir(teamRoot, stateDir); if (!storage.existsSync(dir)) return null; const filePath = findSessionFile(dir, sessionId); diff --git a/packages/squad-cli/templates/after-agent-reference.md b/packages/squad-cli/templates/after-agent-reference.md index b3c4d709b..e94a635cd 100644 --- a/packages/squad-cli/templates/after-agent-reference.md +++ b/packages/squad-cli/templates/after-agent-reference.md @@ -47,8 +47,8 @@ prompt: | 2. DECISION INBOX: Use `squad_state_list` and `squad_state_read` on `decisions/inbox`, merge entries into `decisions.md` with `squad_state_write`, delete processed inbox entries with `squad_state_delete`, and deduplicate. - 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use ISO 8601 UTC timestamp. - 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use ISO 8601 UTC timestamp. + 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use ISO 8601 UTC timestamp. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms (e.g. `2026-06-02T21-15-30Z`). + 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use ISO 8601 UTC timestamp. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms. 5. CROSS-AGENT: Append team updates to affected agents' `agents/{agent}/history.md` with `squad_state_append`. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. 7. HEALTH REPORT: Log decisions.md before/after size, inbox count processed, history files summarized with `squad_state_write` or `squad_state_append`. diff --git a/packages/squad-cli/templates/ralph-reference.md b/packages/squad-cli/templates/ralph-reference.md index 3d8b2b440..2219d4646 100644 --- a/packages/squad-cli/templates/ralph-reference.md +++ b/packages/squad-cli/templates/ralph-reference.md @@ -93,7 +93,7 @@ This runs as a standalone local process (not inside Copilot) that: - Assigns @copilot to `squad:copilot` issues (if auto-assign is enabled) - Runs until Ctrl+C -**Three layers of Ralph:** +> **MCP tools in watch mode:** `squad watch` automatically injects `--yolo --additional-mcp-config @.mcp.json` into every Copilot sub-invocation so `squad_state_*` tools are available. This is required because Copilot CLI's non-interactive (`-p`) mode does not auto-load workspace `.mcp.json` due to a folder-trust security gate. If `.mcp.json` is missing, run `squad init` or `squad upgrade` to regenerate it. | Layer | When | How | |-------|------|-----| diff --git a/packages/squad-cli/templates/scribe-charter.md b/packages/squad-cli/templates/scribe-charter.md index da6ddfe6a..d335e92c3 100644 --- a/packages/squad-cli/templates/scribe-charter.md +++ b/packages/squad-cli/templates/scribe-charter.md @@ -28,7 +28,7 @@ After every substantial work session: -1. **Log the session** to `log/{timestamp}-{topic}.md` with `squad_state_write`: +1. **Log the session** to `log/{timestamp}-{topic}.md` with `squad_state_write` (replace `:` with `-` in `{timestamp}` so the filename is valid on all platforms, e.g. `2026-06-02T21-15-30Z`): - Who worked - What was done - Decisions made diff --git a/packages/squad-cli/templates/squad.agent.md.template b/packages/squad-cli/templates/squad.agent.md.template index 99e696794..ba0d673c1 100644 --- a/packages/squad-cli/templates/squad.agent.md.template +++ b/packages/squad-cli/templates/squad.agent.md.template @@ -128,7 +128,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Resolve `CURRENT_DATETIME` once from the `` value in your system context. Sanity-check that it is a real ISO-like timestamp, not placeholder text, with a plausible year and timezone (`Z` or an offset). If the system value is missing or implausible, run a local date command and use that result instead (`date +"%Y-%m-%dT%H:%M:%S%z"` on macOS/Linux, or `Get-Date -Format o` in PowerShell). Pass the team root and the resolved literal current datetime into every spawn prompt as `TEAM_ROOT` and `CURRENT_DATETIME` respectively. Never pass placeholder text for `CURRENT_DATETIME`. Pass the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. -**Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"worktree"` (default), `"git-notes"`, `"orphan"`, `"two-layer"`. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. +**Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"local"` (default), `"orphan"`, `"two-layer"`. Legacy alias: `"worktree"` maps to `"local"`. Deprecated: `"git-notes"` maps to `"two-layer"` with a deprecation warning. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. **⚡ Context caching:** After the first message in a session, `team.md`, `routing.md`, and `registry.json` are already in your context. Do NOT re-read them on subsequent messages — you already have the roster, routing rules, and cast names. Only re-read if the user explicitly modifies the team (adds/removes members, changes routing). @@ -607,8 +607,8 @@ prompt: | 0b. PRE-CHECK: Read `decisions.md` and list `decisions/inbox` with state tools. Record measurements. 1. DECISIONS ARCHIVE [HARD GATE]: If decisions.md >= 20480 bytes, archive entries older than 30 days NOW. If >= 51200 bytes, archive entries older than 7 days. Do not skip this step. 2. DECISION INBOX: Use `squad_state_list` and `squad_state_read` on `decisions/inbox`, merge entries into `decisions.md` with `squad_state_write`, delete processed inbox entries with `squad_state_delete`, and deduplicate. - 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use the literal CURRENT_DATETIME value. - 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use the literal CURRENT_DATETIME value. + 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use the literal CURRENT_DATETIME value. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms (e.g. `2026-06-02T21-15-30Z`). + 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use the literal CURRENT_DATETIME value. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms. 5. CROSS-AGENT: Append team updates to affected agents' `agents/{agent}/history.md` with `squad_state_append`. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. 7. GIT COMMIT: Do not commit mutable squad state. If non-state repo files changed, report them for coordinator handling. @@ -865,6 +865,8 @@ Do not pause for permission between work items when Ralph is active. **On-demand reference:** Read `.squad/templates/ralph-reference.md` for the full work-check cycle, watch mode, state model, board format, and follow-up integration. +> **Watch mode MCP note:** `squad watch` injects `--yolo --additional-mcp-config @.mcp.json` into every Copilot sub-invocation automatically. Copilot CLI 1.0.59+ does NOT auto-load workspace `.mcp.json` in non-interactive (`-p`) mode due to a folder-trust security gate — this injection is mandatory for `squad_state_*` tools to register. If `.mcp.json` is missing, advise the user to run `squad init` or `squad upgrade`. + ### Connecting to a Repo **On-demand reference:** Read `.squad/templates/issue-lifecycle.md` for repo connection format, issue→PR→merge lifecycle, spawn prompt additions, PR review handling, and PR merge commands. diff --git a/packages/squad-sdk/package.json b/packages/squad-sdk/package.json index e59c60fde..b6970f041 100644 --- a/packages/squad-sdk/package.json +++ b/packages/squad-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@bradygaster/squad-sdk", - "version": "0.9.6", + "version": "0.9.6-preview.13", "description": "Squad SDK — Programmable multi-agent runtime for GitHub Copilot", "type": "module", "main": "./dist/index.js", diff --git a/packages/squad-sdk/src/adapter/client.ts b/packages/squad-sdk/src/adapter/client.ts index 6f654e5f7..161c3047c 100644 --- a/packages/squad-sdk/src/adapter/client.ts +++ b/packages/squad-sdk/src/adapter/client.ts @@ -468,8 +468,24 @@ export class SquadClient { } try { + // Normalize legacy 'approved' permission kind → 'approve-once' before forwarding to SDK + const normalizedConfig: SquadSessionConfig = config.onPermissionRequest + ? { + ...config, + onPermissionRequest: async ( + req: Parameters>[0], + inv: Parameters>[1], + ) => { + const result = await config.onPermissionRequest!(req, inv); + if (result.kind === 'approved') { + return { ...result, kind: 'approve-once' as const }; + } + return result; + }, + } + : config; // Cast config to handle SDK version differences in SessionConfig type - const session = await this.client.createSession(config as unknown as Parameters[0]); + const session = await this.client.createSession(normalizedConfig as unknown as Parameters[0]); const result = new CopilotSessionAdapter(session); if (result.sessionId) { span.setAttribute('session.id', result.sessionId); @@ -505,7 +521,7 @@ export class SquadClient { if (msg.includes('onPermissionRequest')) { throw new Error( 'Session creation failed: an onPermissionRequest handler is required. ' + - 'Pass { onPermissionRequest: () => ({ kind: "approved" }) } in your session config ' + + 'Pass { onPermissionRequest: () => ({ kind: "approve-once" }) } in your session config ' + 'to approve all permissions, or provide a custom handler.' ); } diff --git a/packages/squad-sdk/src/adapter/types.ts b/packages/squad-sdk/src/adapter/types.ts index 8dc73dade..599b4b6df 100644 --- a/packages/squad-sdk/src/adapter/types.ts +++ b/packages/squad-sdk/src/adapter/types.ts @@ -592,6 +592,11 @@ export interface SquadPermissionRequest { export interface SquadPermissionRequestResult { /** Outcome of the permission request */ kind: + | "approve-once" + /** + * @deprecated Use `"approve-once"` instead. This value is kept for + * backwards compatibility and is normalised to `"approve-once"` by the SDK. + */ | "approved" | "denied-by-rules" | "denied-no-approval-rule-and-could-not-request-from-user" diff --git a/packages/squad-sdk/src/config/init.ts b/packages/squad-sdk/src/config/init.ts index 3038834fc..9e37d7363 100644 --- a/packages/squad-sdk/src/config/init.ts +++ b/packages/squad-sdk/src/config/init.ts @@ -624,12 +624,19 @@ interface McpServerSpec { env?: Record; } -function buildMcpServerSpecs(isGitHub: boolean): McpServerSpec[] { +function buildMcpServerSpecs(isGitHub: boolean, cliVersion?: string): McpServerSpec[] { + // Pin the squad-cli package to the currently-installed CLI version so that + // `npx -y @bradygaster/squad-cli state-mcp` does NOT silently resolve to the + // npm `latest` dist-tag (which may predate the `state-mcp` command and thus + // expose zero tools to Copilot — see MCP-BRIDGE-BROKEN root cause). + const pkgSpec = cliVersion && cliVersion !== '0.0.0' + ? `@bradygaster/squad-cli@${cliVersion}` + : '@bradygaster/squad-cli'; const servers: McpServerSpec[] = [ { name: 'squad_state', command: 'npx', - args: ['-y', '@bradygaster/squad-cli', 'state-mcp'], + args: ['-y', pkgSpec, 'state-mcp'], }, ]; @@ -1246,7 +1253,7 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre // No git remote — assume GitHub (default) } - const mcpServers = buildMcpServerSpecs(isGitHub); + const mcpServers = buildMcpServerSpecs(isGitHub, version); // ------------------------------------------------------------------------- // Create .github/agents/squad.agent.md diff --git a/packages/squad-sdk/src/index.ts b/packages/squad-sdk/src/index.ts index a06142f6a..3833c1fb4 100644 --- a/packages/squad-sdk/src/index.ts +++ b/packages/squad-sdk/src/index.ts @@ -104,9 +104,10 @@ export * from './platform/index.js'; export * from './storage/index.js'; export * from './memory/index.js'; -// Git-native state backends (Issue #807) +// Git-native state backends (Issue #807, hardened in #864) export type { StateBackend, StateBackendType, StateBackendConfig } from './state-backend.js'; -export { WorktreeBackend, GitNotesBackend, OrphanBranchBackend, resolveStateBackend, validateStateKey, StateBackendStorageAdapter } from './state-backend.js'; +export { WorktreeBackend, GitNotesBackend, OrphanBranchBackend, TwoLayerBackend, CircuitBreaker, GitExecError, resolveStateBackend, validateStateKey, StateBackendStorageAdapter, verifyStateBackend } from './state-backend.js'; +export type { PromoteNotesResult } from './state-backend.js'; // State facade (Phase 2) — namespaced to avoid conflicts with existing config/sharing exports export { diff --git a/packages/squad-sdk/src/state-backend.ts b/packages/squad-sdk/src/state-backend.ts index c348269ca..c3b022eb8 100644 --- a/packages/squad-sdk/src/state-backend.ts +++ b/packages/squad-sdk/src/state-backend.ts @@ -1,6 +1,10 @@ /** * Git-native state backends for `.squad/` state storage. * + * Hardening: retry with exponential backoff for transient git errors, + * circuit-breaker to prevent cascading failures, startup verification, + * and observable error surfacing (no silent swallowing). + * * @module state-backend */ @@ -11,6 +15,108 @@ import type { StorageProvider, StorageStats } from './storage/storage-provider.j const storage = new FSStorageProvider(); +// ── Retry configuration ───────────────────────────────────────────── +const RETRY_MAX = 3; +const RETRY_BASE_MS = 100; +const RETRY_MAX_DELAY_MS = 2000; + +/** + * Buffer ceiling for git stdout/stderr. The Node default is 1 MiB, which is + * easily blown by `git ls-tree` against large trees or `git notes show` on + * sizeable JSON blobs — spawnSync then dies with ENOBUFS and the wrapper + * surfaces it as a generic "git command failed". 256 MiB keeps us safely + * above any realistic `.squad/` state payload while still capping memory. + */ +const GIT_MAX_BUFFER = 256 * 1024 * 1024; + +// ── Circuit breaker configuration ─────────────────────────────────── +const CIRCUIT_BREAKER_THRESHOLD = 5; +const CIRCUIT_BREAKER_COOLDOWN_MS = 30_000; + +/** Classify git stderr as a transient (retryable) failure. */ +function isTransientGitError(stderr: string): boolean { + return /unable to access|could not lock|timeout|connection refused|network|SSL|couldn't connect|Another git process|index\.lock/i.test(stderr); +} + +/** Non-busy synchronous sleep using Atomics. Safe in Node.js 20+. */ +function sleepSync(ms: number): void { + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); +} + +/** + * Execute a git command with retry for transient errors. + * Throws on failure after exhausting retries. + */ +function gitExecWithRetry(args: string[], cwd: string, trimOutput = true): string { + let lastError: unknown; + for (let attempt = 0; attempt <= RETRY_MAX; attempt++) { + try { + const raw = execFileSync('git', args, { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: GIT_MAX_BUFFER }); + return trimOutput ? raw.trim() : raw; + } catch (err: unknown) { + lastError = err; + const stderr = (err as { stderr?: string }).stderr ?? ''; + if (attempt < RETRY_MAX && isTransientGitError(stderr)) { + const delay = Math.min(RETRY_BASE_MS * 2 ** attempt, RETRY_MAX_DELAY_MS); + sleepSync(delay); + continue; + } + throw err; + } + } + throw lastError; +} + +/** + * Execute a git command with stdin input and retry for transient errors. + * Throws on failure after exhausting retries. + */ +function gitExecWithInputAndRetry(args: string[], cwd: string, input: string): string { + let lastError: unknown; + for (let attempt = 0; attempt <= RETRY_MAX; attempt++) { + try { + return execFileSync('git', args, { cwd, input, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: GIT_MAX_BUFFER }).trim(); + } catch (err: unknown) { + lastError = err; + const stderr = (err as { stderr?: string }).stderr ?? ''; + if (attempt < RETRY_MAX && isTransientGitError(stderr)) { + const delay = Math.min(RETRY_BASE_MS * 2 ** attempt, RETRY_MAX_DELAY_MS); + sleepSync(delay); + continue; + } + throw err; + } + } + throw lastError; +} + +// ── Typed git errors ──────────────────────────────────────────────── + +/** Typed error for git command failures with stderr and command context. */ +export class GitExecError extends Error { + readonly name = 'GitExecError'; + constructor( + public readonly command: string, + public readonly reason: string, + public readonly stderr: string, + ) { + super(`git command failed: ${command} — ${reason}`); + } +} + +/** + * Patterns indicating an expected "not found" result from git, + * as opposed to a real failure (corruption, permission, broken repo). + */ +const GIT_EXPECTED_MISSING_RE = + /no note found|does not exist in|Not a valid object name|invalid object name|not a tree object|bad default revision|Needed a single revision|unknown revision or path|bad object/i; + +function isExpectedMissing(err: unknown): boolean { + const stderr = (err as { stderr?: string }).stderr ?? ''; + const msg = err instanceof Error ? err.message : ''; + return GIT_EXPECTED_MISSING_RE.test(stderr) || GIT_EXPECTED_MISSING_RE.test(msg); +} + export type StateBackendType = 'local' | 'external' | 'orphan' | 'two-layer'; export interface StateBackend { @@ -23,6 +129,186 @@ export interface StateBackend { readonly name: string; } +// ── Circuit Breaker ───────────────────────────────────────────────── + +type CircuitState = 'closed' | 'open' | 'half-open'; + +export class CircuitBreaker { + private state: CircuitState = 'closed'; + private failures = 0; + private lastFailureTime = 0; + + constructor( + private readonly threshold: number = CIRCUIT_BREAKER_THRESHOLD, + private readonly cooldownMs: number = CIRCUIT_BREAKER_COOLDOWN_MS, + ) {} + + /** Execute an operation through the circuit breaker. */ + execute(fn: () => T, operation: string): T { + if (this.state === 'open') { + if (Date.now() - this.lastFailureTime >= this.cooldownMs) { + this.state = 'half-open'; + } else { + throw new Error( + `Circuit breaker OPEN after ${this.failures} consecutive git failures. ` + + `Operation '${operation}' rejected. Will retry after ${Math.ceil((this.cooldownMs - (Date.now() - this.lastFailureTime)) / 1000)}s cooldown.`, + ); + } + } + try { + const result = fn(); + this.onSuccess(); + return result; + } catch (err) { + this.onFailure(); + throw err; + } + } + + private onSuccess(): void { + this.failures = 0; + this.state = 'closed'; + } + + private onFailure(): void { + this.failures++; + this.lastFailureTime = Date.now(); + if (this.failures >= this.threshold) { + this.state = 'open'; + } + } + + get consecutiveFailures(): number { return this.failures; } + get currentState(): CircuitState { return this.state; } +} + +// ── Git exec helpers (with retry + error classification) ──────────── + +/** + * Execute a git command, returning null for expected absence (e.g., missing ref/path/note). + * Throws GitExecError for real failures (permission denied, corruption, broken repo). + * Retries transient errors before classifying. + * + * NOTE: `args` is an array, NOT a space-separated string. This was previously a + * string split on whitespace, which silently mangled any argument containing a + * space (commit messages, paths with spaces, etc.). + */ +function gitExecMaybeMissing(args: string[], cwd: string, trimOutput = true): string | null { + try { + return gitExecWithRetry(args, cwd, trimOutput); + } catch (err: unknown) { + if (isExpectedMissing(err)) return null; + const stderr = (err as { stderr?: string }).stderr ?? ''; + const msg = err instanceof Error ? err.message : String(err); + throw new GitExecError(`git ${args.join(' ')}`, msg, stderr); + } +} + +/** + * Execute a git command that MUST succeed. Throws GitExecError on any failure. + * Retries transient errors before throwing. + * + * NOTE: `args` is an array, NOT a space-separated string (see gitExecMaybeMissing). + */ +function gitExecOrThrow(args: string[], cwd: string): string { + try { + return gitExecWithRetry(args, cwd); + } catch (err: unknown) { + const stderr = (err as { stderr?: string }).stderr ?? ''; + const msg = err instanceof Error ? err.message : String(err); + throw new GitExecError(`git ${args.join(' ')}`, msg, stderr); + } +} + +// ── Optimistic concurrency (compare-and-swap) ─────────────────────── + +/** Maximum CAS retry attempts before surfacing as concurrency error. */ +const CAS_MAX_ATTEMPTS = 5; +/** Base delay for jittered exponential backoff: 50, 100, 200, 400, 800 ms. */ +const CAS_BASE_DELAY_MS = 50; +/** Git's canonical "ref must not exist" sentinel for update-ref CAS. */ +const GIT_NULL_OID = '0000000000000000000000000000000000000000'; + +/** + * Thrown when an optimistic CAS write (update-ref expected-old) fails after + * exhausting all retry attempts. Callers may surface, requeue, or retry with + * application-level coordination. Distinct from GitExecError, which signals + * a real git failure (corruption, permission, broken repo). + */ +export class StateBackendConcurrencyError extends Error { + readonly name = 'StateBackendConcurrencyError'; + constructor( + public readonly operation: string, + public readonly attempts: number, + public readonly lastStderr: string, + ) { + super(`State backend concurrency conflict on '${operation}' after ${attempts} attempts: ${lastStderr || 'ref moved between read and write'}`); + } +} + +/** + * Jittered exponential backoff in milliseconds for attempt N (0-indexed): + * 50, 100, 200, 400, 800 ms base, with ±25% jitter to avoid thundering herd. + */ +function jitteredBackoffMs(attempt: number): number { + const base = CAS_BASE_DELAY_MS * Math.pow(2, attempt); + const jitter = (Math.random() - 0.5) * 0.5 * base; + return Math.max(1, Math.round(base + jitter)); +} + +/** + * Patterns indicating an `update-ref` CAS conflict (retryable) rather than + * a hard failure. We classify any "ref ... is at ... but expected ..." or + * lock contention as retryable so the caller can re-read and re-attempt. + */ +const GIT_UPDATE_REF_CAS_RE = /is at .* but expected|cannot lock ref|reference already exists|cas_error/i; + +/** + * Attempt an atomic ref update with compare-and-swap semantics. + * + * `expectedOldSha` of `null` means "create only if does not exist" + * (passed as 40 zeros, git's canonical no-such-ref sentinel). + * + * Returns `{ ok: true }` on success, `{ ok: false, stderr }` on CAS conflict, + * and re-throws any non-CAS git failure (corruption, permission, etc.). + */ +function tryUpdateRef(ref: string, newSha: string, expectedOldSha: string | null, cwd: string): { ok: boolean; stderr: string } { + if (_casInjector) { + const forced = _casInjector(ref); + if (forced) return forced; + } + const expected = expectedOldSha ?? GIT_NULL_OID; + try { + execFileSync('git', ['update-ref', ref, newSha, expected], { + cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: GIT_MAX_BUFFER, + }); + return { ok: true, stderr: '' }; + } catch (err: unknown) { + const stderr = (err as { stderr?: string }).stderr ?? ''; + if (GIT_UPDATE_REF_CAS_RE.test(stderr)) { + return { ok: false, stderr }; + } + throw err; + } +} + +/** + * Test-only injector for forcing CAS-conflict / success outcomes deterministically. + * Production callers never set this. @internal + */ +let _casInjector: ((ref: string) => { ok: boolean; stderr: string } | null) | null = null; +export function _setCasInjectorForTesting(fn: ((ref: string) => { ok: boolean; stderr: string } | null) | null): void { + _casInjector = fn; +} + +/** + * Internal CAS primitive — exported for unit tests only. + * @internal + */ +export const _tryUpdateRefForTesting = tryUpdateRef; + +// ── Backends ──────────────────────────────────────────────────────── + export class WorktreeBackend implements StateBackend { readonly name = 'local'; private readonly root: string; @@ -61,21 +347,6 @@ export class WorktreeBackend implements StateBackend { } } -function gitExec(args: string[], cwd: string): string | null { - try { - return execFileSync('git', args, { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); - } catch { return null; } -} - -function gitExecWithInput(args: string[], input: string, cwd: string): string { - return execFileSync('git', args, { cwd, input, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); -} - -function gitExecOrThrow(args: string[], cwd: string): string { - const result = gitExec(args, cwd); - if (result === null) throw new Error(`git command failed: git ${args.join(' ')}`); - return result; -} /** * Validate a state key against characters that could corrupt git plumbing @@ -105,6 +376,7 @@ export function validateStateKey(key: string): void { } } + function normalizeKey(relativePath: string): string { const normalized = relativePath.replace(/\\/g, '/').replace(/^\/+/, '').replace(/\/+$/, ''); // Empty string after normalization means "root" — valid for list() operations @@ -118,26 +390,38 @@ export class GitNotesBackend implements StateBackend { readonly name = 'git-notes'; private readonly cwd: string; private readonly ref = 'squad'; - private cachedAnchor: string | undefined; + private readonly breaker = new CircuitBreaker(); + private _rootCommit: string | undefined; constructor(repoRoot: string) { this.cwd = repoRoot; } - /** - * Return the repo's root commit — the first commit with no parents. - * This commit exists on every branch, so the note persists across - * branch switches (unlike HEAD, which moves with the checked-out branch). - */ - private getAnchorCommit(): string { - if (this.cachedAnchor) return this.cachedAnchor; - const root = gitExec(['rev-list', '--max-parents=0', 'HEAD'], this.cwd); - if (!root) throw new Error('git-notes backend: no root commit found'); - // If multiple roots (e.g. from unrelated-history merges), use the first. - this.cachedAnchor = root.split('\n')[0]!.trim(); - return this.cachedAnchor; + /** Returns the root commit SHA — a stable anchor that never moves. Cached after first call. */ + private rootCommit(): string { + if (!this._rootCommit) { + this._rootCommit = gitExecOrThrow(['rev-list', '--max-parents=0', 'HEAD'], this.cwd); + } + return this._rootCommit; } - private loadBlob(): Record { - const anchor = this.getAnchorCommit(); - const raw = gitExec(['notes', `--ref=${this.ref}`, 'show', anchor], this.cwd); + /** Resolve the current SHA of refs/notes/, or null if it doesn't exist. */ + private readNotesRef(): string | null { + return gitExecMaybeMissing(['rev-parse', '--verify', `refs/notes/${this.ref}`], this.cwd); + } + + /** + * Load the JSON blob attached to the root commit at a SPECIFIC notes ref SHA. + * Reading at a pinned SHA (not the live ref tip) is the foundation of the CAS + * loop — without it, a writer could observe state at version N, build version + * N+1, but race against another writer who already advanced to N+1' (losing + * data). With a pinned read, the subsequent update-ref CAS catches the race. + * + * NOTE: this relies on the notes tree having no fanout. Git uses fanout + * (ab/cdef.../) only when many notes are present; we only ever store a single + * note (on the root commit), so the path is just `:`. + */ + private loadBlobAt(refSha: string | null): Record { + if (!refSha) return {}; + const anchor = this.rootCommit(); + const raw = gitExecMaybeMissing(['show', `${refSha}:${anchor}`], this.cwd, false); if (!raw) return {}; try { const parsed: unknown = JSON.parse(raw); @@ -148,53 +432,108 @@ export class GitNotesBackend implements StateBackend { } catch { return {}; } } - private saveBlob(blob: Record): void { - const anchor = this.getAnchorCommit(); + /** Convenience reader at the live ref tip (used for read-only operations). */ + private loadBlob(): Record { + return this.loadBlobAt(this.readNotesRef()); + } + + /** + * Build a new notes commit and attempt to atomically swing refs/notes/ + * from `expectedOldRefSha` to it. Returns the same `{ ok, stderr }` shape as + * tryUpdateRef so the caller's retry loop can act. + */ + private atomicSaveBlob(blob: Record, expectedOldRefSha: string | null): { ok: boolean; stderr: string } { + const anchor = this.rootCommit(); const json = JSON.stringify(blob, null, 2); + let blobSha: string; + let treeSha: string; + let commitSha: string; try { - gitExecWithInput(['notes', `--ref=${this.ref}`, 'add', '-f', '--file', '-', anchor], json, this.cwd); - } catch { throw new Error('git-notes backend: failed to write note on ' + anchor); } + blobSha = gitExecWithInputAndRetry(['hash-object', '-w', '--stdin'], this.cwd, json); + treeSha = gitExecWithInputAndRetry(['mktree'], this.cwd, `100644 blob ${blobSha}\t${anchor}\n`); + const parentArgs = expectedOldRefSha ? ['-p', expectedOldRefSha] : []; + commitSha = gitExecWithRetry(['commit-tree', treeSha, ...parentArgs, '-m', 'Update squad state'], this.cwd); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`git-notes backend: failed to build notes commit — ${msg}`); + } + return tryUpdateRef(`refs/notes/${this.ref}`, commitSha, expectedOldRefSha, this.cwd); + } + + /** + * Run a mutator under optimistic CAS. The mutator receives the current blob + * (re-read on every attempt) and may mutate it; its return value is forwarded + * to the caller on success. On CAS conflict, the loop retries with jittered + * backoff up to CAS_MAX_ATTEMPTS times, then throws StateBackendConcurrencyError. + */ + private mutateBlob(operation: string, mutator: (blob: Record) => T): T { + let lastStderr = ''; + for (let attempt = 0; attempt < CAS_MAX_ATTEMPTS; attempt++) { + const oldRefSha = this.readNotesRef(); + const blob = this.loadBlobAt(oldRefSha); + const result = mutator(blob); + const writeResult = this.atomicSaveBlob(blob, oldRefSha); + if (writeResult.ok) return result; + lastStderr = writeResult.stderr; + if (attempt < CAS_MAX_ATTEMPTS - 1) { + sleepSync(jitteredBackoffMs(attempt)); + } + } + throw new StateBackendConcurrencyError(operation, CAS_MAX_ATTEMPTS, lastStderr); } read(relativePath: string): string | undefined { - const blob = this.loadBlob(); - return blob[normalizeKey(relativePath)]; + return this.breaker.execute(() => { + const blob = this.loadBlob(); + return blob[normalizeKey(relativePath)]; + }, `git-notes:read(${relativePath})`); } write(relativePath: string, content: string): void { - const blob = this.loadBlob(); - blob[normalizeKey(relativePath)] = content; - this.saveBlob(blob); + this.breaker.execute(() => { + this.mutateBlob(`git-notes:write(${relativePath})`, (blob) => { + blob[normalizeKey(relativePath)] = content; + }); + }, `git-notes:write(${relativePath})`); } exists(relativePath: string): boolean { - return Object.hasOwn(this.loadBlob(), normalizeKey(relativePath)); + return this.breaker.execute( + () => Object.hasOwn(this.loadBlob(), normalizeKey(relativePath)), + `git-notes:exists(${relativePath})`, + ); } list(relativeDir: string): string[] { - const blob = this.loadBlob(); - const normalized = normalizeKey(relativeDir); - const dirPrefix = normalized ? normalized + '/' : ''; - const entries = new Set(); - for (const key of Object.keys(blob)) { - if (key.startsWith(dirPrefix)) { - const rest = key.slice(dirPrefix.length); - const slash = rest.indexOf('/'); - entries.add(slash === -1 ? rest : rest.slice(0, slash)); + return this.breaker.execute(() => { + const blob = this.loadBlob(); + const normalized = normalizeKey(relativeDir); + const dirPrefix = normalized ? normalized + '/' : ''; + const entries = new Set(); + for (const key of Object.keys(blob)) { + if (key.startsWith(dirPrefix)) { + const rest = key.slice(dirPrefix.length); + const slash = rest.indexOf('/'); + entries.add(slash === -1 ? rest : rest.slice(0, slash)); + } } - } - return [...entries].sort(); + return [...entries].sort(); + }, `git-notes:list(${relativeDir})`); } delete(relativePath: string): boolean { - const blob = this.loadBlob(); - const key = normalizeKey(relativePath); - if (!Object.hasOwn(blob, key)) return false; - delete blob[key]; - this.saveBlob(blob); - return true; + return this.breaker.execute(() => { + const key = normalizeKey(relativePath); + return this.mutateBlob(`git-notes:delete(${relativePath})`, (blob) => { + if (!Object.hasOwn(blob, key)) return false; + delete blob[key]; + return true; + }); + }, `git-notes:delete(${relativePath})`); } append(relativePath: string, content: string): void { - const blob = this.loadBlob(); - const key = normalizeKey(relativePath); - blob[key] = (blob[key] ?? '') + content; - this.saveBlob(blob); + this.breaker.execute(() => { + this.mutateBlob(`git-notes:append(${relativePath})`, (blob) => { + const key = normalizeKey(relativePath); + blob[key] = (blob[key] ?? '') + content; + }); + }, `git-notes:append(${relativePath})`); } } @@ -202,93 +541,157 @@ export class OrphanBranchBackend implements StateBackend { readonly name = 'orphan'; private readonly cwd: string; private readonly branch: string; + private readonly breaker = new CircuitBreaker(); constructor(repoRoot: string, branch = 'squad-state') { this.cwd = repoRoot; this.branch = branch; } private ensureBranch(): void { - if (gitExec(['rev-parse', '--verify', `refs/heads/${this.branch}`], this.cwd)) return; + if (gitExecMaybeMissing(['rev-parse', '--verify', `refs/heads/${this.branch}`], this.cwd)) return; let tree: string; try { - tree = gitExecWithInput(['mktree'], '', this.cwd); - } catch { throw new Error('orphan backend: failed to create empty tree'); } + tree = gitExecWithInputAndRetry(['mktree'], this.cwd, ''); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`orphan backend: failed to create empty tree — ${msg}`); + } let commit: string; try { - commit = execFileSync('git', ['commit-tree', tree, '-m', 'Initialize squad-state branch'], { - cwd: this.cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], - }).trim(); - } catch { throw new Error('orphan backend: failed to create initial commit'); } - gitExecOrThrow(['update-ref', `refs/heads/${this.branch}`, commit], this.cwd); + commit = gitExecWithRetry( + ['commit-tree', tree, '-m', 'Initialize squad-state branch'], + this.cwd, + ); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`orphan backend: failed to create initial commit — ${msg}`); + } + // CAS create: succeeds only if the ref still doesn't exist. If a concurrent + // writer created it between our check and now, fall through silently — the + // caller's mutation loop will pick up the new head on its next iteration. + const writeResult = tryUpdateRef(`refs/heads/${this.branch}`, commit, null, this.cwd); + if (!writeResult.ok) { + // Re-verify someone else created it; if so, we're done. + if (gitExecMaybeMissing(['rev-parse', '--verify', `refs/heads/${this.branch}`], this.cwd)) return; + throw new Error(`orphan backend: failed to initialize branch — ${writeResult.stderr}`); + } } read(relativePath: string): string | undefined { - const key = normalizeKey(relativePath); - try { - return execFileSync('git', ['show', `${this.branch}:${key}`], { - cwd: this.cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], - }); - } catch { - return undefined; - } + return this.breaker.execute(() => { + const result = gitExecMaybeMissing(['show', `${this.branch}:${normalizeKey(relativePath)}`], this.cwd, false); + return result ?? undefined; + }, `orphan:read(${relativePath})`); } write(relativePath: string, content: string): void { - this.ensureBranch(); - const key = normalizeKey(relativePath); - let blobHash: string; - try { - blobHash = gitExecWithInput(['hash-object', '-w', '--stdin'], content, this.cwd); - } catch { throw new Error(`orphan backend: failed to hash content for ${key}`); } + this.breaker.execute(() => { + this.ensureBranch(); + const key = normalizeKey(relativePath); - let currentTree: string; - const treeResult = gitExec(['log', '--format=%T', '-1', this.branch], this.cwd); - if (!treeResult) { + // Blob is content-addressed, so hash once outside the CAS loop. + let blobHash: string; try { - currentTree = gitExecWithInput(['mktree'], '', this.cwd); - } catch { throw new Error('orphan backend: failed to create empty tree'); } - } else { currentTree = treeResult; } + blobHash = gitExecWithInputAndRetry(['hash-object', '-w', '--stdin'], this.cwd, content); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`orphan backend: failed to hash content for ${key} — ${msg}`); + } - const newTree = this.updateTree(currentTree, key.split('/'), blobHash); - const parentCommit = gitExec(['rev-parse', this.branch], this.cwd); - let newCommit: string; - try { - const parentArgs = parentCommit ? ['-p', parentCommit] : []; - newCommit = execFileSync('git', ['commit-tree', newTree, ...parentArgs, '-m', `Update ${key}`], { - cwd: this.cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], - }).trim(); - } catch { throw new Error(`orphan backend: failed to commit update for ${key}`); } - gitExecOrThrow(['update-ref', `refs/heads/${this.branch}`, newCommit], this.cwd); + let lastStderr = ''; + for (let attempt = 0; attempt < CAS_MAX_ATTEMPTS; attempt++) { + // Re-read the ref every iteration so we rebuild on top of the latest tree. + const parentCommit = gitExecMaybeMissing(['rev-parse', '--verify', `refs/heads/${this.branch}`], this.cwd); + let currentTree: string; + if (parentCommit) { + const treeResult = gitExecMaybeMissing(['rev-parse', `${parentCommit}^{tree}`], this.cwd); + currentTree = treeResult ?? gitExecWithInputAndRetry(['mktree'], this.cwd, ''); + } else { + try { currentTree = gitExecWithInputAndRetry(['mktree'], this.cwd, ''); } + catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`orphan backend: failed to create empty tree — ${msg}`); + } + } + + const newTree = this.updateTree(currentTree, key.split('/'), blobHash); + let newCommit: string; + try { + const parentArgs = parentCommit ? ['-p', parentCommit] : []; + newCommit = gitExecWithRetry( + ['commit-tree', newTree, ...parentArgs, '-m', `Update ${key}`], + this.cwd, + ); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`orphan backend: failed to commit update for ${key} — ${msg}`); + } + + const writeResult = tryUpdateRef(`refs/heads/${this.branch}`, newCommit, parentCommit, this.cwd); + if (writeResult.ok) return; + lastStderr = writeResult.stderr; + if (attempt < CAS_MAX_ATTEMPTS - 1) { + sleepSync(jitteredBackoffMs(attempt)); + } + } + throw new StateBackendConcurrencyError(`orphan:write(${relativePath})`, CAS_MAX_ATTEMPTS, lastStderr); + }, `orphan:write(${relativePath})`); } exists(relativePath: string): boolean { - return gitExec(['cat-file', '-t', `${this.branch}:${normalizeKey(relativePath)}`], this.cwd) !== null; + return this.breaker.execute( + () => gitExecMaybeMissing(['cat-file', '-t', `${this.branch}:${normalizeKey(relativePath)}`], this.cwd) !== null, + `orphan:exists(${relativePath})`, + ); } list(relativeDir: string): string[] { - const key = normalizeKey(relativeDir); - const target = key ? `${this.branch}:${key}` : `${this.branch}:`; - const result = gitExec(['ls-tree', '--name-only', target], this.cwd); - if (!result) return []; - return result.split('\n').filter(Boolean); + return this.breaker.execute(() => { + const key = normalizeKey(relativeDir); + const target = key ? `${this.branch}:${key}` : `${this.branch}:`; + const result = gitExecMaybeMissing(['ls-tree', '--name-only', target], this.cwd); + if (!result) return []; + return result.split('\n').filter(Boolean); + }, `orphan:list(${relativeDir})`); } delete(relativePath: string): boolean { - const key = normalizeKey(relativePath); - if (!this.exists(relativePath)) return false; - this.ensureBranch(); - const treeResult = gitExec(['log', '--format=%T', '-1', this.branch], this.cwd); - if (!treeResult) return false; - const newTree = this.removeFromTree(treeResult, key.split('/')); - const parentCommit = gitExec(['rev-parse', this.branch], this.cwd); - let newCommit: string; - try { - const parentArgs = parentCommit ? ['-p', parentCommit] : []; - newCommit = execFileSync('git', ['commit-tree', newTree, ...parentArgs, '-m', `Delete ${key}`], { - cwd: this.cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], - }).trim(); - } catch { throw new Error(`orphan backend: failed to commit delete for ${key}`); } - gitExecOrThrow(['update-ref', `refs/heads/${this.branch}`, newCommit], this.cwd); - return true; + return this.breaker.execute(() => { + const key = normalizeKey(relativePath); + if (gitExecMaybeMissing(['cat-file', '-t', `${this.branch}:${key}`], this.cwd) === null) return false; + this.ensureBranch(); + + let lastStderr = ''; + for (let attempt = 0; attempt < CAS_MAX_ATTEMPTS; attempt++) { + const parentCommit = gitExecMaybeMissing(['rev-parse', '--verify', `refs/heads/${this.branch}`], this.cwd); + if (!parentCommit) return false; + const treeResult = gitExecMaybeMissing(['rev-parse', `${parentCommit}^{tree}`], this.cwd); + if (!treeResult) return false; + + // Re-check existence at the freshly-read tree — a concurrent delete may + // have already removed our key, in which case there's nothing to do. + if (gitExecMaybeMissing(['cat-file', '-t', `${parentCommit}:${key}`], this.cwd) === null) return false; + + const newTree = this.removeFromTree(treeResult, key.split('/')); + let newCommit: string; + try { + newCommit = gitExecWithRetry( + ['commit-tree', newTree, '-p', parentCommit, '-m', `Delete ${key}`], + this.cwd, + ); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`orphan backend: failed to commit delete for ${key} — ${msg}`); + } + + const writeResult = tryUpdateRef(`refs/heads/${this.branch}`, newCommit, parentCommit, this.cwd); + if (writeResult.ok) return true; + lastStderr = writeResult.stderr; + if (attempt < CAS_MAX_ATTEMPTS - 1) { + sleepSync(jitteredBackoffMs(attempt)); + } + } + throw new StateBackendConcurrencyError(`orphan:delete(${relativePath})`, CAS_MAX_ATTEMPTS, lastStderr); + }, `orphan:delete(${relativePath})`); } append(relativePath: string, content: string): void { @@ -299,34 +702,37 @@ export class OrphanBranchBackend implements StateBackend { private removeFromTree(baseTree: string, pathSegments: string[]): string { if (pathSegments.length === 0) throw new Error('orphan backend: empty path segments'); if (pathSegments.length === 1) { - // Remove the entry from the tree - const listing = gitExec(['ls-tree', baseTree], this.cwd) ?? ''; + const listing = gitExecMaybeMissing(['ls-tree', baseTree], this.cwd) ?? ''; const lines = listing.split('\n').filter(Boolean); const filtered = lines.filter((line) => { const match = line.match(/^(\d+)\s+(blob|tree)\s+([a-f0-9]+)\t(.+)$/); return !(match && match[4] === pathSegments[0]); }); try { - return gitExecWithInput(['mktree'], filtered.length > 0 ? filtered.join('\n') + '\n' : '', this.cwd); - } catch { throw new Error(`orphan backend: failed to remove entry ${pathSegments[0]}`); } + return gitExecWithInputAndRetry(['mktree'], this.cwd, filtered.length > 0 ? filtered.join('\n') + '\n' : ''); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`orphan backend: failed to remove entry ${pathSegments[0]} — ${msg}`); + } } const [dir, ...rest] = pathSegments; const subTreeHash = this.getSubtreeHash(baseTree, dir!); - if (!subTreeHash) return baseTree; // subtree doesn't exist, nothing to remove + if (!subTreeHash) return baseTree; const childTree = this.removeFromTree(subTreeHash, rest); - // If the child tree is now empty, remove the directory entry entirely - const childListing = gitExec(['ls-tree', childTree], this.cwd); + const childListing = gitExecMaybeMissing(['ls-tree', childTree], this.cwd); if (!childListing || childListing.length === 0) { - // Remove the empty directory from the parent tree - const listing = gitExec(['ls-tree', baseTree], this.cwd) ?? ''; + const listing = gitExecMaybeMissing(['ls-tree', baseTree], this.cwd) ?? ''; const lines = listing.split('\n').filter(Boolean); const filtered = lines.filter((line) => { const match = line.match(/^(\d+)\s+(blob|tree)\s+([a-f0-9]+)\t(.+)$/); return !(match && match[4] === dir); }); try { - return gitExecWithInput(['mktree'], filtered.length > 0 ? filtered.join('\n') + '\n' : '', this.cwd); - } catch { throw new Error(`orphan backend: failed to prune empty directory ${dir}`); } + return gitExecWithInputAndRetry(['mktree'], this.cwd, filtered.length > 0 ? filtered.join('\n') + '\n' : ''); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`orphan backend: failed to prune empty directory ${dir} — ${msg}`); + } } return this.replaceEntry(baseTree, dir!, '040000', 'tree', childTree); } @@ -342,14 +748,14 @@ export class OrphanBranchBackend implements StateBackend { if (subTreeHash) { childTree = this.updateTree(subTreeHash, rest, blobHash); } else { - const emptyTree = gitExecWithInput(['mktree'], '', this.cwd); + const emptyTree = gitExecWithInputAndRetry(['mktree'], this.cwd, ''); childTree = this.updateTree(emptyTree, rest, blobHash); } return this.replaceEntry(baseTree, dir!, '040000', 'tree', childTree); } private getSubtreeHash(treeHash: string, name: string): string | null { - const listing = gitExec(['ls-tree', treeHash], this.cwd); + const listing = gitExecMaybeMissing(['ls-tree', treeHash], this.cwd); if (!listing) return null; for (const line of listing.split('\n')) { const match = line.match(/^(\d+)\s+(blob|tree)\s+([a-f0-9]+)\t(.+)$/); @@ -359,7 +765,7 @@ export class OrphanBranchBackend implements StateBackend { } private replaceEntry(treeHash: string, name: string, mode: string, type: string, hash: string): string { - const listing = gitExec(['ls-tree', treeHash], this.cwd) ?? ''; + const listing = gitExecMaybeMissing(['ls-tree', treeHash], this.cwd) ?? ''; const lines = listing.split('\n').filter(Boolean); const filtered = lines.filter((line) => { const match = line.match(/^(\d+)\s+(blob|tree)\s+([a-f0-9]+)\t(.+)$/); @@ -367,8 +773,11 @@ export class OrphanBranchBackend implements StateBackend { }); filtered.push(`${mode} ${type} ${hash}\t${name}`); try { - return gitExecWithInput(['mktree'], filtered.join('\n') + '\n', this.cwd); - } catch { throw new Error(`orphan backend: failed to create tree with entry ${name}`); } + return gitExecWithInputAndRetry(['mktree'], this.cwd, filtered.join('\n') + '\n'); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`orphan backend: failed to create tree with entry ${name} — ${msg}`); + } } } @@ -456,33 +865,68 @@ export class StateBackendStorageAdapter implements StorageProvider { /** Convert absolute path to relative path for the backend. */ private toRelative(filePath: string): string { - const normalized = filePath.replace(/\\/g, '/'); - const squadNorm = this.squadDir.replace(/\\/g, '/'); - if (normalized.startsWith(squadNorm + '/')) { - return normalized.slice(squadNorm.length + 1); + // Use path.resolve() so drive-letter casing differences on Windows are + // normalised before comparison, preventing corrupt git-notes keys. + const resolvedFile = path.resolve(filePath); + const resolvedSquad = path.resolve(this.squadDir); + + const isWindows = process.platform === 'win32'; + const fileCmp = isWindows ? resolvedFile.toLowerCase() : resolvedFile; + const squadCmp = isWindows ? resolvedSquad.toLowerCase() : resolvedSquad; + + const prefix = squadCmp.endsWith(path.sep) ? squadCmp : squadCmp + path.sep; + if (fileCmp.startsWith(prefix)) { + return resolvedFile.slice(resolvedSquad.length + 1).replace(/\\/g, '/'); + } + if (fileCmp === squadCmp) { + return '.'; } - if (normalized.startsWith(squadNorm)) { - return normalized.slice(squadNorm.length).replace(/^\//, '') || '.'; + // If the path is already relative (no drive letter or leading sep), normalise and return. + if (!path.isAbsolute(filePath)) { + return filePath.replace(/\\/g, '/'); } - // Already relative - return normalized; + // Absolute path that doesn't live under squadDir — this would produce a + // corrupt git-notes key (absolute path leaking into the ref namespace). + throw new Error( + `[squad] toRelative: path is outside squadDir and cannot be used as a state key.\n` + + ` path: ${resolvedFile}\n` + + ` squadDir: ${resolvedSquad}` + ); } } +/** + * Result of promoteNotes — how many notes were moved, archived, or skipped. + */ +export interface PromoteNotesResult { + /** Orphan keys written for notes flagged `promote_to_permanent`. */ + promoted: string[]; + /** Orphan keys written for notes flagged `archive_on_close`. */ + archived: string[]; + /** Count of notes that had neither flag and were left in place. */ + skipped: number; +} + /** * Two-Layer Backend — combines git-notes (commit-scoped annotations) with orphan * branch (permanent state). Reads from orphan for bulk state, writes to both: * - Git notes for commit-scoped "why" annotations (per-agent namespace) * - Orphan branch for permanent state (decisions, histories, logs) * - * Ralph promotes notes with promote_to_permanent after PR merge. + * The notes layer is a real, callable consumer in this backend: call + * {@link TwoLayerBackend.promoteNotes} after a PR merges to move notes flagged + * with `promote_to_permanent` into the orphan store, and copy notes flagged + * with `archive_on_close` into `archive/`. {@link TwoLayerBackend.readNote} + * returns a single note's payload. */ export class TwoLayerBackend implements StateBackend { readonly name = 'two-layer'; - private readonly notes: GitNotesBackend; - private readonly orphan: OrphanBranchBackend; + readonly notes: GitNotesBackend; + readonly orphan: OrphanBranchBackend; + private readonly repoRoot: string; constructor(repoRoot: string) { + this.repoRoot = repoRoot; this.notes = new GitNotesBackend(repoRoot); this.orphan = new OrphanBranchBackend(repoRoot); } @@ -495,7 +939,12 @@ export class TwoLayerBackend implements StateBackend { /** Write to orphan (permanent state) AND git notes (commit-scoped annotation) */ write(key: string, value: string): void { this.orphan.write(key, value); - try { this.notes.write(key, value); } catch { /* notes are best-effort */ } + try { + this.notes.write(key, value); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + console.warn(`[two-layer] notes write failed for ${key}: ${msg}`); + } } list(dir: string): string[] { @@ -508,13 +957,130 @@ export class TwoLayerBackend implements StateBackend { delete(key: string): boolean { const result = this.orphan.delete(key); - try { this.notes.delete(key); } catch { /* best-effort */ } + try { + this.notes.delete(key); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + console.warn(`[two-layer] notes delete failed for ${key}: ${msg}`); + } return result; } append(key: string, value: string): void { this.orphan.append(key, value); - try { this.notes.append(key, value); } catch { /* best-effort */ } + try { + this.notes.append(key, value); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + console.warn(`[two-layer] notes append failed for ${key}: ${msg}`); + } + } + + /** + * Read a single git-notes payload as parsed JSON. + * + * Returns `null` if no note exists on the given commit for the given ref, + * or if the note body is not valid JSON. + */ + readNote(ref: string, commitSha: string): unknown | null { + if (!this.isSafeRef(ref) || !this.isSafeCommitSha(commitSha)) return null; + const raw = gitExecMaybeMissing(['notes', `--ref=${ref}`, 'show', commitSha], this.repoRoot, false); + if (raw === null) return null; + try { return JSON.parse(raw); } catch { return null; } + } + + /** + * Walk all notes attached to commits reachable from HEAD on the given ref + * and act based on their flags: + * + * - `promote_to_permanent: true` — write payload to the orphan layer under + * `promoted//.json` and REMOVE the source note (the note has + * been promoted to permanent state and is no longer needed). + * - `archive_on_close: true` — copy payload to the orphan layer under + * `archive//.json` and KEEP the source note (archive = copy). + * - Otherwise — leave the note alone (ephemeral, not worth promoting). + * + * Notes that fail to parse as JSON are counted as skipped. + */ + promoteNotes(ref: string): PromoteNotesResult { + const result: PromoteNotesResult = { promoted: [], archived: [], skipped: 0 }; + if (!this.isSafeRef(ref)) { + throw new Error(`[two-layer] promoteNotes: unsafe ref '${ref}'`); + } + + const listing = gitExecMaybeMissing(['notes', `--ref=${ref}`, 'list'], this.repoRoot); + if (!listing) return result; + + // git notes list output: " " per line. + const noteCommitPairs: Array<{ commitSha: string }> = []; + for (const line of listing.split('\n')) { + const parts = line.trim().split(/\s+/); + if (parts.length < 2) continue; + const commitSha = parts[1]!; + if (this.isSafeCommitSha(commitSha)) noteCommitPairs.push({ commitSha }); + } + if (noteCommitPairs.length === 0) return result; + + // Reachability filter: only commits reachable from HEAD. + const reachableRaw = gitExecMaybeMissing(['rev-list', 'HEAD'], this.repoRoot); + if (!reachableRaw) return result; + const reachable = new Set(reachableRaw.split('\n').map((s) => s.trim()).filter(Boolean)); + + const refKeySegment = this.sanitizeRefForKey(ref); + + for (const { commitSha } of noteCommitPairs) { + if (!reachable.has(commitSha)) continue; + + const raw = gitExecMaybeMissing(['notes', `--ref=${ref}`, 'show', commitSha], this.repoRoot, false); + if (raw === null) continue; + + let payload: unknown; + try { payload = JSON.parse(raw); } catch { result.skipped++; continue; } + + const flags = payload as { promote_to_permanent?: unknown; archive_on_close?: unknown }; + const shouldPromote = flags?.promote_to_permanent === true; + const shouldArchive = flags?.archive_on_close === true; + + if (!shouldPromote && !shouldArchive) { result.skipped++; continue; } + + // Stringify payload deterministically (2-space indent matches existing pattern). + const body = JSON.stringify(payload, null, 2); + + if (shouldPromote) { + const key = `promoted/${refKeySegment}/${commitSha}.json`; + this.orphan.write(key, body); + try { + gitExecOrThrow(['notes', `--ref=${ref}`, 'remove', commitSha], this.repoRoot); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + console.warn(`[two-layer] promoteNotes: removed-source failed for ${commitSha} on ${ref}: ${msg}`); + } + result.promoted.push(key); + } + + if (shouldArchive) { + const key = `archive/${refKeySegment}/${commitSha}.json`; + this.orphan.write(key, body); + result.archived.push(key); + } + } + + return result; + } + + /** True for refs that look like `squad/` — alphanumerics, dash, underscore, slash. */ + private isSafeRef(ref: string): boolean { + return /^[A-Za-z0-9_\-./]+$/.test(ref) && !ref.includes('..'); + } + + /** True for SHA-1 hex (40 chars) or SHA-256 hex (64 chars). */ + private isSafeCommitSha(sha: string): boolean { + return /^[a-f0-9]{40}$|^[a-f0-9]{64}$/.test(sha); + } + + /** Pass the ref through as path segments; normalizeKey will validate each. */ + private sanitizeRefForKey(ref: string): string { + return ref.split('/').filter(Boolean).join('/'); } } @@ -531,30 +1097,77 @@ export function resolveStateBackend(squadDir: string, repoRoot: string, cliOverr configBackend = normalizeBackendType(parsed['stateBackend']); } } - } catch { /* fall through */ } + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + console.warn(`⚠️ Failed to read state backend config from ${path.join(squadDir, 'config.json')}: ${msg}`); + } const explicitBackend = cliOverride !== undefined || configBackend !== undefined; const chosen = normalizeBackendType(cliOverride ?? configBackend ?? 'local'); try { return createBackend(chosen, squadDir, repoRoot); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - if (explicitBackend && chosen !== 'local') { - throw new Error(`State backend '${chosen}' failed: ${msg}`); - } - console.warn(`Warning: State backend '${chosen}' failed: ${msg}. Falling back to 'local'.`); + // Always fall back to local with a warning — a broken backend should not + // prevent Squad from starting. Operators can fix config without losing work. + console.warn(`Warning: State backend '${chosen}' failed${explicitBackend ? ' (explicit)' : ''}: ${msg}. Falling back to 'local'.`); return new WorktreeBackend(squadDir); } } +/** + * Read-only health check for a state backend. + * Verifies the backend is accessible without mutating state. + * + * For {@link TwoLayerBackend}, both layers are probed independently — the + * notes layer can fail (corrupt notes ref, missing commits) even when the + * orphan layer is healthy, and we surface that explicitly. + */ +export function verifyStateBackend(backend: StateBackend): { ok: boolean; error?: string } { + try { + backend.list(''); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + return { ok: false, error: `Backend '${backend.name}' verification failed: ${msg}` }; + } + + if (backend instanceof TwoLayerBackend) { + try { + backend.notes.list(''); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + return { ok: false, error: `Backend '${backend.name}' notes layer unhealthy: ${msg}` }; + } + } + + return { ok: true }; +} + function isValidBackendType(value: string): value is StateBackendType { return ['local', 'worktree', 'external', 'git-notes', 'orphan', 'two-layer'].includes(value); } // Note: 'worktree' and 'git-notes' are accepted for backward compatibility but normalized away +// One-shot flag: warn once per process so repeated resolveStateBackend() calls +// (e.g. multiple agent startups in the same process) don't spam the console. +let _warnedGitNotesMigration = false; + /** Normalize legacy aliases to canonical backend type names. */ function normalizeBackendType(type: string): StateBackendType { if (type === 'worktree') return 'local'; - if (type === 'git-notes') return 'two-layer'; // standalone git-notes removed; migrate to two-layer + if (type === 'git-notes') { + if (!_warnedGitNotesMigration) { + _warnedGitNotesMigration = true; + console.warn( + "[squad] State backend 'git-notes' is deprecated and has been removed. " + + "Your config is being silently migrated to 'two-layer', which creates a " + + "'squad-state' orphan branch in your repository. " + + "To suppress this warning, update .squad/config.json: " + + "set \"stateBackend\": \"two-layer\". " + + "See https://github.com/bradygaster/squad/blob/dev/docs/state-backends.md for upgrade instructions." + ); + } + return 'two-layer'; + } return type as StateBackendType; } @@ -567,11 +1180,19 @@ function createBackend(type: StateBackendType, squadDir: string, repoRoot: strin case 'two-layer': requireGitRepository(repoRoot); return new TwoLayerBackend(repoRoot); - case 'external': return new WorktreeBackend(squadDir); // Stub — PR #797 + case 'external': { + console.warn(`⚠️ State backend 'external' is a stub (PR #797). Using 'local' backend.`); + return new WorktreeBackend(squadDir); + } default: throw new Error(`Unknown state backend type: ${type}`); } } function requireGitRepository(repoRoot: string): void { gitExecOrThrow(['rev-parse', '--git-dir'], repoRoot); +} + +/** @internal Reset the one-shot git-notes migration warn flag. Only for use in tests. */ +export function _resetGitNotesMigrationWarnForTesting(): void { + _warnedGitNotesMigration = false; } \ No newline at end of file diff --git a/packages/squad-sdk/src/tools/index.ts b/packages/squad-sdk/src/tools/index.ts index 60545eaa4..ad802320f 100644 --- a/packages/squad-sdk/src/tools/index.ts +++ b/packages/squad-sdk/src/tools/index.ts @@ -675,6 +675,14 @@ export class ToolRegistry { required: ['key', 'content'], }, handler: async (args) => { + if ((args as unknown as Record)['content'] == null || + typeof (args as unknown as Record)['content'] !== 'string') { + return { + textResultForLlm: 'Failed to write state: content is required and must be a string', + resultType: 'failure' as const, + error: 'content is required', + }; + } try { const key = normalizeStateToolKey(args.key); validateMutableStateToolKey(key); @@ -706,6 +714,14 @@ export class ToolRegistry { required: ['key', 'content'], }, handler: async (args) => { + if ((args as unknown as Record)['content'] == null || + typeof (args as unknown as Record)['content'] !== 'string') { + return { + textResultForLlm: 'Failed to append state: content is required and must be a string', + resultType: 'failure' as const, + error: 'content is required', + }; + } try { const key = normalizeStateToolKey(args.key); validateMutableStateToolKey(key); diff --git a/packages/squad-sdk/templates/after-agent-reference.md b/packages/squad-sdk/templates/after-agent-reference.md index b3c4d709b..e94a635cd 100644 --- a/packages/squad-sdk/templates/after-agent-reference.md +++ b/packages/squad-sdk/templates/after-agent-reference.md @@ -47,8 +47,8 @@ prompt: | 2. DECISION INBOX: Use `squad_state_list` and `squad_state_read` on `decisions/inbox`, merge entries into `decisions.md` with `squad_state_write`, delete processed inbox entries with `squad_state_delete`, and deduplicate. - 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use ISO 8601 UTC timestamp. - 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use ISO 8601 UTC timestamp. + 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use ISO 8601 UTC timestamp. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms (e.g. `2026-06-02T21-15-30Z`). + 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use ISO 8601 UTC timestamp. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms. 5. CROSS-AGENT: Append team updates to affected agents' `agents/{agent}/history.md` with `squad_state_append`. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. 7. HEALTH REPORT: Log decisions.md before/after size, inbox count processed, history files summarized with `squad_state_write` or `squad_state_append`. diff --git a/packages/squad-sdk/templates/scribe-charter.md b/packages/squad-sdk/templates/scribe-charter.md index da6ddfe6a..d335e92c3 100644 --- a/packages/squad-sdk/templates/scribe-charter.md +++ b/packages/squad-sdk/templates/scribe-charter.md @@ -28,7 +28,7 @@ After every substantial work session: -1. **Log the session** to `log/{timestamp}-{topic}.md` with `squad_state_write`: +1. **Log the session** to `log/{timestamp}-{topic}.md` with `squad_state_write` (replace `:` with `-` in `{timestamp}` so the filename is valid on all platforms, e.g. `2026-06-02T21-15-30Z`): - Who worked - What was done - Decisions made diff --git a/packages/squad-sdk/templates/squad.agent.md.template b/packages/squad-sdk/templates/squad.agent.md.template index 99e696794..9c71cacce 100644 --- a/packages/squad-sdk/templates/squad.agent.md.template +++ b/packages/squad-sdk/templates/squad.agent.md.template @@ -128,7 +128,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Resolve `CURRENT_DATETIME` once from the `` value in your system context. Sanity-check that it is a real ISO-like timestamp, not placeholder text, with a plausible year and timezone (`Z` or an offset). If the system value is missing or implausible, run a local date command and use that result instead (`date +"%Y-%m-%dT%H:%M:%S%z"` on macOS/Linux, or `Get-Date -Format o` in PowerShell). Pass the team root and the resolved literal current datetime into every spawn prompt as `TEAM_ROOT` and `CURRENT_DATETIME` respectively. Never pass placeholder text for `CURRENT_DATETIME`. Pass the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. -**Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"worktree"` (default), `"git-notes"`, `"orphan"`, `"two-layer"`. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. +**Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"local"` (default), `"orphan"`, `"two-layer"`. Legacy alias: `"worktree"` maps to `"local"`. Deprecated: `"git-notes"` maps to `"two-layer"` with a deprecation warning. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. **⚡ Context caching:** After the first message in a session, `team.md`, `routing.md`, and `registry.json` are already in your context. Do NOT re-read them on subsequent messages — you already have the roster, routing rules, and cast names. Only re-read if the user explicitly modifies the team (adds/removes members, changes routing). @@ -607,8 +607,8 @@ prompt: | 0b. PRE-CHECK: Read `decisions.md` and list `decisions/inbox` with state tools. Record measurements. 1. DECISIONS ARCHIVE [HARD GATE]: If decisions.md >= 20480 bytes, archive entries older than 30 days NOW. If >= 51200 bytes, archive entries older than 7 days. Do not skip this step. 2. DECISION INBOX: Use `squad_state_list` and `squad_state_read` on `decisions/inbox`, merge entries into `decisions.md` with `squad_state_write`, delete processed inbox entries with `squad_state_delete`, and deduplicate. - 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use the literal CURRENT_DATETIME value. - 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use the literal CURRENT_DATETIME value. + 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use the literal CURRENT_DATETIME value. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms (e.g. `2026-06-02T21-15-30Z`). + 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use the literal CURRENT_DATETIME value. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms. 5. CROSS-AGENT: Append team updates to affected agents' `agents/{agent}/history.md` with `squad_state_append`. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. 7. GIT COMMIT: Do not commit mutable squad state. If non-state repo files changed, report them for coordinator handling. diff --git a/samples/knock-knock/index.ts b/samples/knock-knock/index.ts index ceba4d8a5..333a08a9c 100644 --- a/samples/knock-knock/index.ts +++ b/samples/knock-knock/index.ts @@ -86,7 +86,7 @@ async function main(): Promise { const session = await client.createSession({ streaming: true, systemMessage: { mode: 'append', content: agent.systemPrompt }, - onPermissionRequest: () => ({ kind: 'approved' }), + onPermissionRequest: () => ({ kind: 'approve-once' }), }); agent.sessionId = session.sessionId; pipeline.attachToSession(session.sessionId); @@ -153,7 +153,7 @@ async function sendAndCapture( pipeline.markMessageStart(sessionId); const session = await client.resumeSession(sessionId, { - onPermissionRequest: () => ({ kind: 'approved' }), + onPermissionRequest: () => ({ kind: 'approve-once' }), }); const handler = (event: { type: string; [key: string]: unknown }) => { diff --git a/templates/after-agent-reference.md b/templates/after-agent-reference.md index b3c4d709b..e94a635cd 100644 --- a/templates/after-agent-reference.md +++ b/templates/after-agent-reference.md @@ -47,8 +47,8 @@ prompt: | 2. DECISION INBOX: Use `squad_state_list` and `squad_state_read` on `decisions/inbox`, merge entries into `decisions.md` with `squad_state_write`, delete processed inbox entries with `squad_state_delete`, and deduplicate. - 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use ISO 8601 UTC timestamp. - 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use ISO 8601 UTC timestamp. + 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use ISO 8601 UTC timestamp. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms (e.g. `2026-06-02T21-15-30Z`). + 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use ISO 8601 UTC timestamp. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms. 5. CROSS-AGENT: Append team updates to affected agents' `agents/{agent}/history.md` with `squad_state_append`. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. 7. HEALTH REPORT: Log decisions.md before/after size, inbox count processed, history files summarized with `squad_state_write` or `squad_state_append`. diff --git a/templates/scribe-charter.md b/templates/scribe-charter.md index da6ddfe6a..d335e92c3 100644 --- a/templates/scribe-charter.md +++ b/templates/scribe-charter.md @@ -28,7 +28,7 @@ After every substantial work session: -1. **Log the session** to `log/{timestamp}-{topic}.md` with `squad_state_write`: +1. **Log the session** to `log/{timestamp}-{topic}.md` with `squad_state_write` (replace `:` with `-` in `{timestamp}` so the filename is valid on all platforms, e.g. `2026-06-02T21-15-30Z`): - Who worked - What was done - Decisions made diff --git a/templates/squad.agent.md.template b/templates/squad.agent.md.template index 99e696794..9c71cacce 100644 --- a/templates/squad.agent.md.template +++ b/templates/squad.agent.md.template @@ -128,7 +128,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Resolve `CURRENT_DATETIME` once from the `` value in your system context. Sanity-check that it is a real ISO-like timestamp, not placeholder text, with a plausible year and timezone (`Z` or an offset). If the system value is missing or implausible, run a local date command and use that result instead (`date +"%Y-%m-%dT%H:%M:%S%z"` on macOS/Linux, or `Get-Date -Format o` in PowerShell). Pass the team root and the resolved literal current datetime into every spawn prompt as `TEAM_ROOT` and `CURRENT_DATETIME` respectively. Never pass placeholder text for `CURRENT_DATETIME`. Pass the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. -**Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"worktree"` (default), `"git-notes"`, `"orphan"`, `"two-layer"`. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. +**Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"local"` (default), `"orphan"`, `"two-layer"`. Legacy alias: `"worktree"` maps to `"local"`. Deprecated: `"git-notes"` maps to `"two-layer"` with a deprecation warning. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. **⚡ Context caching:** After the first message in a session, `team.md`, `routing.md`, and `registry.json` are already in your context. Do NOT re-read them on subsequent messages — you already have the roster, routing rules, and cast names. Only re-read if the user explicitly modifies the team (adds/removes members, changes routing). @@ -607,8 +607,8 @@ prompt: | 0b. PRE-CHECK: Read `decisions.md` and list `decisions/inbox` with state tools. Record measurements. 1. DECISIONS ARCHIVE [HARD GATE]: If decisions.md >= 20480 bytes, archive entries older than 30 days NOW. If >= 51200 bytes, archive entries older than 7 days. Do not skip this step. 2. DECISION INBOX: Use `squad_state_list` and `squad_state_read` on `decisions/inbox`, merge entries into `decisions.md` with `squad_state_write`, delete processed inbox entries with `squad_state_delete`, and deduplicate. - 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use the literal CURRENT_DATETIME value. - 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use the literal CURRENT_DATETIME value. + 3. ORCHESTRATION LOG: Write `orchestration-log/{timestamp}-{agent}.md` with `squad_state_write` per agent. Use the literal CURRENT_DATETIME value. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms (e.g. `2026-06-02T21-15-30Z`). + 4. SESSION LOG: Write `log/{timestamp}-{topic}.md` with `squad_state_write`. Brief. Use the literal CURRENT_DATETIME value. Replace `:` with `-` in `{timestamp}` so filenames are valid on all platforms. 5. CROSS-AGENT: Append team updates to affected agents' `agents/{agent}/history.md` with `squad_state_append`. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. 7. GIT COMMIT: Do not commit mutable squad state. If non-state repo files changed, report them for coordinator handling. diff --git a/test/adapter-client.test.ts b/test/adapter-client.test.ts index b2efe2415..255a9d369 100644 --- a/test/adapter-client.test.ts +++ b/test/adapter-client.test.ts @@ -286,6 +286,18 @@ describe('SquadClient — Auto-Reconnection', () => { await expect(client.createSession()).rejects.toThrow('ECONNREFUSED'); }); + it('should provide approve-once guidance for permission handler errors', async () => { + const client = new SquadClient({ autoReconnect: false }); + await client.connect(); + + const MockedCopilotClient = CopilotClient as unknown as ReturnType; + const instance = MockedCopilotClient.mock.results[0].value; + + instance.createSession.mockRejectedValue(new Error('onPermissionRequest is required')); + + await expect(client.createSession()).rejects.toThrow('kind: "approve-once"'); + }); + it('should not auto-reconnect after manual disconnect', async () => { const client = new SquadClient({ autoReconnect: true, autoStart: false }); await client.connect(); diff --git a/test/cli-command-wiring.test.ts b/test/cli-command-wiring.test.ts index 7139988ca..0f087d0a0 100644 --- a/test/cli-command-wiring.test.ts +++ b/test/cli-command-wiring.test.ts @@ -15,8 +15,9 @@ import { join, basename } from 'node:path'; const COMMANDS_DIR = join(process.cwd(), 'packages', 'squad-cli', 'src', 'cli', 'commands'); const CLI_ENTRY = join(process.cwd(), 'packages', 'squad-cli', 'src', 'cli-entry.ts'); -// sync.ts is internal — not exposed as a user-facing command (absorbed into upgrade flow). -const KNOWN_UNWIRED = new Set(['sync']); +// All commands in commands/ are wired into cli-entry.ts at present. +// Add commands here if they are intentionally internal-only. +const KNOWN_UNWIRED = new Set([]); describe('CLI command wiring regression (issues #224, #236, #237)', () => { const commandFiles = readdirSync(COMMANDS_DIR) diff --git a/test/cli/doctor.test.ts b/test/cli/doctor.test.ts index b172779eb..7a6a73c33 100644 --- a/test/cli/doctor.test.ts +++ b/test/cli/doctor.test.ts @@ -9,9 +9,10 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdir, rm, writeFile } from 'fs/promises'; import { join } from 'path'; -import { existsSync } from 'fs'; +import { existsSync, mkdirSync } from 'fs'; +import { execFileSync } from 'child_process'; import { randomBytes } from 'crypto'; -import { runDoctor, getDoctorMode, checkNodeVersion } from '@bradygaster/squad-cli/commands/doctor'; +import { runDoctor, getDoctorMode, checkNodeVersion, checkGitSyncHooks } from '@bradygaster/squad-cli/commands/doctor'; import type { DoctorCheck } from '@bradygaster/squad-cli/commands/doctor'; const TEST_ROOT = join(process.cwd(), `.test-doctor-${randomBytes(4).toString('hex')}`); @@ -291,4 +292,139 @@ describe('squad doctor', () => { expect(agentMdCheck?.status).toBe('fail'); expect(agentMdCheck?.message).toContain('squad upgrade'); }); + + // ── #1185 — git sync hooks check for two-layer / orphan backends ── + + it('does not include hook check when stateBackend is absent', async () => { + await scaffold(TEST_ROOT); + const checks = await runDoctor(TEST_ROOT); + const hookCheck = checks.find((c: DoctorCheck) => c.name === 'git sync hooks installed'); + expect(hookCheck).toBeUndefined(); + }); + + it('does not include hook check when stateBackend=local', async () => { + await scaffold(TEST_ROOT); + await writeFile(join(TEST_ROOT, '.squad', 'config.json'), JSON.stringify({ stateBackend: 'local' })); + const checks = await runDoctor(TEST_ROOT); + const hookCheck = checks.find((c: DoctorCheck) => c.name === 'git sync hooks installed'); + expect(hookCheck).toBeUndefined(); + }); + it('reports FAIL when stateBackend=two-layer and squad hooks are missing', async () => { + const squadDir = join(TEST_ROOT, '.squad'); + await mkdir(squadDir, { recursive: true }); + execFileSync('git', ['init', '--quiet', '-b', 'main'], { cwd: TEST_ROOT }); + await writeFile(join(squadDir, 'config.json'), JSON.stringify({ stateBackend: 'two-layer' })); + await mkdir(join(TEST_ROOT, '.git', 'hooks'), { recursive: true }); + + const result = checkGitSyncHooks(TEST_ROOT, squadDir); + expect(result).toBeDefined(); + expect(result?.status).toBe('fail'); + expect(result?.message).toContain('squad install-hooks'); + }); + + it('reports FAIL when stateBackend=orphan and squad hooks are missing', async () => { + const squadDir = join(TEST_ROOT, '.squad'); + await mkdir(squadDir, { recursive: true }); + execFileSync('git', ['init', '--quiet', '-b', 'main'], { cwd: TEST_ROOT }); + await writeFile(join(squadDir, 'config.json'), JSON.stringify({ stateBackend: 'orphan' })); + await mkdir(join(TEST_ROOT, '.git', 'hooks'), { recursive: true }); + + const result = checkGitSyncHooks(TEST_ROOT, squadDir); + expect(result).toBeDefined(); + expect(result?.status).toBe('fail'); + }); + + it('reports PASS when stateBackend=two-layer and all squad sync hooks are present', async () => { + const squadDir = join(TEST_ROOT, '.squad'); + await mkdir(squadDir, { recursive: true }); + execFileSync('git', ['init', '--quiet', '-b', 'main'], { cwd: TEST_ROOT }); + await writeFile(join(squadDir, 'config.json'), JSON.stringify({ stateBackend: 'two-layer' })); + const hooksDir = join(TEST_ROOT, '.git', 'hooks'); + await mkdir(hooksDir, { recursive: true }); + for (const hookName of ['pre-push', 'post-merge', 'post-rewrite', 'post-checkout']) { + await writeFile( + join(hooksDir, hookName), + `#!/bin/sh\n# --- squad-sync-hook ---\n# squad sync hook\n`, + ); + } + + const result = checkGitSyncHooks(TEST_ROOT, squadDir); + expect(result?.status).toBe('pass'); + expect(result?.message).toContain('two-layer'); + }); + + it('checkGitSyncHooks returns FAIL when hook file lacks squad marker', async () => { + const squadDir = join(TEST_ROOT, '.squad'); + const hooksDir = join(TEST_ROOT, '.git', 'hooks'); + await mkdir(squadDir, { recursive: true }); + execFileSync('git', ['init', '--quiet', '-b', 'main'], { cwd: TEST_ROOT }); + await mkdir(hooksDir, { recursive: true }); + await writeFile(join(squadDir, 'config.json'), JSON.stringify({ stateBackend: 'two-layer' })); + for (const hookName of ['pre-push', 'post-merge', 'post-rewrite', 'post-checkout']) { + await writeFile(join(hooksDir, hookName), '#!/bin/sh\necho "no squad marker here"\n'); + } + + const result = checkGitSyncHooks(TEST_ROOT, squadDir); + expect(result).toBeDefined(); + expect(result?.status).toBe('fail'); + expect(result?.message).toContain('pre-push'); + }); +}); + +// ── Finding 2 regression: git rev-parse --git-dir for worktree repos ───────── + +describe('checkGitSyncHooks — git rev-parse --git-dir resolution', () => { + let repoDir: string; + + beforeEach(() => { + repoDir = join(process.cwd(), `.test-doctor-gitdir-${randomBytes(4).toString('hex')}`); + mkdirSync(repoDir, { recursive: true }); + execFileSync('git', ['init', '--quiet', '-b', 'main'], { cwd: repoDir }); + execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoDir }); + execFileSync('git', ['config', 'user.name', 'Squad Test'], { cwd: repoDir }); + }); + + afterEach(async () => { + await rm(repoDir, { recursive: true, force: true }); + }); + + it('reports PASS when hooks are installed in the real git-dir (git rev-parse --git-dir)', async () => { + const squadDir = join(repoDir, '.squad'); + await mkdir(squadDir, { recursive: true }); + await writeFile(join(squadDir, 'config.json'), JSON.stringify({ stateBackend: 'two-layer' })); + + // Install squad hooks in the actual .git/hooks dir (same as git rev-parse --git-dir → '.git') + const hooksDir = join(repoDir, '.git', 'hooks'); + await mkdir(hooksDir, { recursive: true }); + for (const hookName of ['pre-push', 'post-merge', 'post-rewrite', 'post-checkout']) { + await writeFile( + join(hooksDir, hookName), + `#!/bin/sh\n# --- squad-sync-hook ---\n# squad sync hook\n`, + ); + } + + const result = checkGitSyncHooks(repoDir, squadDir); + expect(result?.status).toBe('pass'); + }); + + it('reports FAIL when hooks exist under a fake path but not the real git-dir', async () => { + const squadDir = join(repoDir, '.squad'); + await mkdir(squadDir, { recursive: true }); + await writeFile(join(squadDir, 'config.json'), JSON.stringify({ stateBackend: 'two-layer' })); + + // Write hooks to a fake hooks directory (not where git rev-parse --git-dir would point) + const fakeHooksDir = join(repoDir, 'fake-git', 'hooks'); + await mkdir(fakeHooksDir, { recursive: true }); + for (const hookName of ['pre-push', 'post-merge', 'post-rewrite', 'post-checkout']) { + await writeFile( + join(fakeHooksDir, hookName), + `#!/bin/sh\n# --- squad-sync-hook ---\n`, + ); + } + // Real .git/hooks is empty + await mkdir(join(repoDir, '.git', 'hooks'), { recursive: true }); + + const result = checkGitSyncHooks(repoDir, squadDir); + expect(result?.status).toBe('fail'); + }); }); diff --git a/test/cli/init.test.ts b/test/cli/init.test.ts index 6a8ad1797..30dd5d659 100644 --- a/test/cli/init.test.ts +++ b/test/cli/init.test.ts @@ -13,6 +13,7 @@ import { runInit } from '@bradygaster/squad-cli/core/init'; import { getPackageVersion } from '@bradygaster/squad-cli/core/version'; const TEST_ROOT = join(tmpdir(), `.test-cli-init-${randomBytes(4).toString('hex')}`); +const TEST_HOME = join(tmpdir(), `.test-cli-init-home-${randomBytes(4).toString('hex')}`); describe('CLI: init command', () => { beforeEach(async () => { @@ -20,12 +21,23 @@ describe('CLI: init command', () => { await rm(TEST_ROOT, { recursive: true, force: true }); } await mkdir(TEST_ROOT, { recursive: true }); + if (existsSync(TEST_HOME)) { + await rm(TEST_HOME, { recursive: true, force: true }); + } + await mkdir(TEST_HOME, { recursive: true }); + // iter-7: redirect ~/.copilot/mcp-config.json writes to a temp dir so + // tests don't pollute the developer's real HOME. + process.env.SQUAD_HOME_DIR_OVERRIDE = TEST_HOME; }); afterEach(async () => { + delete process.env.SQUAD_HOME_DIR_OVERRIDE; if (existsSync(TEST_ROOT)) { await rm(TEST_ROOT, { recursive: true, force: true }); } + if (existsSync(TEST_HOME)) { + await rm(TEST_HOME, { recursive: true, force: true }); + } }); it('should create squad.agent.md in .github/agents/', async () => { @@ -84,17 +96,18 @@ describe('CLI: init command', () => { expect(wisdomContent).toContain('Team Wisdom'); }); - it('should create .copilot/mcp-config.json', async () => { + it('should create .copilot/mcp-config.json without squad_state (iter-7: lives in ~/.copilot)', async () => { await runInit(TEST_ROOT); - + const mcpPath = join(TEST_ROOT, '.copilot', 'mcp-config.json'); expect(existsSync(mcpPath)).toBe(true); - + const content = await readFile(mcpPath, 'utf-8'); const config = JSON.parse(content); expect(config).toHaveProperty('mcpServers'); - expect(config.mcpServers).toHaveProperty('squad_state'); - expect(config.mcpServers.squad_state).not.toHaveProperty('env'); + // iter-7: squad_state is now written to ~/.copilot/mcp-config.json and + // tombstoned out of the project file so github/copilot auto-loads it. + expect(config.mcpServers).not.toHaveProperty('squad_state'); expect(content).not.toContain('SQUAD_TEAM_ROOT'); expect(content).not.toContain(TEST_ROOT); }); @@ -110,7 +123,10 @@ describe('CLI: init command', () => { expect(content).toContain('mcp-servers:'); expect(content).toContain(' squad_state:'); expect(content).toContain(' type: local'); - expect(content).toContain(" args: ['-y', '@bradygaster/squad-cli', 'state-mcp']"); + // args may be pinned (`@bradygaster/squad-cli@`) or unpinned + // depending on whether getPackageVersion() resolved a real version at + // test time. Either shape is acceptable here. + expect(content).toMatch(/args:\s*\['-y',\s*'@bradygaster\/squad-cli(@[^']+)?',\s*'state-mcp'\]/); expect(content).toContain(' tools: ["*"]'); const frontmatterEnd = content.indexOf('\n---', 4); expect(frontmatterEnd).toBeGreaterThan(0); diff --git a/test/cli/notes-promote.test.ts b/test/cli/notes-promote.test.ts new file mode 100644 index 000000000..7702cf2fd --- /dev/null +++ b/test/cli/notes-promote.test.ts @@ -0,0 +1,194 @@ +/** + * squad notes promote — CLI test (Round 5, P0.3 A3 production caller). + * + * Verifies the `squad notes promote` command actually invokes + * TwoLayerBackend.promoteNotes against `refs/notes/squad/*` refs in a real + * git repo. Pre-Round 5 the SDK API had zero production callers (commit + * aaec183f). This test guarantees that regression cannot recur silently. + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { runNotesPromote } from '../../packages/squad-cli/src/cli/commands/notes.js'; +import { TwoLayerBackend } from '../../packages/squad-sdk/src/state-backend.js'; + +function mkRepo(): { dir: string; squadDir: string } { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-notes-promote-')); + execFileSync('git', ['init', '--quiet', '-b', 'main'], { cwd: dir }); + execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: dir }); + execFileSync('git', ['config', 'user.name', 'Squad NotesTest'], { cwd: dir }); + fs.writeFileSync(path.join(dir, 'README.md'), '# test\n'); + execFileSync('git', ['add', 'README.md'], { cwd: dir }); + execFileSync('git', ['commit', '-q', '-m', 'init'], { cwd: dir }); + + const squadDir = path.join(dir, '.squad'); + fs.mkdirSync(squadDir, { recursive: true }); + fs.writeFileSync( + path.join(squadDir, 'config.json'), + JSON.stringify({ stateBackend: 'two-layer', teamRoot: '.' }, null, 2), + ); + return { dir, squadDir }; +} + +function cleanup(dir: string): void { + try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best-effort */ } +} + +/** Get current HEAD sha. */ +function headSha(dir: string): string { + return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: dir, encoding: 'utf-8' }).trim(); +} + +/** Attach a JSON note on HEAD under refs/notes/squad/. */ +function addSquadNote(dir: string, agent: string, payload: Record): void { + const ref = `squad/${agent}`; + execFileSync( + 'git', + ['notes', `--ref=${ref}`, 'add', '-f', '-m', JSON.stringify(payload), 'HEAD'], + { cwd: dir }, + ); +} + +/** True if `refs/notes/squad/` has any note on HEAD. */ +function hasNote(dir: string, agent: string): boolean { + try { + execFileSync( + 'git', ['notes', `--ref=squad/${agent}`, 'show', 'HEAD'], + { cwd: dir, stdio: ['pipe', 'pipe', 'pipe'] }, + ); + return true; + } catch { return false; } +} + +describe('squad notes promote', () => { + let dir = ''; + afterEach(() => { if (dir) cleanup(dir); dir = ''; }); + + it('is a no-op when stateBackend is not two-layer', { timeout: 30_000 }, async () => { + const repo = mkRepo(); + dir = repo.dir; + // Downgrade config to worktree. + fs.writeFileSync( + path.join(repo.squadDir, 'config.json'), + JSON.stringify({ stateBackend: 'worktree', teamRoot: '.' }, null, 2), + ); + const code = await runNotesPromote(dir, []); + expect(code).toBe(0); + }); + + it('returns 0 with no squad notes refs present', { timeout: 30_000 }, async () => { + const repo = mkRepo(); + dir = repo.dir; + const code = await runNotesPromote(dir, []); + expect(code).toBe(0); + }); + + it('promotes flagged notes to permanent orphan storage and removes the source note', { timeout: 30_000 }, async () => { + const repo = mkRepo(); + dir = repo.dir; + addSquadNote(dir, 'picard', { + promote_to_permanent: true, + decision: 'D1 — adopt two-layer backend', + }); + expect(hasNote(dir, 'picard')).toBe(true); + + const code = await runNotesPromote(dir, []); + expect(code).toBe(0); + + // Source note removed. + expect(hasNote(dir, 'picard')).toBe(false); + + // Permanent copy written to orphan branch under promoted/. + const sha = headSha(dir); + const promotedPath = `promoted/squad/picard/${sha}.json`; + const onBranch = execFileSync( + 'git', ['show', `refs/heads/squad-state:${promotedPath}`], + { cwd: dir, encoding: 'utf-8' }, + ); + expect(onBranch).toContain('D1 — adopt two-layer backend'); + }); + + it('archives flagged notes but keeps the source note', { timeout: 30_000 }, async () => { + const repo = mkRepo(); + dir = repo.dir; + addSquadNote(dir, 'data', { + archive_on_close: true, + observation: 'B1 ENOBUFS edge case', + }); + + const code = await runNotesPromote(dir, []); + expect(code).toBe(0); + + expect(hasNote(dir, 'data')).toBe(true); // archive = copy + + const sha = headSha(dir); + const archivedPath = `archive/squad/data/${sha}.json`; + const onBranch = execFileSync( + 'git', ['show', `refs/heads/squad-state:${archivedPath}`], + { cwd: dir, encoding: 'utf-8' }, + ); + expect(onBranch).toContain('B1 ENOBUFS edge case'); + }); + + it('is idempotent — second run finds nothing to promote', { timeout: 30_000 }, async () => { + const repo = mkRepo(); + dir = repo.dir; + addSquadNote(dir, 'picard', { promote_to_permanent: true, decision: 'D2' }); + + expect(await runNotesPromote(dir, [])).toBe(0); + // Second invocation must succeed and be a no-op. + expect(await runNotesPromote(dir, [])).toBe(0); + }); + + it('--ref restricts promotion to a single ref', { timeout: 30_000 }, async () => { + const repo = mkRepo(); + dir = repo.dir; + addSquadNote(dir, 'picard', { promote_to_permanent: true, decision: 'pic' }); + addSquadNote(dir, 'data', { promote_to_permanent: true, decision: 'dat' }); + + const code = await runNotesPromote(dir, ['--ref', 'squad/picard']); + expect(code).toBe(0); + + // picard's note was promoted, data's was left alone. + expect(hasNote(dir, 'picard')).toBe(false); + expect(hasNote(dir, 'data')).toBe(true); + }); + + it('--dry-run reports work without writing or removing notes', { timeout: 30_000 }, async () => { + const repo = mkRepo(); + dir = repo.dir; + addSquadNote(dir, 'picard', { promote_to_permanent: true, decision: 'D3' }); + + const code = await runNotesPromote(dir, ['--dry-run']); + expect(code).toBe(0); + + // Note still in place. + expect(hasNote(dir, 'picard')).toBe(true); + // Orphan branch must not contain a promoted entry yet. + const sha = headSha(dir); + expect(() => execFileSync( + 'git', ['show', `refs/heads/squad-state:promoted/squad/picard/${sha}.json`], + { cwd: dir, stdio: ['pipe', 'pipe', 'pipe'] }, + )).toThrow(); + }); + + it('directly drives TwoLayerBackend.promoteNotes (smoke check)', { timeout: 30_000 }, async () => { + // Belt-and-braces: even bypassing the CLI surface, the SDK API behaves as advertised. + const repo = mkRepo(); + dir = repo.dir; + addSquadNote(dir, 'picard', { promote_to_permanent: true, x: 1 }); + addSquadNote(dir, 'data', { archive_on_close: true, y: 2 }); + + const backend = new TwoLayerBackend(dir); + const r1 = backend.promoteNotes('squad/picard'); + expect(r1.promoted.length).toBe(1); + expect(r1.archived.length).toBe(0); + + const r2 = backend.promoteNotes('squad/data'); + expect(r2.promoted.length).toBe(0); + expect(r2.archived.length).toBe(1); + }); +}); diff --git a/test/cli/upgrade.test.ts b/test/cli/upgrade.test.ts index 2f036b445..7fded53c5 100644 --- a/test/cli/upgrade.test.ts +++ b/test/cli/upgrade.test.ts @@ -14,6 +14,7 @@ import { runUpgrade, ensureGitattributes, ensureGitignore, ensureDirectories, en import { getPackageVersion } from '@bradygaster/squad-cli/core/version'; const TEST_ROOT = join(tmpdir(), `.test-cli-upgrade-${randomBytes(4).toString('hex')}`); +const TEST_HOME = join(tmpdir(), `.test-cli-upgrade-home-${randomBytes(4).toString('hex')}`); describe('CLI: upgrade command', () => { beforeEach(async () => { @@ -21,15 +22,26 @@ describe('CLI: upgrade command', () => { await rm(TEST_ROOT, { recursive: true, force: true }); } await mkdir(TEST_ROOT, { recursive: true }); - + if (existsSync(TEST_HOME)) { + await rm(TEST_HOME, { recursive: true, force: true }); + } + await mkdir(TEST_HOME, { recursive: true }); + // iter-7: redirect ~/.copilot/mcp-config.json writes to a temp dir so + // tests don't pollute the developer's real HOME. + process.env.SQUAD_HOME_DIR_OVERRIDE = TEST_HOME; + // Initialize a squad await runInit(TEST_ROOT); }); afterEach(async () => { + delete process.env.SQUAD_HOME_DIR_OVERRIDE; if (existsSync(TEST_ROOT)) { await rm(TEST_ROOT, { recursive: true, force: true }); } + if (existsSync(TEST_HOME)) { + await rm(TEST_HOME, { recursive: true, force: true }); + } }); it('should upgrade squad.agent.md to current version', async () => { @@ -156,7 +168,10 @@ describe('CLI: upgrade command', () => { const upgraded = await readFile(agentPath, 'utf-8'); expect(upgraded).toContain('mcp-servers:'); expect(upgraded).toContain(' squad_state:'); - expect(upgraded).toContain(" args: ['-y', '@bradygaster/squad-cli', 'state-mcp']"); + // After MCP-BRIDGE-BROKEN fix the args MUST pin the CLI version so npx + // does not silently resolve to the npm `latest` dist-tag (which lacks the + // state-mcp command). Match a regex rather than literal version. + expect(upgraded).toMatch(/args: \['-y', '@bradygaster\/squad-cli@[^']+', 'state-mcp'\]/); expect(upgraded).toContain(' EXAMPLE-github:'); expect(upgraded).toContain(" args: ['-y', '@anthropic/github-mcp-server']"); expect(upgraded).toContain(' GITHUB_TOKEN: ${GITHUB_TOKEN}'); diff --git a/test/copilot-invocation-mcp-wrap.test.ts b/test/copilot-invocation-mcp-wrap.test.ts new file mode 100644 index 000000000..a5013957b --- /dev/null +++ b/test/copilot-invocation-mcp-wrap.test.ts @@ -0,0 +1,67 @@ +/** + * Tests for the centralized copilot invocation helper that injects + * `--additional-mcp-config @` so that Copilot CLI 1.0.58 actually loads + * a project's `.copilot/mcp-config.json` (it ignores that file by default). + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { randomBytes } from 'node:crypto'; +import { + buildAdditionalMcpConfigArgs, + withAdditionalMcpConfig, +} from '../packages/squad-cli/src/cli/core/copilot-invocation.js'; + +describe('copilot-invocation: --additional-mcp-config wrapping', () => { + let workdir: string; + + beforeEach(() => { + workdir = path.join(tmpdir(), `squad-copilot-invocation-${randomBytes(4).toString('hex')}`); + mkdirSync(workdir, { recursive: true }); + }); + + afterEach(() => { + try { rmSync(workdir, { recursive: true, force: true }); } catch { /* ignore */ } + }); + + it('returns no extra args when cmd is not `copilot`', () => { + mkdirSync(path.join(workdir, '.copilot'), { recursive: true }); + writeFileSync(path.join(workdir, '.copilot', 'mcp-config.json'), '{}'); + const args = buildAdditionalMcpConfigArgs('claude', workdir); + expect(args).toEqual([]); + }); + + it('returns no extra args when teamRoot is undefined', () => { + const args = buildAdditionalMcpConfigArgs('copilot', undefined); + expect(args).toEqual([]); + }); + + it('returns no extra args when the project mcp-config.json does not exist', () => { + const args = buildAdditionalMcpConfigArgs('copilot', workdir); + expect(args).toEqual([]); + }); + + it('returns `--additional-mcp-config @` when config exists', () => { + const cfg = path.join(workdir, '.mcp.json'); + writeFileSync(cfg, '{"mcpServers":{}}'); + const args = buildAdditionalMcpConfigArgs('copilot', workdir); + expect(args).toEqual(['--yolo', '--additional-mcp-config', `@${cfg}`]); + }); + + it('withAdditionalMcpConfig prepends the flag to user args when applicable', () => { + const cfg = path.join(workdir, '.mcp.json'); + writeFileSync(cfg, '{}'); + const result = withAdditionalMcpConfig('copilot', ['-p', 'hi'], workdir); + expect(result[0]).toBe('--yolo'); + expect(result[1]).toBe('--additional-mcp-config'); + expect(result[2]).toBe(`@${cfg}`); + expect(result.slice(3)).toEqual(['-p', 'hi']); + }); + + it('withAdditionalMcpConfig is a no-op when injection is not applicable', () => { + const result = withAdditionalMcpConfig('copilot', ['-p', 'hi'], workdir); + expect(result).toEqual(['-p', 'hi']); + }); +}); diff --git a/test/effective-squad-dir.test.ts b/test/effective-squad-dir.test.ts new file mode 100644 index 000000000..60fd590c6 --- /dev/null +++ b/test/effective-squad-dir.test.ts @@ -0,0 +1,137 @@ +/** + * Tests for effective-squad-dir: resolveStateDir() and effectiveSquadDir() + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { mkdirSync, rmSync, existsSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { randomBytes } from 'node:crypto'; +import { resolveStateDir, effectiveSquadDir } from '../packages/squad-cli/src/cli/core/effective-squad-dir.js'; +import { resolveGlobalSquadPath } from '@bradygaster/squad-sdk/resolution'; + +const TMP = join(process.cwd(), `.test-effective-squad-dir-${randomBytes(4).toString('hex')}`); + +// Stub platform env vars so resolveGlobalSquadPath() points inside TMP (not the real user dir) +const origAppData = process.env['APPDATA']; +const origXdgConfig = process.env['XDG_CONFIG_HOME']; +beforeEach(() => { + if (process.platform === 'win32') { + process.env['APPDATA'] = TMP; + } else { + process.env['XDG_CONFIG_HOME'] = TMP; + } +}); +afterEach(() => { + if (process.platform === 'win32') { + if (origAppData === undefined) delete process.env['APPDATA']; + else process.env['APPDATA'] = origAppData; + } else { + if (origXdgConfig === undefined) delete process.env['XDG_CONFIG_HOME']; + else process.env['XDG_CONFIG_HOME'] = origXdgConfig; + } +}); + +function scaffold(...dirs: string[]): void { + for (const d of dirs) { + mkdirSync(join(TMP, d), { recursive: true }); + } +} + +function writeConfig(squadDir: string, config: Record): void { + writeFileSync(join(squadDir, 'config.json'), JSON.stringify(config, null, 2)); +} + +describe('resolveStateDir()', () => { + beforeEach(() => { + if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); + mkdirSync(TMP, { recursive: true }); + }); + + afterEach(() => { + if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); + }); + + it('returns local path when no config.json exists', () => { + scaffold('.squad'); + const squadDir = join(TMP, '.squad'); + expect(resolveStateDir(squadDir)).toBe(squadDir); + }); + + it('returns local path when stateLocation is not external', () => { + scaffold('.squad'); + const squadDir = join(TMP, '.squad'); + writeConfig(squadDir, { version: 1, teamRoot: '.' }); + expect(resolveStateDir(squadDir)).toBe(squadDir); + }); + + it('returns external path when stateLocation is external', () => { + scaffold('.squad'); + const squadDir = join(TMP, '.squad'); + const projectKey = `test-external-${randomBytes(4).toString('hex')}`; + writeConfig(squadDir, { + version: 1, + teamRoot: '.', + projectKey, + stateLocation: 'external', + }); + + const result = resolveStateDir(squadDir); + const globalDir = resolveGlobalSquadPath(); + const expected = join(globalDir, 'projects', projectKey); + expect(result).toBe(expected); + }); + + it('returns local path when stateLocation is external but projectKey is missing', () => { + scaffold('.squad'); + const squadDir = join(TMP, '.squad'); + writeConfig(squadDir, { + version: 1, + teamRoot: '.', + stateLocation: 'external', + }); + expect(resolveStateDir(squadDir)).toBe(squadDir); + }); +}); + +describe('effectiveSquadDir()', () => { + beforeEach(() => { + if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); + mkdirSync(TMP, { recursive: true }); + }); + + afterEach(() => { + if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); + }); + + it('returns local for both when state is not externalized', () => { + scaffold('.squad'); + const { local, stateDir } = effectiveSquadDir(TMP); + expect(local.path).toBe(join(TMP, '.squad')); + expect(stateDir).toBe(join(TMP, '.squad')); + }); + + it('returns external stateDir when state is externalized', () => { + scaffold('.squad'); + const squadDir = join(TMP, '.squad'); + const projectKey = `test-effective-${randomBytes(4).toString('hex')}`; + writeConfig(squadDir, { + version: 1, + teamRoot: '.', + projectKey, + stateLocation: 'external', + }); + + const { local, stateDir } = effectiveSquadDir(TMP); + expect(local.path).toBe(squadDir); + + const globalDir = resolveGlobalSquadPath(); + expect(stateDir).toBe(join(globalDir, 'projects', projectKey)); + }); + + it('preserves SquadDirInfo metadata in local field', () => { + scaffold('.squad'); + const { local } = effectiveSquadDir(TMP); + expect(local.name).toBe('.squad'); + expect(local.isLegacy).toBe(false); + }); +}); diff --git a/test/init-leak-mutable-state.test.ts b/test/init-leak-mutable-state.test.ts new file mode 100644 index 000000000..9646f5a41 --- /dev/null +++ b/test/init-leak-mutable-state.test.ts @@ -0,0 +1,110 @@ +/** + * INSIDER3-INIT-LEAK regression test. + * + * Bug: `squad init --state-backend two-layer|orphan` leaves the freshly-init'd + * mutable state files (decisions.md, agents//history.md) in the working + * tree, where they shadow the squad-state orphan branch and bypass the runtime + * state bridge. The user thinks they have a clean orphan-backed setup; in + * reality the files leaked into the worktree commit graph. + * + * Fix: liftInitMutableStateOntoOrphan() pushes those files onto the squad-state + * branch and unlinks them from the working tree, preserving static config + * (team.md, charters, ceremonies.md, casting/*) which legitimately lives on disk. + * + * Bug evidence: data-3 baseline — `.squad/files/validation/UPGRADE-PATH-BASELINE-INSIDER3-REPORT.md`. + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { + ensureOrphanBranch, + liftInitMutableStateOntoOrphan, +} from '../packages/squad-cli/src/cli/commands/migrate-backend.js'; + +function mkTempRepo(): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-init-leak-')); + execFileSync('git', ['init', '--quiet', '-b', 'main'], { cwd: dir }); + execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: dir }); + execFileSync('git', ['config', 'user.name', 'Squad Init Leak Test'], { cwd: dir }); + // Seed a commit so HEAD exists. + fs.writeFileSync(path.join(dir, 'README.md'), '# t\n'); + execFileSync('git', ['add', '.'], { cwd: dir }); + execFileSync('git', ['commit', '-q', '-m', 'init'], { cwd: dir }); + return dir; +} + +function seedSquadDir(dest: string): void { + const squadDir = path.join(dest, '.squad'); + fs.mkdirSync(squadDir, { recursive: true }); + fs.writeFileSync(path.join(squadDir, 'decisions.md'), '# Squad Decisions\n\n## Active Decisions\n\nNo decisions recorded yet.\n'); + // Static files (must NOT be lifted) + fs.writeFileSync(path.join(squadDir, 'team.md'), '# Squad Team\n'); + fs.writeFileSync(path.join(squadDir, 'ceremonies.md'), '# Ceremonies\n'); + fs.mkdirSync(path.join(squadDir, 'casting'), { recursive: true }); + fs.writeFileSync(path.join(squadDir, 'casting', 'roles.md'), '# Roles\n'); + // Agents — charter (static) + history (mutable) + const aliceDir = path.join(squadDir, 'agents', 'alice'); + const bobDir = path.join(squadDir, 'agents', 'bob'); + fs.mkdirSync(aliceDir, { recursive: true }); + fs.mkdirSync(bobDir, { recursive: true }); + fs.writeFileSync(path.join(aliceDir, 'charter.md'), '# Alice charter\n'); + fs.writeFileSync(path.join(aliceDir, 'history.md'), '# Alice history\n\nFirst entry\n'); + fs.writeFileSync(path.join(bobDir, 'charter.md'), '# Bob charter\n'); + fs.writeFileSync(path.join(bobDir, 'history.md'), '# Bob history\n'); +} + +function readBlobAtBranch(dest: string, branch: string, relPath: string): string { + return execFileSync('git', ['show', `refs/heads/${branch}:${relPath}`], { + cwd: dest, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], + }); +} + +describe('INSIDER3-INIT-LEAK — lift mutable state onto orphan branch after init', () => { + let dest: string; + + beforeEach(() => { dest = mkTempRepo(); seedSquadDir(dest); ensureOrphanBranch(dest); }); + afterEach(() => { fs.rmSync(dest, { recursive: true, force: true }); }); + + it('moves decisions.md + each agent history.md onto squad-state and removes from worktree', () => { + const lifted = liftInitMutableStateOntoOrphan(dest); + + expect(lifted.sort()).toEqual([ + 'agents/alice/history.md', + 'agents/bob/history.md', + 'decisions.md', + ]); + + // Working tree no longer contains them + expect(fs.existsSync(path.join(dest, '.squad', 'decisions.md'))).toBe(false); + expect(fs.existsSync(path.join(dest, '.squad', 'agents', 'alice', 'history.md'))).toBe(false); + expect(fs.existsSync(path.join(dest, '.squad', 'agents', 'bob', 'history.md'))).toBe(false); + + // Squad-state branch DOES contain them with exact original content + expect(readBlobAtBranch(dest, 'squad-state', 'decisions.md')).toContain('# Squad Decisions'); + expect(readBlobAtBranch(dest, 'squad-state', 'agents/alice/history.md')).toContain('First entry'); + expect(readBlobAtBranch(dest, 'squad-state', 'agents/bob/history.md')).toContain('# Bob history'); + }); + + it('preserves static config files (team.md, charters, ceremonies.md, casting/*) on disk', () => { + liftInitMutableStateOntoOrphan(dest); + + // Static files must remain on disk — they're not mutable state. + expect(fs.existsSync(path.join(dest, '.squad', 'team.md'))).toBe(true); + expect(fs.existsSync(path.join(dest, '.squad', 'ceremonies.md'))).toBe(true); + expect(fs.existsSync(path.join(dest, '.squad', 'casting', 'roles.md'))).toBe(true); + expect(fs.existsSync(path.join(dest, '.squad', 'agents', 'alice', 'charter.md'))).toBe(true); + expect(fs.existsSync(path.join(dest, '.squad', 'agents', 'bob', 'charter.md'))).toBe(true); + }); + + it('returns empty when there is no mutable state to lift', () => { + // Remove the mutable files first to simulate a no-op call. + fs.unlinkSync(path.join(dest, '.squad', 'decisions.md')); + fs.unlinkSync(path.join(dest, '.squad', 'agents', 'alice', 'history.md')); + fs.unlinkSync(path.join(dest, '.squad', 'agents', 'bob', 'history.md')); + + expect(liftInitMutableStateOntoOrphan(dest)).toEqual([]); + }); +}); diff --git a/test/install-hooks-wi1.test.ts b/test/install-hooks-wi1.test.ts new file mode 100644 index 000000000..3de96dac3 --- /dev/null +++ b/test/install-hooks-wi1.test.ts @@ -0,0 +1,119 @@ +/** + * WI-1 regression test — verifies that two-layer / orphan backends install + * pre-commit + post-commit hooks (plus the existing sync hooks) and that + * ensureHooksForBackend re-installs hooks if any required one is missing. + * + * Bug evidence: .squad/files/validation/TWOLAYER-BASELINE-INSIDER3-CONSOLIDATED.md + * - Fresh init two-layer installed pre-push / post-merge / post-rewrite / post-checkout + * but NOT pre-commit / post-commit. + * - Upgrade --state-backend two-layer installed zero hooks. + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { + installGitHooks, + ensureHooksForBackend, +} from '../packages/squad-cli/src/cli/commands/install-hooks.js'; + +function mkTempRepo(): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-wi1-')); + execFileSync('git', ['init', '--quiet', '-b', 'main'], { cwd: dir }); + // Required minimum git config for commits / hook installs. + execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: dir }); + execFileSync('git', ['config', 'user.name', 'Squad WI-1 Test'], { cwd: dir }); + fs.mkdirSync(path.join(dir, '.squad'), { recursive: true }); + return dir; +} + +function writeConfig(dir: string, backend: string): void { + fs.writeFileSync( + path.join(dir, '.squad', 'config.json'), + JSON.stringify({ stateBackend: backend }, null, 2), + ); +} + +const REQUIRED_HOOKS = [ + 'pre-push', + 'post-merge', + 'post-rewrite', + 'post-checkout', + 'pre-commit', + 'post-commit', +]; + +describe('WI-1: install-hooks installs commit hooks on two-layer / orphan', () => { + let dir: string; + + beforeEach(() => { + dir = mkTempRepo(); + }); + + afterEach(() => { + try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best-effort */ } + }); + + it('installGitHooks installs ALL required hooks (incl. pre-commit + post-commit) when backend is two-layer', () => { + writeConfig(dir, 'two-layer'); + installGitHooks(dir, { force: false }); + + for (const hook of REQUIRED_HOOKS) { + const p = path.join(dir, '.git', 'hooks', hook); + expect(fs.existsSync(p), `hook ${hook} should exist`).toBe(true); + const content = fs.readFileSync(p, 'utf-8'); + expect(content).toContain('squad-sync-hook'); + } + }); + + it('installGitHooks installs ALL required hooks when backend is orphan', () => { + writeConfig(dir, 'orphan'); + installGitHooks(dir, { force: false }); + + for (const hook of REQUIRED_HOOKS) { + expect(fs.existsSync(path.join(dir, '.git', 'hooks', hook))).toBe(true); + } + }); + + it('installGitHooks skips hook installation for local backend', () => { + writeConfig(dir, 'local'); + installGitHooks(dir, { force: false }); + + for (const hook of REQUIRED_HOOKS) { + expect(fs.existsSync(path.join(dir, '.git', 'hooks', hook))).toBe(false); + } + }); + + it('ensureHooksForBackend reinstalls missing pre-commit / post-commit on existing two-layer repos', () => { + writeConfig(dir, 'two-layer'); + // Simulate an insider.3-era install: only the four sync hooks present, no commit hooks. + const hooksDir = path.join(dir, '.git', 'hooks'); + fs.mkdirSync(hooksDir, { recursive: true }); + for (const h of ['pre-push', 'post-merge', 'post-rewrite', 'post-checkout']) { + fs.writeFileSync( + path.join(hooksDir, h), + '#!/bin/sh\n# --- squad-sync-hook ---\nexit 0\n', + { mode: 0o755 }, + ); + } + + expect(fs.existsSync(path.join(hooksDir, 'pre-commit'))).toBe(false); + expect(fs.existsSync(path.join(hooksDir, 'post-commit'))).toBe(false); + + ensureHooksForBackend(dir); + + expect(fs.existsSync(path.join(hooksDir, 'pre-commit'))).toBe(true); + expect(fs.existsSync(path.join(hooksDir, 'post-commit'))).toBe(true); + }); + + it('pre-commit hook content guards against committing two-layer state into working tree', () => { + writeConfig(dir, 'two-layer'); + installGitHooks(dir, { force: false }); + const preCommit = fs.readFileSync(path.join(dir, '.git', 'hooks', 'pre-commit'), 'utf-8'); + expect(preCommit).toContain('decisions'); + expect(preCommit).toContain('agents'); + expect(preCommit).toContain('history'); + }); +}); diff --git a/test/mcp-root-write.test.ts b/test/mcp-root-write.test.ts new file mode 100644 index 000000000..184e281ee --- /dev/null +++ b/test/mcp-root-write.test.ts @@ -0,0 +1,145 @@ +/** + * iter-8 tests for the repo-root `.mcp.json` squad_state writer + the + * `.copilot/mcp-config.json` tombstone helper. + * + * Validates: + * - Missing `.mcp.json` is created with valid JSON containing exactly + * the desired squad_state entry. + * - Existing user `mcpServers.*` entries are preserved byte-for-byte. + * - Tombstone removes only `squad_state` from `.copilot/mcp-config.json` + * and preserves siblings. + * - Malformed `.mcp.json` is refused (throws) rather than overwritten. + * - Idempotency: re-writing the same spec is a no-op (written === false). + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { + ensureSquadStateMcpInRoot, + tombstoneStaleSquadStateInProjectMcp, + getProjectMcpJsonPath, +} from '../packages/squad-cli/src/cli/core/mcp-root.js'; +import type { SquadStateMcpSpec } from '../packages/squad-cli/src/cli/core/mcp-spec.js'; + +const PINNED_SPEC: SquadStateMcpSpec = { + source: 'pinned', + command: 'npx', + args: ['-y', '@bradygaster/squad-cli@0.9.6-preview.14', 'state-mcp'], +}; + +describe('iter-8 mcp-root: repo-root .mcp.json writer + project tombstone', () => { + let tmpProject: string; + + beforeEach(() => { + tmpProject = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-proj-')); + }); + + afterEach(() => { + fs.rmSync(tmpProject, { recursive: true, force: true }); + }); + + it('creates .mcp.json with the squad_state entry when missing', () => { + const result = ensureSquadStateMcpInRoot(tmpProject, '0.9.6-preview.14', PINNED_SPEC); + expect(result.written).toBe(true); + expect(result.key).toBe('squad_state'); + expect(result.path).toBe(getProjectMcpJsonPath(tmpProject)); + + const parsed = JSON.parse(fs.readFileSync(result.path, 'utf8')); + expect(parsed.mcpServers.squad_state.command).toBe('npx'); + expect(parsed.mcpServers.squad_state.args).toEqual(PINNED_SPEC.args); + expect(parsed.mcpServers.squad_state.tools).toEqual(['*']); + expect(parsed.mcpServers.squad_state.env).toEqual({}); + }); + + it('preserves existing user mcpServers entries', () => { + const cfgPath = getProjectMcpJsonPath(tmpProject); + fs.writeFileSync( + cfgPath, + JSON.stringify( + { + mcpServers: { + github: { command: 'gh-mcp', args: ['--stdio'] }, + 'custom-tool': { command: 'node', args: ['./tool.js'] }, + }, + }, + null, + 2, + ), + ); + + const result = ensureSquadStateMcpInRoot(tmpProject, '0.9.6-preview.14', PINNED_SPEC); + expect(result.written).toBe(true); + + const parsed = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); + expect(parsed.mcpServers.github).toEqual({ command: 'gh-mcp', args: ['--stdio'] }); + expect(parsed.mcpServers['custom-tool']).toEqual({ command: 'node', args: ['./tool.js'] }); + expect(parsed.mcpServers.squad_state.command).toBe('npx'); + }); + + it('refuses to overwrite malformed .mcp.json', () => { + const cfgPath = getProjectMcpJsonPath(tmpProject); + fs.writeFileSync(cfgPath, '{ this is : not json'); + expect(() => + ensureSquadStateMcpInRoot(tmpProject, '0.9.6-preview.14', PINNED_SPEC), + ).toThrow(/Refusing to overwrite malformed/); + + // Original content untouched. + expect(fs.readFileSync(cfgPath, 'utf8')).toBe('{ this is : not json'); + }); + + it('is idempotent — second call with same spec returns written=false', () => { + const first = ensureSquadStateMcpInRoot(tmpProject, '0.9.6-preview.14', PINNED_SPEC); + expect(first.written).toBe(true); + + const second = ensureSquadStateMcpInRoot(tmpProject, '0.9.6-preview.14', PINNED_SPEC); + expect(second.written).toBe(false); + }); + + it('tombstone removes squad_state from .copilot/mcp-config.json while preserving siblings', () => { + const copilotDir = path.join(tmpProject, '.copilot'); + fs.mkdirSync(copilotDir, { recursive: true }); + const cfgPath = path.join(copilotDir, 'mcp-config.json'); + fs.writeFileSync( + cfgPath, + JSON.stringify( + { + mcpServers: { + squad_state: { command: 'old-stale', args: [] }, + github: { command: 'gh-mcp', args: ['--stdio'] }, + }, + }, + null, + 2, + ), + ); + + const result = tombstoneStaleSquadStateInProjectMcp(tmpProject); + expect(result.removed).toBe(true); + expect(result.path).toBe(cfgPath); + + const parsed = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); + expect(parsed.mcpServers.squad_state).toBeUndefined(); + expect(parsed.mcpServers.github).toEqual({ command: 'gh-mcp', args: ['--stdio'] }); + }); + + it('tombstone is a no-op when project mcp-config.json has no squad_state or is missing', () => { + // missing file + const r1 = tombstoneStaleSquadStateInProjectMcp(tmpProject); + expect(r1.removed).toBe(false); + + // present without squad_state + const copilotDir = path.join(tmpProject, '.copilot'); + fs.mkdirSync(copilotDir, { recursive: true }); + const cfgPath = path.join(copilotDir, 'mcp-config.json'); + fs.writeFileSync(cfgPath, JSON.stringify({ mcpServers: { github: { command: 'gh' } } })); + + const r2 = tombstoneStaleSquadStateInProjectMcp(tmpProject); + expect(r2.removed).toBe(false); + + const parsed = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); + expect(parsed.mcpServers.github).toEqual({ command: 'gh' }); + }); +}); diff --git a/test/mcp-spec-init.test.ts b/test/mcp-spec-init.test.ts new file mode 100644 index 000000000..7fa719689 --- /dev/null +++ b/test/mcp-spec-init.test.ts @@ -0,0 +1,126 @@ +/** + * Tests for the mcp-spec helper. + * + * Iter-7 simplified the resolver to 2 tiers: + * 1. Pinned version published on npm → npx -y @ + * 2. Anything else → npx -y @insider + * + * The iter-6 local-install path and the hard-error fallback were deleted; + * smoke data-30/data-32 confirmed `@insider` is always reachable in practice + * and tier-3 never fired. + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { readFileSync } from 'node:fs'; +import path from 'node:path'; + +vi.mock( + '../packages/squad-cli/src/cli/core/npm-registry.js', + () => ({ + isSquadCliVersionPublished: vi.fn(), + }), +); + +import { + resolveSquadStateMcpSpec, + _resetMcpSpecCache, +} from '../packages/squad-cli/src/cli/core/mcp-spec.js'; +import { isSquadCliVersionPublished } from '../packages/squad-cli/src/cli/core/npm-registry.js'; + +const mockIsPublished = vi.mocked(isSquadCliVersionPublished); + +describe('resolveSquadStateMcpSpec (iter-7: 2-tier resolver)', () => { + beforeEach(() => { + mockIsPublished.mockReset(); + _resetMcpSpecCache(); + }); + + it('returns a pinned npx spec when the version is published on npm', async () => { + const spec = await resolveSquadStateMcpSpec('0.9.6-preview.42', { + publishedCheck: async () => true, + }); + expect(spec.source).toBe('pinned'); + expect(spec.command).toBe('npx'); + expect(spec.args).toEqual([ + '-y', + '@bradygaster/squad-cli@0.9.6-preview.42', + 'state-mcp', + ]); + }); + + it('falls back to @insider when the version is NOT published', async () => { + const spec = await resolveSquadStateMcpSpec('0.9.6-preview.99999', { + publishedCheck: async () => false, + }); + expect(spec.source).toBe('insider'); + expect(spec.command).toBe('npx'); + expect(spec.args).toEqual(['-y', '@bradygaster/squad-cli@insider', 'state-mcp']); + }); + + it('short-circuits the registry check for the placeholder 0.0.0 version (returns @insider)', async () => { + const spec = await resolveSquadStateMcpSpec('0.0.0', { + publishedCheck: async () => { + throw new Error('publishedCheck should not be called for 0.0.0'); + }, + }); + expect(spec.source).toBe('insider'); + expect(spec.args[1]).toBe('@bradygaster/squad-cli@insider'); + }); + + it('short-circuits the registry check for empty version (returns @insider)', async () => { + const spec = await resolveSquadStateMcpSpec('', { + publishedCheck: async () => { + throw new Error('publishedCheck should not be called for empty version'); + }, + }); + expect(spec.source).toBe('insider'); + }); + + it('never throws — always returns a usable spec (no hard-error tier in iter-7)', async () => { + const spec = await resolveSquadStateMcpSpec('0.9.6-preview.99999', { + publishedCheck: async () => false, + }); + expect(spec).toBeDefined(); + expect(spec.command).toBe('npx'); + }); + + it('uses the real npm-registry probe by default when publishedCheck is not injected', async () => { + mockIsPublished.mockResolvedValue(false); + const spec = await resolveSquadStateMcpSpec('0.9.6-preview.99999'); + expect(mockIsPublished).toHaveBeenCalledWith('0.9.6-preview.99999'); + expect(spec.source).toBe('insider'); + }); +}); + +describe('init.ts uses resolveSquadStateMcpSpec (asymmetry fix)', () => { + // Source-level architectural check: init.ts must reference the shared + // resolver to keep the npm-registry fallback consistent with upgrade.ts. + it('packages/squad-cli/src/cli/core/init.ts imports and calls resolveSquadStateMcpSpec', () => { + const initPath = path.join( + process.cwd(), + 'packages', + 'squad-cli', + 'src', + 'cli', + 'core', + 'init.ts', + ); + const src = readFileSync(initPath, 'utf-8'); + expect(src).toMatch(/resolveSquadStateMcpSpec/); + expect(src).toMatch(/from ['"]\.\/mcp-spec\.js['"]/); + }); + + it('upgrade.ts re-exports resolveSquadStateMcpSpec from mcp-spec (compat)', () => { + const upgradePath = path.join( + process.cwd(), + 'packages', + 'squad-cli', + 'src', + 'cli', + 'core', + 'upgrade.ts', + ); + const src = readFileSync(upgradePath, 'utf-8'); + expect(src).toMatch(/from ['"]\.\/mcp-spec\.js['"]/); + }); +}); diff --git a/test/npm-registry-fallback.test.ts b/test/npm-registry-fallback.test.ts new file mode 100644 index 000000000..a0acc3034 --- /dev/null +++ b/test/npm-registry-fallback.test.ts @@ -0,0 +1,58 @@ +/** + * Tests for the npm-registry HEAD-check used to decide whether the squad_state + * MCP launch spec should pin the current version or fall back to `@insider`. + * + * These tests stub the global `fetch`-equivalent (https.request) by relying on + * the cache mechanism: we directly seed the cache so we never actually hit + * the public npm registry from CI. + */ + +import { describe, it, expect, beforeEach } from 'vitest'; +import { + isSquadCliVersionPublished, + _resetNpmRegistryCache, +} from '../packages/squad-cli/src/cli/core/npm-registry.js'; +import { resolveSquadStateMcpSpec } from '../packages/squad-cli/src/cli/core/upgrade.js'; + +describe('npm-registry: isSquadCliVersionPublished', () => { + beforeEach(() => { + _resetNpmRegistryCache(); + }); + + it('returns false for empty / sentinel versions without hitting the network', async () => { + const ok = await isSquadCliVersionPublished('0.0.0', 100); + expect(ok).toBe(false); + }); + + it('returns false on network failure within the timeout budget', async () => { + // Use a version string that cannot exist + very small timeout. The HEAD + // request will either 404 or be aborted by the timeout — both produce + // `false`, never a hang. + const ok = await isSquadCliVersionPublished('999.999.999-not-a-real-version', 1500); + expect(ok).toBe(false); + }); +}); + +describe('resolveSquadStateMcpSpec: chooses pinned or @insider fallback', () => { + beforeEach(() => { + _resetNpmRegistryCache(); + }); + + it('falls back to @insider when version is empty / 0.0.0', async () => { + const spec = await resolveSquadStateMcpSpec('0.0.0'); + expect(spec).toEqual({ + command: 'npx', + args: ['-y', '@bradygaster/squad-cli@insider', 'state-mcp'], + source: 'insider', + }); + }); + + it('falls back to @insider when version is not published on the registry', async () => { + const spec = await resolveSquadStateMcpSpec('999.999.999-not-a-real-version'); + expect(spec).toEqual({ + command: 'npx', + args: ['-y', '@bradygaster/squad-cli@insider', 'state-mcp'], + source: 'insider', + }); + }); +}); diff --git a/test/session-store.test.ts b/test/session-store.test.ts index e6d0e053e..01362aa4a 100644 --- a/test/session-store.test.ts +++ b/test/session-store.test.ts @@ -195,9 +195,50 @@ describe('loadLatestSession', () => { }); // ============================================================================ -// loadSessionById +// stateDir override (externalized state backend) // ============================================================================ +describe('external stateDir support', () => { + it('saveSession writes to stateDir/sessions, not teamRoot/.squad/sessions', () => { + const externalDir = join(tmpRoot, 'external-state'); + mkdirSync(externalDir, { recursive: true }); + + const session = createSession(); + session.messages.push({ role: 'user', content: 'external', timestamp: new Date() }); + const filePath = saveSession(tmpRoot, session, externalDir); + + expect(filePath.startsWith(join(externalDir, 'sessions'))).toBe(true); + expect(existsSync(filePath)).toBe(true); + // Nothing written under teamRoot + expect(existsSync(join(tmpRoot, '.squad', 'sessions'))).toBe(false); + }); + + it('loadLatestSession finds a session saved to an external stateDir', () => { + const externalDir = join(tmpRoot, 'external-state'); + mkdirSync(externalDir, { recursive: true }); + + const session = createSession(); + session.messages.push({ role: 'user', content: 'hello-external', timestamp: new Date() }); + saveSession(tmpRoot, session, externalDir); + + const loaded = loadLatestSession(tmpRoot, externalDir); + expect(loaded).not.toBeNull(); + expect(loaded!.id).toBe(session.id); + expect(loaded!.messages[0]!.content).toBe('hello-external'); + }); + + it('loadLatestSession returns null when no sessions in external stateDir', () => { + const externalDir = join(tmpRoot, 'external-state'); + mkdirSync(externalDir, { recursive: true }); + // Sessions exist in teamRoot, but not in external dir + const session = createSession(); + saveSession(tmpRoot, session); + + expect(loadLatestSession(tmpRoot, externalDir)).toBeNull(); + }); +}); + + describe('loadSessionById', () => { it('returns null for non-existent session', () => { expect(loadSessionById(tmpRoot, 'does-not-exist')).toBeNull(); diff --git a/test/speed-gates.test.ts b/test/speed-gates.test.ts index 42c15cdd7..557ec9240 100644 --- a/test/speed-gates.test.ts +++ b/test/speed-gates.test.ts @@ -41,7 +41,9 @@ describe('Speed: --help is scannable', { timeout: 30_000 }, () => { await harness.waitForExit(15000); const output = harness.captureFrame(); const lines = output.split('\n').filter(l => l.trim()); - expect(lines.length).toBeLessThanOrEqual(130); + // Budget grew to accommodate `sync` + `state-mcp` commands. Hard cap + // keeps us honest if help text starts ballooning again. + expect(lines.length).toBeLessThanOrEqual(150); }); it('first 5 lines tell user what to do next', async () => { diff --git a/test/state-backend.test.ts b/test/state-backend.test.ts index 13260c128..0fba2f078 100644 --- a/test/state-backend.test.ts +++ b/test/state-backend.test.ts @@ -1,10 +1,10 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; -import { execSync } from 'node:child_process'; +import { execSync, execFileSync } from 'node:child_process'; import { randomBytes } from 'node:crypto'; import { tmpdir } from 'node:os'; -import { WorktreeBackend, GitNotesBackend, OrphanBranchBackend, TwoLayerBackend, resolveStateBackend, validateStateKey, StateBackendStorageAdapter } from '../packages/squad-sdk/src/state-backend.js'; +import { WorktreeBackend, GitNotesBackend, OrphanBranchBackend, TwoLayerBackend, CircuitBreaker, GitExecError, resolveStateBackend, validateStateKey, StateBackendStorageAdapter, verifyStateBackend, _resetGitNotesMigrationWarnForTesting } from '../packages/squad-sdk/src/state-backend.js'; import type { StateBackendType } from '../packages/squad-sdk/src/state-backend.js'; import { resolveSquadState, clearResolveSquadCache } from '../packages/squad-sdk/src/resolution.js'; import { ToolRegistry } from '../packages/squad-sdk/src/tools/index.js'; @@ -104,7 +104,7 @@ describe('OrphanBranchBackend', () => { describe('resolveStateBackend()', () => { const squadDir = () => join(TMP, '.squad'); - beforeEach(() => { clearResolveSquadCache(); if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); initRepo(); mkdirSync(squadDir(), { recursive: true }); }); + beforeEach(() => { clearResolveSquadCache(); _resetGitNotesMigrationWarnForTesting(); if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); initRepo(); mkdirSync(squadDir(), { recursive: true }); }); afterEach(() => { clearResolveSquadCache(); if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); }); it('defaults to local', () => { expect(resolveStateBackend(squadDir(), TMP).name).toBe('local'); }); it('reads stateBackend from config.json (git-notes migrates to two-layer)', () => { @@ -128,14 +128,31 @@ describe('resolveStateBackend()', () => { it('legacy git-notes migrates to two-layer', () => { expect(resolveStateBackend(squadDir(), TMP, 'git-notes' as any).name).toBe('two-layer'); }); - it('fails closed when an explicit git-native backend is unavailable', () => { + it('git-notes deprecation warning fires exactly once per process across repeated calls (Bug C)', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + resolveStateBackend(squadDir(), TMP, 'git-notes' as any); + resolveStateBackend(squadDir(), TMP, 'git-notes' as any); + resolveStateBackend(squadDir(), TMP, 'git-notes' as any); + // Warn should fire on the FIRST call only, never again. + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy.mock.calls[0][0]).toContain("'git-notes' is deprecated"); + } finally { + warnSpy.mockRestore(); + } + }); + it('soft-falls-back to local when an explicit git-native backend is unavailable', () => { const nonGitRoot = join(tmpdir(), `.squad-state-non-git-${randomBytes(4).toString('hex')}`); const nonGitSquad = join(nonGitRoot, '.squad'); mkdirSync(nonGitSquad, { recursive: true }); writeFileSync(join(nonGitSquad, 'config.json'), JSON.stringify({ version: 1, teamRoot: '.', stateBackend: 'two-layer' })); try { - expect(() => resolveStateBackend(nonGitSquad, nonGitRoot)).toThrow(/State backend 'two-layer' failed/); + // Bug B fix: resolveStateBackend no longer throws when a git-native backend + // fails; it emits a console.warn and falls back to WorktreeBackend ('local'). + expect(() => resolveStateBackend(nonGitSquad, nonGitRoot)).not.toThrow(); + const backend = resolveStateBackend(nonGitSquad, nonGitRoot); + expect(backend.name).toBe('local'); } finally { rmSync(nonGitRoot, { recursive: true, force: true }); } @@ -467,6 +484,52 @@ describe('StateBackendStorageAdapter', () => { expect(adapter.readSync('decisions.md')).toBe('# Decisions'); }); + it('toRelative handles Windows-style mixed drive-letter casing (Bug F)', () => { + // Simulate Windows drive-letter case mismatch: process.cwd() might return + // 'C:\...' while the stored path arrives as 'c:\...'. Because path.resolve() + // canonicalises the separator (but NOT the case on Windows), we fold to + // lower-case for the prefix comparison only. + // + // We cannot easily mock process.platform here, but we CAN exercise the + // lower-case comparison branch by constructing a squadDir path that differs + // only in drive-letter case from the filePath argument (simulating the real + // Windows scenario by treating the test paths as opaque strings the way + // path.resolve does on the host OS). + // + // On non-Windows hosts path.isAbsolute returns false for Windows-style paths, + // so we test the relative-path normalisation path instead. + const backend = new GitNotesBackend(TMP); + const adapter = new StateBackendStorageAdapter(backend, squadDir()); + + // Relative paths must always come back normalised (no backslashes) regardless + // of platform — this is the safe cross-platform subset of the fix. + const relWithBackslash = 'sub\\dir\\file.md'; + adapter.writeSync(relWithBackslash, 'backslash test'); + expect(adapter.readSync('sub/dir/file.md')).toBe('backslash test'); + }); + + it('toRelative throws for absolute paths outside squadDir (Bug F)', () => { + if (process.platform !== 'win32') { + // Only absolute paths starting with / are unambiguous on POSIX + const backend = new GitNotesBackend(TMP); + const adapter = new StateBackendStorageAdapter(backend, squadDir()); + // A path outside squadDir should throw, not silently return an absolute + // path as a git-notes key (which would corrupt the notes namespace). + expect(() => adapter.writeSync('/tmp/outside-squad.md', 'data')).toThrow( + /toRelative: path is outside squadDir/ + ); + } else { + // On Windows use a different drive to guarantee "outside" + const backend = new GitNotesBackend(TMP); + const adapter = new StateBackendStorageAdapter(backend, squadDir()); + // Use a drive letter that is guaranteed to differ from squadDir + const outsidePath = 'Z:\\outside\\file.md'; + expect(() => adapter.writeSync(outsidePath, 'data')).toThrow( + /toRelative: path is outside squadDir/ + ); + } + }); + it('deleteSync removes entries', () => { const backend = new GitNotesBackend(TMP); const adapter = new StateBackendStorageAdapter(backend, squadDir()); @@ -596,6 +659,57 @@ describe('ToolRegistry state tools with git-native backend', () => { expect(existsSync(join(squadDir(), 'decisions', 'inbox'))).toBe(false); expect(git('status --porcelain')).toBe(''); }); + + // Regression test for NEW-4: MCP tool layer writing empty blob (e69de29bb) when + // content is missing from the JSON-RPC payload (args.content === undefined at runtime). + it('squad_state_write with undefined content returns failure, does not write empty blob (NEW-4)', { timeout: 20_000 }, async () => { + const backend = new OrphanBranchBackend(TMP); + const adapter = new StateBackendStorageAdapter(backend, squadDir()); + const registry = new ToolRegistry(squadDir(), undefined, adapter); + const write = registry.getTool('squad_state_write')!; + + // Simulate MCP payload where content is missing (parseObject returns {} missing 'content'). + // Cast to any to bypass TypeScript's type checking, as the MCP layer does at runtime. + const result = await write.handler({ key: 'agents/scribe/history.md', content: undefined as unknown as string }); + + expect(result.resultType).toBe('failure'); + expect(result.textResultForLlm).toContain('content is required'); + // Backend must NOT have written an empty blob + expect(backend.exists('agents/scribe/history.md')).toBe(false); + expect(git('status --porcelain')).toBe(''); + }); + + it('squad_state_write with valid content writes correct non-empty content (NEW-4)', { timeout: 20_000 }, async () => { + const backend = new OrphanBranchBackend(TMP); + const adapter = new StateBackendStorageAdapter(backend, squadDir()); + const registry = new ToolRegistry(squadDir(), undefined, adapter); + const write = registry.getTool('squad_state_write')!; + + const content = '# Scribe History\n\n## Session 1\nCompleted replay without branch choreography.\n'; + const result = await write.handler({ key: 'agents/scribe/history.md', content }); + + expect(result.resultType).toBe('success'); + expect(backend.read('agents/scribe/history.md')).toBe(content); + // Ensure the blob is not the empty-content sentinel + expect(backend.read('agents/scribe/history.md')).not.toBe(''); + }); + + it('squad_state_append with undefined content returns failure, does not corrupt existing content (NEW-4)', { timeout: 20_000 }, async () => { + const backend = new OrphanBranchBackend(TMP); + const adapter = new StateBackendStorageAdapter(backend, squadDir()); + const registry = new ToolRegistry(squadDir(), undefined, adapter); + const write = registry.getTool('squad_state_write')!; + const append = registry.getTool('squad_state_append')!; + + await write.handler({ key: 'agents/data/history.md', content: '# Data\n' }); + + const result = await append.handler({ key: 'agents/data/history.md', content: undefined as unknown as string }); + + expect(result.resultType).toBe('failure'); + expect(result.textResultForLlm).toContain('content is required'); + // Existing content must be unchanged + expect(backend.read('agents/data/history.md')).toBe('# Data\n'); + }); }); describe('downloaded session replay regressions', () => { @@ -789,4 +903,552 @@ describe('downloaded session replay regressions', () => { expect(existsSync(squadDir())).toBe(true); expectWorktreeUnmoved(initialBranch, initialHead); }); +}); +describe('CircuitBreaker', () => { + it('starts in closed state', () => { + const cb = new CircuitBreaker(3, 1000); + expect(cb.currentState).toBe('closed'); + expect(cb.consecutiveFailures).toBe(0); + }); + + it('passes through successful operations', () => { + const cb = new CircuitBreaker(3, 1000); + const result = cb.execute(() => 42, 'test'); + expect(result).toBe(42); + expect(cb.consecutiveFailures).toBe(0); + }); + + it('tracks consecutive failures', () => { + const cb = new CircuitBreaker(3, 1000); + for (let i = 0; i < 2; i++) { + try { cb.execute(() => { throw new Error('fail'); }, 'test'); } catch { /* expected */ } + } + expect(cb.consecutiveFailures).toBe(2); + expect(cb.currentState).toBe('closed'); + }); + + it('trips open after threshold failures', () => { + const cb = new CircuitBreaker(3, 1000); + for (let i = 0; i < 3; i++) { + try { cb.execute(() => { throw new Error('fail'); }, 'test'); } catch { /* expected */ } + } + expect(cb.currentState).toBe('open'); + expect(cb.consecutiveFailures).toBe(3); + }); + + it('fast-fails when open', () => { + const cb = new CircuitBreaker(3, 1000); + for (let i = 0; i < 3; i++) { + try { cb.execute(() => { throw new Error('fail'); }, 'test'); } catch { /* expected */ } + } + expect(() => cb.execute(() => 42, 'test')).toThrow(/Circuit breaker OPEN/); + }); + + it('resets on success', () => { + const cb = new CircuitBreaker(3, 1000); + try { cb.execute(() => { throw new Error('fail'); }, 'test'); } catch { /* expected */ } + expect(cb.consecutiveFailures).toBe(1); + cb.execute(() => 'ok', 'test'); + expect(cb.consecutiveFailures).toBe(0); + expect(cb.currentState).toBe('closed'); + }); + + it('transitions to half-open after cooldown', () => { + const cb = new CircuitBreaker(2, 50); // 50ms cooldown for test speed + for (let i = 0; i < 2; i++) { + try { cb.execute(() => { throw new Error('fail'); }, 'test'); } catch { /* expected */ } + } + expect(cb.currentState).toBe('open'); + + // Wait for cooldown + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 60); + + // Next call should go through (half-open probe) + const result = cb.execute(() => 'recovered', 'test'); + expect(result).toBe('recovered'); + expect(cb.currentState).toBe('closed'); + }); +}); + +describe('verifyStateBackend()', () => { + const squadDir = () => join(TMP, '.squad'); + beforeEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); initRepo(); mkdirSync(squadDir(), { recursive: true }); }); + afterEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); }); + + it('worktree backend passes verification', () => { + const backend = new WorktreeBackend(squadDir()); + const result = verifyStateBackend(backend); + expect(result.ok).toBe(true); + expect(result.error).toBeUndefined(); + }); + + it('git-notes backend passes verification', () => { + const backend = new GitNotesBackend(TMP); + const result = verifyStateBackend(backend); + expect(result.ok).toBe(true); + }); + + it('orphan backend passes verification', () => { + const backend = new OrphanBranchBackend(TMP); + const result = verifyStateBackend(backend); + expect(result.ok).toBe(true); + }); + + it('returns error for broken backend', () => { + const brokenBackend = { + name: 'broken', + read: () => undefined, + write: () => {}, + exists: () => false, + list: () => { throw new Error('backend is broken'); }, + }; + const result = verifyStateBackend(brokenBackend); + expect(result.ok).toBe(false); + expect(result.error).toContain('backend is broken'); + }); +}); + +describe('GitExecError (missing vs real failure)', () => { + beforeEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); initRepo(); }); + afterEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); }); + + it('GitExecError has command, reason, and stderr fields', () => { + const err = new GitExecError('git show HEAD:x', 'file not found', 'fatal: path does not exist'); + expect(err.name).toBe('GitExecError'); + expect(err.command).toBe('git show HEAD:x'); + expect(err.reason).toBe('file not found'); + expect(err.stderr).toBe('fatal: path does not exist'); + expect(err.message).toContain('git show HEAD:x'); + expect(err).toBeInstanceOf(Error); + }); + + it('git-notes read returns undefined for missing note (not throw)', () => { + // In a valid git repo with no notes, read should return undefined (expected missing) + const b = new GitNotesBackend(TMP); + expect(b.read('nonexistent.md')).toBeUndefined(); + }); + + it('orphan read returns undefined for missing path (not throw)', () => { + const b = new OrphanBranchBackend(TMP); + expect(b.read('nonexistent.md')).toBeUndefined(); + }); + + it('git-notes throws GitExecError for real failures (not a git repo)', () => { + // Must be OUTSIDE any git repo — using os.tmpdir() to avoid inheriting parent .git + const nonGitDir = join(tmpdir(), `.test-nongit-${randomBytes(4).toString('hex')}`); + mkdirSync(nonGitDir, { recursive: true }); + try { + const b = new GitNotesBackend(nonGitDir); + expect(() => b.read('team.md')).toThrow(GitExecError); + } finally { + rmSync(nonGitDir, { recursive: true, force: true }); + } + }); + + it('orphan exists throws GitExecError for real failures (not a git repo)', () => { + const nonGitDir = join(tmpdir(), `.test-nongit-${randomBytes(4).toString('hex')}`); + mkdirSync(nonGitDir, { recursive: true }); + try { + const b = new OrphanBranchBackend(nonGitDir); + expect(() => b.exists('team.md')).toThrow(GitExecError); + } finally { + rmSync(nonGitDir, { recursive: true, force: true }); + } + }); + + it('orphan list throws GitExecError for real failures (not a git repo)', () => { + const nonGitDir = join(tmpdir(), `.test-nongit-${randomBytes(4).toString('hex')}`); + mkdirSync(nonGitDir, { recursive: true }); + try { + const b = new OrphanBranchBackend(nonGitDir); + expect(() => b.list('')).toThrow(GitExecError); + } finally { + rmSync(nonGitDir, { recursive: true, force: true }); + } + }); +}); +describe('TwoLayerBackend.promoteNotes / readNote / observability', () => { + beforeEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); initRepo(); }); + afterEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); vi.restoreAllMocks(); }); + + function addCommit(filename: string): string { + writeFileSync(join(TMP, filename), `content of ${filename}\n`); + git(`add ${filename}`); + git(`commit -m "add ${filename}"`); + return git('rev-parse HEAD'); + } + + function addNote(ref: string, commitSha: string, payload: object): void { + const body = JSON.stringify(payload); + // Write to a temp file so quoting/newlines don't get mangled by execSync. + const noteFile = join(TMP, `.note-${randomBytes(4).toString('hex')}.json`); + writeFileSync(noteFile, body); + git(`notes --ref=${ref} add -F "${noteFile}" ${commitSha}`); + rmSync(noteFile); + } + + it('promoteNotes moves promote_to_permanent notes to orphan and removes source', () => { + const b = new TwoLayerBackend(TMP); + const sha = addCommit('feature.ts'); + addNote('squad/picard', sha, { promote_to_permanent: true, decision: 'ship it' }); + + const result = b.promoteNotes('squad/picard'); + + expect(result.promoted).toHaveLength(1); + expect(result.promoted[0]).toBe(`promoted/squad/picard/${sha}.json`); + expect(result.archived).toHaveLength(0); + expect(result.skipped).toBe(0); + + // Orphan layer received the payload. + const stored = b.orphan.read(`promoted/squad/picard/${sha}.json`); + expect(stored).toBeDefined(); + expect(JSON.parse(stored!).decision).toBe('ship it'); + + // Source note was removed. + expect(() => git(`notes --ref=squad/picard show ${sha}`)).toThrow(); + }, 30000); + + it('promoteNotes copies archive_on_close notes to orphan archive/ without removing source', () => { + const b = new TwoLayerBackend(TMP); + const sha = addCommit('research.ts'); + addNote('squad/research', sha, { archive_on_close: true, notes: 'investigation log' }); + + const result = b.promoteNotes('squad/research'); + + expect(result.archived).toHaveLength(1); + expect(result.archived[0]).toBe(`archive/squad/research/${sha}.json`); + expect(result.promoted).toHaveLength(0); + expect(result.skipped).toBe(0); + + // Orphan layer received the archive. + const stored = b.orphan.read(`archive/squad/research/${sha}.json`); + expect(stored).toBeDefined(); + expect(JSON.parse(stored!).notes).toBe('investigation log'); + + // Source note is KEPT (archive = copy). + expect(git(`notes --ref=squad/research show ${sha}`)).toContain('investigation log'); + }, 30000); + + it('promoteNotes skips notes without either flag', () => { + const b = new TwoLayerBackend(TMP); + const sha = addCommit('chat.ts'); + addNote('squad/data', sha, { ephemeral: true, message: 'just a thought' }); + + const result = b.promoteNotes('squad/data'); + + expect(result.promoted).toHaveLength(0); + expect(result.archived).toHaveLength(0); + expect(result.skipped).toBe(1); + + // Source note is left in place. + expect(git(`notes --ref=squad/data show ${sha}`)).toContain('just a thought'); + }, 30000); + + it('readNote returns null when no note exists', () => { + const b = new TwoLayerBackend(TMP); + const sha = git('rev-parse HEAD'); + expect(b.readNote('squad/picard', sha)).toBeNull(); + }); + + it('readNote returns parsed JSON when note exists', () => { + const b = new TwoLayerBackend(TMP); + const sha = git('rev-parse HEAD'); + addNote('squad/picard', sha, { type: 'decision', body: 'approved' }); + + const parsed = b.readNote('squad/picard', sha) as { type: string; body: string }; + expect(parsed).toEqual({ type: 'decision', body: 'approved' }); + }); + + it('verifyStateBackend fails when TwoLayerBackend notes layer is broken', () => { + const b = new TwoLayerBackend(TMP); + // Make the orphan layer report healthy by stubbing list. + vi.spyOn(b.orphan, 'list').mockReturnValue([]); + // Break the notes layer. + vi.spyOn(b.notes, 'list').mockImplementation(() => { throw new Error('notes ref corrupt'); }); + + const result = verifyStateBackend(b); + expect(result.ok).toBe(false); + expect(result.error).toMatch(/notes layer unhealthy/); + expect(result.error).toMatch(/notes ref corrupt/); + }); + + it('write/delete/append failures on notes layer log console.warn', () => { + const b = new TwoLayerBackend(TMP); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { /* swallow */ }); + + vi.spyOn(b.notes, 'write').mockImplementation(() => { throw new Error('boom-write'); }); + vi.spyOn(b.notes, 'append').mockImplementation(() => { throw new Error('boom-append'); }); + vi.spyOn(b.notes, 'delete').mockImplementation(() => { throw new Error('boom-delete'); }); + + b.write('decisions/foo.md', 'hi'); + b.append('history/foo.md', 'more'); + b.delete('decisions/foo.md'); + + const warnings = warnSpy.mock.calls.map((c) => String(c[0])); + expect(warnings.some((w) => w.includes('notes write failed for decisions/foo.md') && w.includes('boom-write'))).toBe(true); + expect(warnings.some((w) => w.includes('notes append failed for history/foo.md') && w.includes('boom-append'))).toBe(true); + expect(warnings.some((w) => w.includes('notes delete failed for decisions/foo.md') && w.includes('boom-delete'))).toBe(true); + }, 30000); +}); + +// ─────────────────────────────────────────────────────────────────── +// Compare-and-swap (CAS) tests for GitNotesBackend + OrphanBranchBackend. +// These cover the optimistic-concurrency refactor that replaced the +// silently-clobbering `git notes add -f` and unconditional update-ref +// with a read → mutate → update-ref-with-expected-old loop. +// ─────────────────────────────────────────────────────────────────── + +describe('tryUpdateRef (CAS primitive)', () => { + beforeEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); initRepo(); }); + afterEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); }); + + it('succeeds when ref is at the expected SHA', async () => { + const { _tryUpdateRefForTesting } = await import('../packages/squad-sdk/src/state-backend.js'); + const headSha = git('rev-parse HEAD'); + // Create a target ref pointing at HEAD, then CAS-update it to itself. + git(`update-ref refs/test/cas ${headSha}`); + const result = _tryUpdateRefForTesting('refs/test/cas', headSha, headSha, TMP); + expect(result.ok).toBe(true); + }); + + it('returns ok:false with stderr on CAS conflict (expected-old mismatch)', async () => { + const { _tryUpdateRefForTesting } = await import('../packages/squad-sdk/src/state-backend.js'); + const headSha = git('rev-parse HEAD'); + git(`update-ref refs/test/cas2 ${headSha}`); + // Lie about the expected old SHA -> CAS must reject. + const bogus = '0123456789abcdef0123456789abcdef01234567'; + const result = _tryUpdateRefForTesting('refs/test/cas2', headSha, bogus, TMP); + expect(result.ok).toBe(false); + expect(result.stderr.length).toBeGreaterThan(0); + }); + + it('returns ok:false when creating a ref that already exists (expected-old = null)', async () => { + const { _tryUpdateRefForTesting } = await import('../packages/squad-sdk/src/state-backend.js'); + const headSha = git('rev-parse HEAD'); + git(`update-ref refs/test/cas3 ${headSha}`); + // null expectedOld -> tryUpdateRef sends 40 zeros == "must not exist". + const result = _tryUpdateRefForTesting('refs/test/cas3', headSha, null, TMP); + expect(result.ok).toBe(false); + }); + + it('throws (not returns) on non-CAS failures (e.g., bogus SHA)', async () => { + const { _tryUpdateRefForTesting } = await import('../packages/squad-sdk/src/state-backend.js'); + // Reference a non-existent object — this is a real git error, not a CAS conflict. + expect(() => _tryUpdateRefForTesting('refs/test/cas4', 'deadbeef'.repeat(5), null, TMP)).toThrow(); + }); +}); + +describe('GitNotesBackend CAS retry semantics', () => { + beforeEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); initRepo(); }); + afterEach(async () => { + const { _setCasInjectorForTesting } = await import('../packages/squad-sdk/src/state-backend.js'); + _setCasInjectorForTesting(null); + if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); + }); + + it('write succeeds on first try when ref is uncontended', { timeout: 15_000 }, () => { + const b = new GitNotesBackend(TMP); + b.write('a.md', 'A'); + expect(b.read('a.md')).toBe('A'); + }); + + it('two sequential writes preserve both keys (no clobber)', { timeout: 15_000 }, () => { + const b = new GitNotesBackend(TMP); + b.write('a.md', 'A'); + b.write('b.md', 'B'); + expect(b.read('a.md')).toBe('A'); + expect(b.read('b.md')).toBe('B'); + }); + + it('rebuilds on top of out-of-band ref advancement (CAS retry converges)', { timeout: 20_000 }, () => { + // Seed the notes ref with one key. + const b = new GitNotesBackend(TMP); + b.write('seed.md', 'S'); + const refBefore = git('rev-parse refs/notes/squad'); + + // Out-of-band advance: another writer added a key while we weren't looking. + // Build the new note via plumbing (use execFileSync to bypass cmd.exe `^` escaping). + const anchor = git('rev-list --max-parents=0 HEAD'); + const existingRaw = execSync(`git show ${refBefore}:${anchor}`, { cwd: TMP, encoding: 'utf-8' }); + const existingJson = JSON.parse(existingRaw); + existingJson['outOfBand.md'] = 'OOB'; + const newJson = JSON.stringify(existingJson, null, 2); + const blobSha = execSync('git hash-object -w --stdin', { cwd: TMP, encoding: 'utf-8', input: newJson }).trim(); + const treeSha = execSync('git mktree', { cwd: TMP, encoding: 'utf-8', input: `100644 blob ${blobSha}\t${anchor}\n` }).trim(); + const newCommit = execSync(`git commit-tree ${treeSha} -p ${refBefore} -m "oob"`, { cwd: TMP, encoding: 'utf-8' }).trim(); + git(`update-ref refs/notes/squad ${newCommit} ${refBefore}`); + + // SDK writes again. Under old `notes add -f` this would clobber outOfBand.md. + // Under CAS the rebuild reads the latest tip and preserves OOB. + b.write('postBand.md', 'P'); + expect(b.read('seed.md')).toBe('S'); + expect(b.read('outOfBand.md')).toBe('OOB'); + expect(b.read('postBand.md')).toBe('P'); + }); + + it('converges after exactly one CAS conflict via injector', { timeout: 20_000 }, async () => { + const { _setCasInjectorForTesting } = await import('../packages/squad-sdk/src/state-backend.js'); + const b = new GitNotesBackend(TMP); + b.write('seed.md', 'S'); + + let injectCount = 0; + _setCasInjectorForTesting((ref) => { + if (ref !== 'refs/notes/squad') return null; + if (injectCount++ < 1) return { ok: false, stderr: 'simulated CAS mismatch' }; + return null; // subsequent attempts go through to real git + }); + + b.write('retry.md', 'R'); + expect(injectCount).toBe(2); // attempt 1 forced fail, attempt 2 real success + expect(b.read('seed.md')).toBe('S'); + expect(b.read('retry.md')).toBe('R'); + }); + + it('converges after 4 CAS conflicts (right at the retry budget edge)', { timeout: 20_000 }, async () => { + const { _setCasInjectorForTesting } = await import('../packages/squad-sdk/src/state-backend.js'); + const b = new GitNotesBackend(TMP); + b.write('seed.md', 'S'); + + let injectCount = 0; + _setCasInjectorForTesting((ref) => { + if (ref !== 'refs/notes/squad') return null; + if (injectCount++ < 4) return { ok: false, stderr: 'simulated CAS mismatch' }; + return null; // 5th attempt goes through + }); + + b.write('edge.md', 'E'); + expect(b.read('seed.md')).toBe('S'); + expect(b.read('edge.md')).toBe('E'); + }); + + it('throws StateBackendConcurrencyError after exhausting all 5 attempts', { timeout: 20_000 }, async () => { + const { _setCasInjectorForTesting, StateBackendConcurrencyError } = await import('../packages/squad-sdk/src/state-backend.js'); + const b = new GitNotesBackend(TMP); + b.write('seed.md', 'S'); + + _setCasInjectorForTesting((ref) => { + if (ref !== 'refs/notes/squad') return null; + return { ok: false, stderr: 'simulated nonstop CAS mismatch' }; + }); + + let caught: unknown; + try { b.write('boom.md', 'X'); } catch (e) { caught = e; } + expect(caught).toBeInstanceOf(StateBackendConcurrencyError); + expect((caught as Error).message).toContain('git-notes:write(boom.md)'); + expect((caught as Error).message).toContain('5 attempts'); + }); +}); + +describe('OrphanBranchBackend CAS retry semantics', () => { + beforeEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); initRepo(); }); + afterEach(async () => { + const { _setCasInjectorForTesting } = await import('../packages/squad-sdk/src/state-backend.js'); + _setCasInjectorForTesting(null); + if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); + }); + + it('write rebuilds on top of out-of-band branch advancement', { timeout: 20_000 }, () => { + const b = new OrphanBranchBackend(TMP); + b.write('seed.md', 'S'); + const refBefore = git('rev-parse refs/heads/squad-state'); + + // Out-of-band: append a new file to the orphan branch via plumbing. + // Use array-form execFileSync to bypass cmd.exe interpreting `^` in `^{tree}`. + const treeBefore = execFileSync('git', ['rev-parse', `${refBefore}^{tree}`], { cwd: TMP, encoding: 'utf-8' }).trim(); + const blobSha = execSync('git hash-object -w --stdin', { cwd: TMP, encoding: 'utf-8', input: 'OOB' }).trim(); + const existingTree = execSync(`git ls-tree ${treeBefore}`, { cwd: TMP, encoding: 'utf-8' }).split('\n').filter(Boolean); + existingTree.push(`100644 blob ${blobSha}\toutOfBand.md`); + const newTree = execSync('git mktree', { cwd: TMP, encoding: 'utf-8', input: existingTree.join('\n') + '\n' }).trim(); + const newCommit = execSync(`git commit-tree ${newTree} -p ${refBefore} -m "oob"`, { cwd: TMP, encoding: 'utf-8' }).trim(); + git(`update-ref refs/heads/squad-state ${newCommit} ${refBefore}`); + + b.write('postBand.md', 'P'); + expect(b.read('seed.md')).toBe('S'); + expect(b.read('outOfBand.md')).toBe('OOB'); + expect(b.read('postBand.md')).toBe('P'); + }); + + it('converges after one CAS conflict via injector', { timeout: 20_000 }, async () => { + const { _setCasInjectorForTesting } = await import('../packages/squad-sdk/src/state-backend.js'); + const b = new OrphanBranchBackend(TMP); + b.write('seed.md', 'S'); + + let injectCount = 0; + _setCasInjectorForTesting((ref) => { + if (ref !== 'refs/heads/squad-state') return null; + if (injectCount++ < 1) return { ok: false, stderr: 'simulated CAS mismatch' }; + return null; + }); + + b.write('retry.md', 'R'); + expect(b.read('seed.md')).toBe('S'); + expect(b.read('retry.md')).toBe('R'); + }); + + it('throws StateBackendConcurrencyError after exhausting all 5 attempts on write', { timeout: 20_000 }, async () => { + const { _setCasInjectorForTesting, StateBackendConcurrencyError } = await import('../packages/squad-sdk/src/state-backend.js'); + const b = new OrphanBranchBackend(TMP); + b.write('seed.md', 'S'); + + _setCasInjectorForTesting((ref) => { + if (ref !== 'refs/heads/squad-state') return null; + return { ok: false, stderr: 'simulated nonstop CAS mismatch' }; + }); + + let caught: unknown; + try { b.write('boom.md', 'X'); } catch (e) { caught = e; } + expect(caught).toBeInstanceOf(StateBackendConcurrencyError); + expect((caught as Error).message).toContain('orphan:write(boom.md)'); + }); + + it('delete throws StateBackendConcurrencyError after exhausting retries', { timeout: 20_000 }, async () => { + const { _setCasInjectorForTesting, StateBackendConcurrencyError } = await import('../packages/squad-sdk/src/state-backend.js'); + const b = new OrphanBranchBackend(TMP); + b.write('seed.md', 'S'); + + _setCasInjectorForTesting((ref) => { + if (ref !== 'refs/heads/squad-state') return null; + return { ok: false, stderr: 'simulated nonstop CAS mismatch' }; + }); + + let caught: unknown; + try { b.delete('seed.md'); } catch (e) { caught = e; } + expect(caught).toBeInstanceOf(StateBackendConcurrencyError); + }); +}); + +// ─────────────────────────────────────────────────────────────────── +// Regression: arg-tokenization in gitExecMaybeMissing. +// The previous implementation accepted a space-separated string and did +// args.split(' '), which silently mangled any argument containing a space. +// After the P1.2 fix, helpers take a string[] so spaces in path segments, +// commit messages, and refs survive untouched. +// validateStateKey forbids \n/\r/\t but NOT space, so spaces in state keys +// are legal and must be supported end-to-end. +// ─────────────────────────────────────────────────────────────────── + +describe('gitExec arg tokenization (P1.2 regression)', () => { + beforeEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); initRepo(); }); + afterEach(() => { if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); }); + + it('OrphanBranchBackend handles state keys with spaces (write/read/exists/delete round-trip)', { timeout: 20_000 }, () => { + const b = new OrphanBranchBackend(TMP); + const key = 'agents/data picard.md'; + b.write(key, 'team config with space in name'); + expect(b.exists(key)).toBe(true); + expect(b.read(key)).toBe('team config with space in name'); + const listing = b.list('agents'); + expect(listing).toContain('data picard.md'); + expect(b.delete(key)).toBe(true); + expect(b.exists(key)).toBe(false); + }); + + it('GitNotesBackend handles state keys with spaces (write/read/list round-trip)', { timeout: 15_000 }, () => { + const b = new GitNotesBackend(TMP); + const key = 'decisions/my decision.md'; + b.write(key, 'decision body'); + expect(b.read(key)).toBe('decision body'); + expect(b.exists(key)).toBe(true); + expect(b.list('decisions')).toContain('my decision.md'); + }); }); \ No newline at end of file diff --git a/test/sync-command.test.ts b/test/sync-command.test.ts new file mode 100644 index 000000000..d6a22e4a3 --- /dev/null +++ b/test/sync-command.test.ts @@ -0,0 +1,50 @@ +/** + * `squad sync` command — basic unit coverage. + * + * Gap 1 from iteration-2 smoke: post-commit hook invokes `squad sync --quiet` + * but the subcommand did not exist. This test confirms the entrypoint resolves + * and exits cleanly on local/worktree backends (no-op), and that it correctly + * detects an orphan-style backend without crashing when there is no remote. + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { runSync } from '../packages/squad-cli/src/cli/commands/sync.js'; + +function mkRepo(): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-sync-cmd-')); + execFileSync('git', ['init', '-q', '-b', 'main', dir], { stdio: ['pipe', 'pipe', 'pipe'] }); + execFileSync('git', ['-C', dir, 'config', 'user.email', 'test@example.com']); + execFileSync('git', ['-C', dir, 'config', 'user.name', 'Test']); + fs.writeFileSync(path.join(dir, 'README.md'), '# t\n'); + execFileSync('git', ['-C', dir, 'add', '.']); + execFileSync('git', ['-C', dir, 'commit', '-q', '-m', 'init']); + fs.mkdirSync(path.join(dir, '.squad'), { recursive: true }); + return dir; +} + +describe('squad sync command', () => { + let dir: string; + beforeEach(() => { dir = mkRepo(); }); + afterEach(() => { fs.rmSync(dir, { recursive: true, force: true }); }); + + it('no-ops cleanly when backend is local', async () => { + fs.writeFileSync(path.join(dir, '.squad', 'config.json'), + JSON.stringify({ version: 1, stateBackend: 'local' }, null, 2)); + await expect(runSync({ direction: 'both', cwd: dir, quiet: true })).resolves.toBeUndefined(); + }); + + it('no-ops cleanly when no .squad/config.json exists', async () => { + fs.rmSync(path.join(dir, '.squad'), { recursive: true, force: true }); + await expect(runSync({ direction: 'both', cwd: dir, quiet: true })).resolves.toBeUndefined(); + }); + + it('runs without throwing for two-layer backend even with no remote configured', async () => { + fs.writeFileSync(path.join(dir, '.squad', 'config.json'), + JSON.stringify({ version: 1, stateBackend: 'two-layer' }, null, 2)); + // No remote — fetch will fail silently inside syncPull; push will report "no branches". + await expect(runSync({ direction: 'both', cwd: dir, quiet: true })).resolves.toBeUndefined(); + }); +}); diff --git a/test/template-routing.test.ts b/test/template-routing.test.ts new file mode 100644 index 000000000..d51affdf8 --- /dev/null +++ b/test/template-routing.test.ts @@ -0,0 +1,87 @@ +/** + * Template routing regression test (iter-5). + * + * Prior to iter-5, TEMPLATE_MANIFEST entries for ~20 generic template docs + * (charter.md, history.md, roster.md, scribe-charter.md, ...) had + * `destination: '.md'` — flat to `.squad/`. On every `squad upgrade`, + * the upgrade loop would dump that pile of reference docs into the `.squad/` + * root, cluttering it and confusing users (visible in + * `.squad/files/validation/REVAL-ITER4-multiplayer-sudoku.md`). + * + * This test pins the routing: every `.md` template (except those that target + * `.github/` or `.copilot/` via the `..` parent prefix) must land under either + * `templates/`, `agents/`, `identity/`, or another nested subdirectory — never + * directly at `.squad/` root. + */ + +import { describe, it, expect } from 'vitest'; +import { TEMPLATE_MANIFEST } from '../packages/squad-cli/src/cli/core/templates.js'; + +describe('TEMPLATE_MANIFEST routing (iter-5: no doc dumping into .squad/ root)', () => { + it('no plain .md template lands at the .squad/ root', () => { + const offenders: { source: string; destination: string }[] = []; + + for (const entry of TEMPLATE_MANIFEST) { + // skip files routed outside .squad/ (../.github, ../.copilot) + if (entry.destination.startsWith('..')) continue; + // only check markdown templates here + if (!entry.destination.endsWith('.md')) continue; + // user-owned bootstrap files (overwriteOnUpgrade: false) legitimately + // live at the root — they are real runtime files, not template docs. + if (!entry.overwriteOnUpgrade) continue; + // anything still at the root after the above is a flat-doc offender + if (!entry.destination.includes('/')) { + offenders.push({ source: entry.source, destination: entry.destination }); + } + } + + expect( + offenders, + `${offenders.length} template .md(s) are still flat-routed to .squad/ root: ` + + JSON.stringify(offenders, null, 2), + ).toEqual([]); + }); + + it('generic doc templates are routed to .squad/templates/', () => { + const expected: Record = { + 'charter.md': 'templates/charter.md', + 'history.md': 'templates/history.md', + 'roster.md': 'templates/roster.md', + 'run-output.md': 'templates/run-output.md', + 'mcp-config.md': 'templates/mcp-config.md', + 'orchestration-log.md': 'templates/orchestration-log.md', + 'multi-agent-format.md': 'templates/multi-agent-format.md', + 'plugin-marketplace.md': 'templates/plugin-marketplace.md', + 'raw-agent-output.md': 'templates/raw-agent-output.md', + 'constraint-tracking.md': 'templates/constraint-tracking.md', + 'copilot-instructions.md': 'templates/copilot-instructions.md', + 'skill.md': 'templates/skill.md', + 'issue-lifecycle.md': 'templates/issue-lifecycle.md', + 'scribe-charter.md': 'templates/scribe-charter.md', + 'Rai-charter.md': 'templates/Rai-charter.md', + 'fact-checker-charter.md': 'templates/fact-checker-charter.md', + 'rai-policy.md': 'templates/rai-policy.md', + }; + + for (const [source, dest] of Object.entries(expected)) { + const entry = TEMPLATE_MANIFEST.find(e => e.source === source); + expect(entry, `manifest is missing entry for source="${source}"`).toBeDefined(); + expect(entry!.destination, `source="${source}" should route to "${dest}"`).toBe(dest); + } + }); + + it('casting JSON files remain flat at .squad/ root (runtime contract)', () => { + // The SDK and many agent skills read these via the flat path; moving them + // would silently break runtime. This pin documents the intentional carve-out. + const flatJsonExpected = [ + 'casting-history.json', + 'casting-policy.json', + 'casting-registry.json', + ]; + for (const name of flatJsonExpected) { + const entry = TEMPLATE_MANIFEST.find(e => e.source === name); + expect(entry, `manifest missing casting entry "${name}"`).toBeDefined(); + expect(entry!.destination).toBe(name); + } + }); +}); diff --git a/test/upgrade-eperm-false-success.test.ts b/test/upgrade-eperm-false-success.test.ts new file mode 100644 index 000000000..72c5b00dd --- /dev/null +++ b/test/upgrade-eperm-false-success.test.ts @@ -0,0 +1,81 @@ +/** + * Regression test for UPGRADE-EPERM-FALSE-SUCCESS. + * + * Before the fix: when `npm install -g @bradygaster/squad-cli` failed (EPERM / + * EACCES / EBUSY), `selfUpgradeCli` swallowed the error and returned normally, + * causing the caller in cli-entry.ts to unconditionally print + * `✅ Upgraded. Please restart your terminal...` and exit 0 — contradicting + * the `⚠️ Upgrade failed` warning printed moments earlier. + * + * Expected after fix: `selfUpgradeCli` throws on package-manager failure so the + * caller can exit non-zero and only the failure message is shown. + * + * Evidence: .squad/files/validation/TWOLAYER-BASELINE-INSIDER3-CONSOLIDATED.md + */ + +import { describe, it, expect, vi, afterEach } from 'vitest'; + +afterEach(() => { + vi.resetModules(); + vi.restoreAllMocks(); +}); + +describe('UPGRADE-EPERM-FALSE-SUCCESS: selfUpgradeCli surfaces install failures', () => { + it('throws when the package-manager install command fails with EPERM', async () => { + // Stub child_process.execSync to simulate an EPERM from npm install -g. + vi.doMock('node:child_process', async () => { + const actual = await vi.importActual('node:child_process'); + return { + ...actual, + execSync: vi.fn(() => { + const e = new Error('EPERM: operation not permitted, copyfile ... squad.cmd'); + (e as NodeJS.ErrnoException).code = 'EPERM'; + throw e; + }), + }; + }); + + const { selfUpgradeCli } = await import('../packages/squad-cli/src/cli/core/upgrade.js'); + await expect(selfUpgradeCli({ insider: false })).rejects.toThrow(/Self-upgrade failed/); + }); + + it('throws with EACCES hint when the install command fails with EACCES', async () => { + vi.doMock('node:child_process', async () => { + const actual = await vi.importActual('node:child_process'); + return { + ...actual, + execSync: vi.fn(() => { + const e = new Error('EACCES: permission denied'); + (e as NodeJS.ErrnoException).code = 'EACCES'; + throw e; + }), + }; + }); + + const { selfUpgradeCli } = await import('../packages/squad-cli/src/cli/core/upgrade.js'); + await expect(selfUpgradeCli({ insider: false })).rejects.toThrow(/Self-upgrade failed/); + }); + + it('cli-entry exits non-zero when selfUpgradeCli throws (no "✅ Upgraded" printed)', async () => { + // Static source-level check: the upgrade-self branch in cli-entry.ts must + // wrap selfUpgradeCli in a try/catch that calls process.exit(1) on failure, + // and must NOT print "✅ Upgraded" before the call. This prevents the + // baseline-observed contradictory output. + const fs = await import('node:fs'); + const path = await import('node:path'); + const src = fs.readFileSync( + path.join(process.cwd(), 'packages', 'squad-cli', 'src', 'cli-entry.ts'), + 'utf-8', + ); + // Match the self-upgrade block heuristically. + const block = src.match(/if \(selfUpgrade\) \{[\s\S]*?return;\s*\}/); + expect(block, 'self-upgrade block should exist in cli-entry.ts').toBeTruthy(); + const blockSrc = block![0]; + expect(blockSrc).toMatch(/try\s*\{[\s\S]*?selfUpgradeCli/); + expect(blockSrc).toMatch(/catch\s*\([\s\S]*?\)\s*\{[\s\S]*?process\.exit\(1\)/); + // And the success log must appear AFTER the catch block (only reached if no throw). + const successIdx = blockSrc.indexOf('✅ Upgraded'); + const catchIdx = blockSrc.indexOf('catch'); + expect(successIdx).toBeGreaterThan(catchIdx); + }); +}); diff --git a/test/upgrade-eperm-state-backend-continues.test.ts b/test/upgrade-eperm-state-backend-continues.test.ts new file mode 100644 index 000000000..8dda7796a --- /dev/null +++ b/test/upgrade-eperm-state-backend-continues.test.ts @@ -0,0 +1,71 @@ +/** + * Tests that EPERM during `squad upgrade --self --state-backend two-layer` + * does NOT short-circuit the state-backend migration. Self-upgrade and + * backend migration are independent operations; failing one must not block + * the other. + * + * These tests spawn the CLI directly (not unit-mock the runUpgrade path) + * because the regression is in the cli-entry.ts control flow. + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { spawnSync } from 'node:child_process'; +import { mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { randomBytes } from 'node:crypto'; +import { fileURLToPath } from 'node:url'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(here, '..'); +const cliEntry = path.join(repoRoot, 'packages', 'squad-cli', 'dist', 'cli-entry.js'); + +describe('upgrade --self --state-backend with self-upgrade EPERM', () => { + let workdir: string; + + beforeEach(() => { + workdir = path.join(tmpdir(), `squad-eperm-statebackend-${randomBytes(4).toString('hex')}`); + mkdirSync(workdir, { recursive: true }); + // Seed a minimal squad project so runUpgrade and migrateStateBackend + // have something to operate on. + mkdirSync(path.join(workdir, '.squad'), { recursive: true }); + writeFileSync(path.join(workdir, '.squad', 'team.md'), '# Test team\n'); + writeFileSync(path.join(workdir, '.squad', 'config.json'), JSON.stringify({ + version: 1, + stateBackend: 'worktree', + }, null, 2)); + }); + + afterEach(() => { + try { rmSync(workdir, { recursive: true, force: true }); } catch { /* ignore */ } + }); + + it('cli-entry has refactored upgrade control flow that no longer process.exit(1)s before state-backend', () => { + // Static source check: in iter-3 the EPERM catch block did + // `process.exit(1)` unconditionally, BEFORE the --state-backend block. + // Iter-4 refactors so EPERM defers when --state-backend is requested. + const entrySrc = readFileSync( + path.join(repoRoot, 'packages', 'squad-cli', 'src', 'cli-entry.ts'), + 'utf-8', + ); + // The selfUpgradeFailed deferred path is the marker that the iter-4 + // refactor landed. + expect(entrySrc).toContain('selfUpgradeFailed'); + expect(entrySrc).toMatch(/Continuing with --state-backend migration/); + }); + + it('built CLI binary exists (skipped if not built; sanity check for end-to-end run)', () => { + if (!existsSync(cliEntry)) { + // Built artifacts not present; this is acceptable in dev runs where + // only `tsc` for the SDK was run. The static check above is the + // authoritative regression guard. + return; + } + // If built, we can at least verify the CLI doesn't crash on --help. + const out = spawnSync(process.execPath, [cliEntry, '--help'], { + encoding: 'utf-8', + timeout: 20000, + }); + expect(out.status).toBe(0); + }); +}); diff --git a/test/upgrade-state-backend.test.ts b/test/upgrade-state-backend.test.ts new file mode 100644 index 000000000..8accf3194 --- /dev/null +++ b/test/upgrade-state-backend.test.ts @@ -0,0 +1,154 @@ +/** + * Regression test for the upgrade state-backend flow: + * - UPGRADE-FLAG-IGNORED: `--state-backend` must update config.json without + * duplicate keys. + * - UPGRADE-NO-MIGRATION: pre-existing `.squad/decisions.md` and agent + * histories must be carried onto the squad-state orphan branch. + * - WI-1: hook set (incl. pre-commit + post-commit) must be installed after + * migration. + * + * Evidence: + * .squad/files/validation/TWOLAYER-BASELINE-INSIDER3-CONSOLIDATED.md (data-5) + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { migrateStateBackend } from '../packages/squad-cli/src/cli/commands/migrate-backend.js'; + +function mkRepo(backend: string): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-upgrade-mig-')); + execFileSync('git', ['init', '--quiet', '-b', 'main'], { cwd: dir }); + execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: dir }); + execFileSync('git', ['config', 'user.name', 'Squad UpgradeTest'], { cwd: dir }); + // Seed an initial commit so HEAD exists and orphan creation works. + fs.writeFileSync(path.join(dir, 'README.md'), '# test\n'); + execFileSync('git', ['add', 'README.md'], { cwd: dir }); + execFileSync('git', ['commit', '-q', '-m', 'init'], { cwd: dir }); + + fs.mkdirSync(path.join(dir, '.squad', 'agents', 'data'), { recursive: true }); + fs.writeFileSync( + path.join(dir, '.squad', 'config.json'), + JSON.stringify({ stateBackend: backend, teamRoot: '.' }, null, 2), + ); + return dir; +} + +function cleanup(dir: string): void { + try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best-effort */ } +} + +describe('squad upgrade --state-backend migration', () => { + let dir: string; + afterEach(() => dir && cleanup(dir)); + + it('UPGRADE-FLAG-IGNORED: writes stateBackend to config.json with no duplicate keys', { timeout: 30_000 }, async () => { + dir = mkRepo('worktree'); + await migrateStateBackend(dir, 'two-layer'); + + const raw = fs.readFileSync(path.join(dir, '.squad', 'config.json'), 'utf-8'); + const parsed = JSON.parse(raw); + expect(parsed.stateBackend).toBe('two-layer'); + // Bug E guard: only one occurrence of "stateBackend" in the raw text. + const occurrences = (raw.match(/"stateBackend"/g) || []).length; + expect(occurrences).toBe(1); + }); + + it('UPGRADE-FLAG-IGNORED (clean target): writes stateBackend when config.json has no stateBackend field', { timeout: 30_000 }, async () => { + // Regression for the original bug: an older squad install has config.json + // with no stateBackend field at all. `squad upgrade --state-backend two-layer` + // must add the field rather than silently drop it. + dir = mkRepo('worktree'); + // Remove stateBackend so config only has other fields (e.g. teamRoot). + const configPath = path.join(dir, '.squad', 'config.json'); + const existing = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + delete existing['stateBackend']; + fs.writeFileSync(configPath, JSON.stringify(existing, null, 2) + '\n'); + + await migrateStateBackend(dir, 'two-layer'); + + const raw = fs.readFileSync(configPath, 'utf-8'); + const parsed = JSON.parse(raw); + expect(parsed.stateBackend).toBe('two-layer'); + expect(parsed['teamRoot']).toBe('.'); + const occurrences = (raw.match(/"stateBackend"/g) || []).length; + expect(occurrences).toBe(1); + }); + + it('WI-1: installs commit hooks after backend migration', { timeout: 30_000 }, async () => { + dir = mkRepo('worktree'); + await migrateStateBackend(dir, 'two-layer'); + + for (const h of ['pre-push', 'post-merge', 'post-rewrite', 'post-checkout', 'pre-commit', 'post-commit']) { + expect(fs.existsSync(path.join(dir, '.git', 'hooks', h)), `hook ${h} should exist`).toBe(true); + } + }); + + it('UPGRADE-NO-MIGRATION: copies decisions.md + agent history.md onto squad-state branch', { timeout: 30_000 }, async () => { + dir = mkRepo('worktree'); + fs.writeFileSync( + path.join(dir, '.squad', 'decisions.md'), + '# Squad Decisions\n\n## D1 — pre-upgrade decision\n\nKeep this.\n', + ); + fs.writeFileSync( + path.join(dir, '.squad', 'agents', 'data', 'history.md'), + '# Data history\n\n- entry 1\n', + ); + + await migrateStateBackend(dir, 'two-layer'); + + // Verify orphan branch contains the migrated files. + const decisionsOnBranch = execFileSync( + 'git', ['show', 'refs/heads/squad-state:decisions.md'], + { cwd: dir, encoding: 'utf-8' }, + ); + expect(decisionsOnBranch).toContain('pre-upgrade decision'); + + const historyOnBranch = execFileSync( + 'git', ['show', 'refs/heads/squad-state:agents/data/history.md'], + { cwd: dir, encoding: 'utf-8' }, + ); + expect(historyOnBranch).toContain('entry 1'); + }); + + it('F1 (Round 5): migrated working-tree state files are removed after upgrade', { timeout: 30_000 }, async () => { + dir = mkRepo('worktree'); + fs.writeFileSync( + path.join(dir, '.squad', 'decisions.md'), + '# Squad Decisions\n\n## D1 — pre-upgrade decision\n', + ); + fs.writeFileSync( + path.join(dir, '.squad', 'agents', 'data', 'history.md'), + '# Data history\n', + ); + // A static file that must NOT be touched by the cleanup. + fs.writeFileSync( + path.join(dir, '.squad', 'charter.md'), + '# Charter\nStatic content — do not delete on upgrade.\n', + ); + + await migrateStateBackend(dir, 'two-layer'); + + // Working-tree mutable state must be gone (orphan branch is authoritative). + expect(fs.existsSync(path.join(dir, '.squad', 'decisions.md'))).toBe(false); + expect(fs.existsSync(path.join(dir, '.squad', 'agents', 'data', 'history.md'))).toBe(false); + // The now-empty agent directory should also be cleaned up. + expect(fs.existsSync(path.join(dir, '.squad', 'agents', 'data'))).toBe(false); + // Static / config files must remain untouched. + expect(fs.existsSync(path.join(dir, '.squad', 'charter.md'))).toBe(true); + expect(fs.existsSync(path.join(dir, '.squad', 'config.json'))).toBe(true); + }); + + it('migration is idempotent: re-running with same target does not duplicate config or fail', { timeout: 30_000 }, async () => { + dir = mkRepo('worktree'); + await migrateStateBackend(dir, 'two-layer'); + await migrateStateBackend(dir, 'two-layer'); // no-op path + + const raw = fs.readFileSync(path.join(dir, '.squad', 'config.json'), 'utf-8'); + const occurrences = (raw.match(/"stateBackend"/g) || []).length; + expect(occurrences).toBe(1); + expect(JSON.parse(raw).stateBackend).toBe('two-layer'); + }); +}); diff --git a/test/watch-notes-promote.test.ts b/test/watch-notes-promote.test.ts new file mode 100644 index 000000000..b392bcd91 --- /dev/null +++ b/test/watch-notes-promote.test.ts @@ -0,0 +1,99 @@ +/** + * NotesPromoteCapability — Ralph heartbeat integration test (Round 5, P0.3 A3 path B). + * + * Verifies the watch capability: + * - Skips cleanly when the backend is not two-layer. + * - Promotes flagged squad notes when running on a two-layer repo. + * - Is idempotent (subsequent rounds find nothing to promote). + */ + +import { describe, it, expect, afterEach } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { NotesPromoteCapability } from '../packages/squad-cli/src/cli/commands/watch/capabilities/notes-promote.js'; +import type { WatchContext } from '../packages/squad-cli/src/cli/commands/watch/types.js'; + +function mkRepo(backend: string): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-cap-promote-')); + execFileSync('git', ['init', '--quiet', '-b', 'main'], { cwd: dir }); + execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: dir }); + execFileSync('git', ['config', 'user.name', 'Squad CapTest'], { cwd: dir }); + fs.writeFileSync(path.join(dir, 'README.md'), '# test\n'); + execFileSync('git', ['add', 'README.md'], { cwd: dir }); + execFileSync('git', ['commit', '-q', '-m', 'init'], { cwd: dir }); + fs.mkdirSync(path.join(dir, '.squad'), { recursive: true }); + fs.writeFileSync( + path.join(dir, '.squad', 'config.json'), + JSON.stringify({ stateBackend: backend, teamRoot: '.' }, null, 2), + ); + return dir; +} + +function makeContext(teamRoot: string, round = 1, config: Record = {}): WatchContext { + return { + teamRoot, + adapter: {} as WatchContext['adapter'], + round, + roster: [], + config, + }; +} + +describe('NotesPromoteCapability', () => { + let dir = ''; + afterEach(() => { + if (dir) { + try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best-effort */ } + } + dir = ''; + }); + + const cap = new NotesPromoteCapability(); + + it('has expected metadata', () => { + expect(cap.name).toBe('notes-promote'); + expect(cap.phase).toBe('housekeeping'); + expect(cap.configShape).toBe('object'); + }); + + it('preflight fails when backend is not two-layer', { timeout: 30_000 }, async () => { + dir = mkRepo('worktree'); + const result = await cap.preflight(makeContext(dir)); + expect(result.ok).toBe(false); + expect(result.reason).toContain("not 'two-layer'"); + }); + + it('preflight succeeds on a two-layer repo', { timeout: 30_000 }, async () => { + dir = mkRepo('two-layer'); + const result = await cap.preflight(makeContext(dir)); + expect(result.ok).toBe(true); + }); + + it('execute promotes flagged notes on a two-layer repo', { timeout: 30_000 }, async () => { + dir = mkRepo('two-layer'); + execFileSync( + 'git', ['notes', '--ref=squad/picard', 'add', '-f', '-m', + JSON.stringify({ promote_to_permanent: true, decision: 'D1' }), 'HEAD'], + { cwd: dir }, + ); + + const result = await cap.execute(makeContext(dir)); + expect(result.success).toBe(true); + expect(result.summary).toMatch(/promoted=1/); + expect((result.data as { promoted: number }).promoted).toBe(1); + + // Second run: nothing left — idempotent. + const again = await cap.execute(makeContext(dir)); + expect(again.success).toBe(true); + expect((again.data as { promoted: number }).promoted).toBe(0); + }); + + it('respects everyNRounds throttle', { timeout: 30_000 }, async () => { + dir = mkRepo('two-layer'); + // round=2 with everyNRounds=5 → skipped. + const result = await cap.execute(makeContext(dir, 2, { everyNRounds: 5 })); + expect(result.summary).toContain('skipped'); + }); +});