diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 0e2f127..533353b 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "second-brain", "source": "./", "description": "Self-evolving AI second brain. Auto-learns from sessions, discovers tools, maintains a local knowledge base, and self-critiques code quality.", - "version": "0.24.11" + "version": "0.24.12" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index b7e9a7b..72d3bf9 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "second-brain", "description": "Self-evolving AI second brain. Automatically learns from sessions, discovers tools, maintains a local knowledge base, and self-critiques code quality — getting smarter with every interaction.", - "version": "0.24.11", + "version": "0.24.12", "author": { "name": "second-brain" }, diff --git a/docs/plans/2026-06-03-setup-deep-scan.md b/docs/plans/2026-06-03-setup-deep-scan.md new file mode 100644 index 0000000..51f25fa --- /dev/null +++ b/docs/plans/2026-06-03-setup-deep-scan.md @@ -0,0 +1,455 @@ +# SP-3 Setup Deep-Scan Implementation Plan + +> **For agentic workers:** Implement this plan task-by-task following TDD. Steps use checkbox (`- [ ]`) syntax for tracking. See `second-brain:test-driven-development` and `second-brain:verification-before-completion`. + +**Goal:** At setup, deep-scan the repo for high-signal knowledge docs and capture them into the project's raw inbox (`captured_by: setup-scan`), with a dry-run preview, a cap, and content-hash dedup — reusing `doc-sources` filtering and SP-2's `captureItem`. + +**Architecture:** A pure `raw-scan.ts` module globs `**/*.{md,markdown}`, applies a curation heuristic (high-signal include rules) plus low-signal/secret denylists plus `doc-sources.filterIgnored` (junk + git-ignore), caps at `SB_SCAN_MAX`, and captures survivors via SP-2 `captureItem`. A thin `raw-scan-cli` exposes `--dry-run`/capture; the `setup` skill runs it (preview → confirm → capture). + +**Tech Stack:** TypeScript (esbuild ESM, Node 20), vitest, the `glob` dep, POSIX bash (mawk-safe), git. + +**Spec:** `docs/specs/2026-06-03-setup-deep-scan-design.md` + +--- + +## File Structure + +| File | Responsibility | Action | +|---|---|---| +| `mcp/src/tools/doc-sources.ts` | export `filterIgnored` (reuse junk + git-ignore drop) | Modify (1 line) | +| `mcp/src/tools/raw-scan.ts` | pure: `scanCandidates`, `scanCap`, `runScan` | Create | +| `mcp/src/tools/raw-scan.test.ts` | vitest | Create | +| `mcp/src/tools/raw-scan-cli.ts` | thin CLI (`--dry-run` / capture) | Create | +| `mcp/package.json` | register the esbuild bundle | Modify | +| `skills/setup/SKILL.md` | new scan step + `Bash(node *)` allowed-tool | Modify | +| `tests/test-setup-scan.sh` | CLI/skill end-to-end (incl. git-ignore) | Create | + +Reused as-is: `captureItem` (SP-2), `assertSafeSlug`/`hashContent` (already exported), the `glob` dep, the `CapturedBy` union (`'setup-scan'` already valid — no schema change). + +--- + +## Task 1: `raw-scan.ts` curation + capture module + +**Files:** +- Modify: `mcp/src/tools/doc-sources.ts` (export `filterIgnored`) +- Create: `mcp/src/tools/raw-scan.ts` +- Test: `mcp/src/tools/raw-scan.test.ts` + +- [ ] **Step 1: Export `filterIgnored`.** In `mcp/src/tools/doc-sources.ts` line 52, change `function filterIgnored(` to `export function filterIgnored(`. + +- [ ] **Step 2: Write the failing test.** Create `mcp/src/tools/raw-scan.test.ts`: + +```typescript +import { describe, it, expect } from 'vitest'; +import { promises as fs } from 'fs'; +import { join } from 'path'; +import { tmpdir } from 'os'; +import { scanCandidates, runScan, scanCap } from './raw-scan.js'; +import { listItems } from './raw-inbox.js'; + +/** Build a temp repo with a known file set; returns its root. NOT a git repo (junk-filter only). */ +async function repo(): Promise { + const root = await fs.mkdtemp(join(tmpdir(), 'scan-')); + // Distinct body per file — captureItem dedups by content hash, so identical bodies + // would collapse to one captured item and break the capture-count assertion. + const w = async (rel: string, body = `# ${rel}\nunique content for ${rel}`) => { + await fs.mkdir(join(root, rel, '..'), { recursive: true }); + await fs.writeFile(join(root, rel), body); + }; + await w('README.md'); // rule 1 (root) + await w('docs/guide.md'); // rule 2 (docs dir) + await w('docs/adr/ADR-001.md'); // rule 2 (adr dir) + await w('src/DESIGN.md'); // rule 3 (basename) + await w('src/components/notes.md'); // EXCLUDED: file named notes, not a notes/ dir + await w('CHANGELOG.md'); // EXCLUDED: low-signal denylist + await w('docs/credentials.md'); // EXCLUDED: secret denylist + await w('node_modules/pkg/README.md'); // EXCLUDED: junk dir + return root; +} + +async function brain(): Promise<{ brainDir: string; slug: string }> { + const brainDir = await fs.mkdtemp(join(tmpdir(), 'scan-brain-')); + const slug = 'demo'; + await fs.mkdir(join(brainDir, 'projects', slug), { recursive: true }); + return { brainDir, slug }; +} + +function rels(root: string, paths: string[]): string[] { + return paths.map(p => p.slice(root.length + 1).split('\\').join('/')); +} + +describe('raw-scan', () => { + it('curates high-signal docs and excludes notes/changelog/secret/junk', async () => { + const root = await repo(); + const got = rels(root, await scanCandidates(root)).sort(); + expect(got).toEqual(['README.md', 'docs/adr/ADR-001.md', 'docs/guide.md', 'src/DESIGN.md']); + }); + + it('scanCap reads SB_SCAN_MAX (default 50)', () => { + delete process.env.SB_SCAN_MAX; + expect(scanCap()).toBe(50); + process.env.SB_SCAN_MAX = '2'; + expect(scanCap()).toBe(2); + delete process.env.SB_SCAN_MAX; + }); + + it('dryRun previews (capped) and writes nothing', async () => { + const root = await repo(); + const { brainDir, slug } = await brain(); + process.env.SB_SCAN_MAX = '2'; + const r = await runScan(root, brainDir, slug, { dryRun: true }); + delete process.env.SB_SCAN_MAX; + expect(r.candidates).toHaveLength(2); + expect(r.truncated).toBe(2); // 4 high-signal, cap 2 + expect(r.captured).toBe(0); + expect(await listItems(brainDir, slug)).toHaveLength(0); // nothing written + }); + + it('captures survivors stamped captured_by: setup-scan, and dedups on re-run', async () => { + const root = await repo(); + const { brainDir, slug } = await brain(); + const r1 = await runScan(root, brainDir, slug, {}); + expect(r1.captured).toBe(4); + const items = await listItems(brainDir, slug); + expect(items).toHaveLength(4); + expect(items.every(i => i.captured_by === 'setup-scan')).toBe(true); + const r2 = await runScan(root, brainDir, slug, {}); // re-run: unchanged → all skipped + expect(r2.captured).toBe(0); + expect(r2.skipped).toBe(4); + expect(await listItems(brainDir, slug)).toHaveLength(4); + }); +}); +``` + +- [ ] **Step 3: Run it to verify it fails.** + +Run: `cd mcp && npx vitest run raw-scan.test.ts` +Expected: FAIL — `Cannot find module './raw-scan.js'`. + +- [ ] **Step 4: Implement `mcp/src/tools/raw-scan.ts`.** + +```typescript +import { resolve, relative, sep } from 'path'; +import { glob } from 'glob'; +import { filterIgnored, assertSafeSlug } from './doc-sources.js'; +import { captureItem } from './raw-inbox.js'; + +const DOC_DIRS = new Set(['docs', 'doc', 'adr', 'adrs', 'rfc', 'rfcs', 'spec', 'specs', 'decisions', '.ai-docs', 'notes']); +const NAME_INCLUDE = /^(readme|architecture|design|contributing|roadmap)/i; // basename (sans ext) +const LOW_SIGNAL = /^(changelog|license|licence|code_of_conduct)/i; // basename (sans ext) +const TEMPLATE_RE = /template/i; // basename +const SECRET_RE = /(^|\/)\.env|\.pem$|\.key$|id_rsa|secret|credential/i; // full rel path + +/** A repo-relative markdown path is high-signal iff it matches an include rule and no denylist. */ +function isHighSignal(rel: string): boolean { + const segs = rel.split('/'); + const file = segs[segs.length - 1]; + if (!/\.(md|markdown)$/i.test(file)) return false; + const baseName = file.replace(/\.(md|markdown)$/i, ''); + const dirs = segs.slice(0, -1).map(s => s.toLowerCase()); + const include = segs.length === 1 // rule 1: root-level *.md + || dirs.some(d => DOC_DIRS.has(d)) // rule 2: a directory segment is a doc dir + || NAME_INCLUDE.test(baseName); // rule 3: high-signal basename anywhere + if (!include) return false; + if (LOW_SIGNAL.test(baseName) || TEMPLATE_RE.test(file)) return false; // low-signal + if (SECRET_RE.test(rel)) return false; // secret defense-in-depth + return true; +} + +/** Max items captured per scan (SB_SCAN_MAX, default 50). */ +export function scanCap(): number { + const n = parseInt(process.env.SB_SCAN_MAX ?? '', 10); + return Number.isFinite(n) && n >= 0 ? n : 50; +} + +/** Walk the repo for high-signal markdown docs (junk + git-ignored dropped). Sorted, uncapped. */ +export async function scanCandidates(projectRoot: string): Promise { + const root = resolve(projectRoot); + const matches = await glob('**/*.{md,markdown}', { cwd: root, absolute: true, nodir: true }).catch(() => [] as string[]); + const within = matches.filter(p => { const r = resolve(p); return r === root || r.startsWith(root + sep); }); + const highSignal = within.filter(p => isHighSignal(relative(root, p))); + const kept = filterIgnored(root, highSignal); // drops JUNK_DIRS + `git check-ignore` paths + kept.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); // byte-stable, locale-independent + return kept; +} + +export interface ScanResult { candidates: string[]; captured: number; skipped: number; truncated: number; } + +/** Scan + (unless dryRun) capture each candidate into the raw inbox as `setup-scan` material. */ +export async function runScan(projectRoot: string, brainDir: string, slug: string, + opts: { dryRun?: boolean }): Promise { + assertSafeSlug(slug); + const all = await scanCandidates(projectRoot); + const cap = scanCap(); + const candidates = all.slice(0, cap); + const truncated = Math.max(0, all.length - cap); + if (opts.dryRun) return { candidates, captured: 0, skipped: 0, truncated }; + let captured = 0, skipped = 0; + for (const src of candidates) { + try { + const r = await captureItem({ brainDir, slug, kind: 'file', source: src, capturedBy: 'setup-scan' }); + if (r.duplicate) skipped++; else captured++; + } catch { skipped++; } // unreadable → skip, never abort the scan + } + return { candidates, captured, skipped, truncated }; +} +``` + +- [ ] **Step 5: Run the tests to verify they pass.** + +Run: `cd mcp && npx vitest run raw-scan.test.ts` +Expected: PASS (4 tests). + +- [ ] **Step 6: Run the full vitest suite (the `filterIgnored` export is the only shared change).** + +Run: `cd mcp && npx vitest run` +Expected: all green. + +- [ ] **Step 7: Commit.** + +```bash +git add mcp/src/tools/doc-sources.ts mcp/src/tools/raw-scan.ts mcp/src/tools/raw-scan.test.ts +git commit -m "feat(kb): raw-scan curation + capture module (SP-3 Task 1)" +``` + +--- + +## Task 2: `raw-scan-cli` thin bundle + +**Files:** +- Create: `mcp/src/tools/raw-scan-cli.ts` +- Modify: `mcp/package.json` (esbuild registration) + +- [ ] **Step 1: Write the CLI.** Create `mcp/src/tools/raw-scan-cli.ts`: + +```typescript +import { homedir } from 'os'; +import { join, basename, relative } from 'path'; +import { existsSync, readFileSync } from 'fs'; +import { runScan } from './raw-scan.js'; + +function resolveSlug(brainDir: string): string | undefined { + if (process.env.SB_ACTIVE_SLUG) return process.env.SB_ACTIVE_SLUG; + try { + const pin = readFileSync(join(brainDir, '.active-session-slug'), 'utf-8').trim(); + if (pin && existsSync(join(brainDir, 'projects', pin, 'PROJECT.md'))) return pin; + } catch { /* no pin */ } + const base = basename(process.cwd()); + return base && base !== '/' && base !== '.' && base !== '..' ? base : undefined; +} + +async function main(): Promise { + const brainDir = process.env.BRAIN_DIR || join(homedir(), '.second-brain'); + const projectRoot = process.env.SCAN_ROOT || process.cwd(); + const slug = resolveSlug(brainDir); + if (!slug) { console.log('scan: could not resolve the active project. cd into a project.'); return; } + const dryRun = process.argv.includes('--dry-run'); + try { + const r = await runScan(projectRoot, brainDir, slug, { dryRun }); + if (dryRun) { + const more = r.truncated ? ` (+${r.truncated} over the SB_SCAN_MAX cap)` : ''; + console.log(`${r.candidates.length} high-signal doc(s) to capture into ${slug}'s raw inbox${more}:`); + for (const p of r.candidates) console.log(` - ${relative(projectRoot, p)}`); + if (r.candidates.length === 0) console.log(' (no high-signal docs found)'); + } else { + const more = r.truncated ? `, ${r.truncated} over the cap (raise SB_SCAN_MAX or /second-brain:track them)` : ''; + console.log(`Captured ${r.captured}, skipped ${r.skipped} already-in-inbox${more}. Review: /second-brain:capture --list`); + } + } catch (e) { + console.log(`scan error: ${e instanceof Error ? e.message : String(e)}`); + } +} + +main(); +``` + +- [ ] **Step 2: Register the esbuild bundle.** In `mcp/package.json`, the `"bundle"` script ends with the `raw-capture-cli` entry. Append one more after it (identical flags, new entry name): + +``` + && esbuild src/tools/raw-scan-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/raw-scan-cli.bundle.js +``` + +- [ ] **Step 3: Build to verify it compiles + bundles.** + +Run: `cd mcp && npm run build` +Expected: build succeeds; `ls dist/tools/raw-scan-cli.bundle.js` exists. + +- [ ] **Step 4: Smoke-test the CLI by hand.** + +Run: +```bash +T=$(mktemp -d); mkdir -p "$T/projects/demo"; : > "$T/projects/demo/PROJECT.md" +R=$(mktemp -d); printf '# Readme\nx\n' > "$R/README.md"; mkdir -p "$R/docs"; printf '# G\nx\n' > "$R/docs/guide.md" +BRAIN_DIR="$T" SB_ACTIVE_SLUG=demo SCAN_ROOT="$R" node mcp/dist/tools/raw-scan-cli.bundle.js --dry-run +BRAIN_DIR="$T" SB_ACTIVE_SLUG=demo SCAN_ROOT="$R" node mcp/dist/tools/raw-scan-cli.bundle.js +rm -rf "$T" "$R" +``` +Expected: dry-run lists `README.md` + `docs/guide.md`; capture prints `Captured 2, skipped 0 already-in-inbox.` + +- [ ] **Step 5: Commit.** + +```bash +git add mcp/src/tools/raw-scan-cli.ts mcp/package.json mcp/dist +git commit -m "feat(kb): raw-scan-cli bundle (SP-3 Task 2)" +``` + +--- + +## Task 3: `setup` scan step + end-to-end test + +**Files:** +- Modify: `skills/setup/SKILL.md` +- Test: `tests/test-setup-scan.sh` + +- [ ] **Step 1: Write the failing bash test.** Create `tests/test-setup-scan.sh`: + +```bash +#!/bin/bash +# End-to-end: raw-scan-cli previews + captures high-signal docs into a project's raw inbox, +# honors git-ignore, dedups on re-run; the setup skill wires the CLI. +set -u +ROOT="$(cd "$(dirname "$0")"/.. && pwd)" +CLI="$ROOT/mcp/dist/tools/raw-scan-cli.bundle.js" +SKILL="$ROOT/skills/setup/SKILL.md" +fail(){ echo "FAIL: $1"; exit 1; }; pass(){ echo "PASS: $1"; } + +grep -q 'raw-scan-cli.bundle.js' "$SKILL" || fail "setup skill does not invoke raw-scan-cli" +grep -qE 'allowed-tools:.*Bash\(node \*\)' "$SKILL" || fail "setup skill missing Bash(node *) allowed-tool" +pass "setup skill wires the scan CLI" + +command -v node >/dev/null 2>&1 || { echo "SKIP: node"; echo; echo "ALL PASS"; exit 0; } +[ -f "$CLI" ] || { echo "SKIP: CLI bundle not built"; echo; echo "ALL PASS"; exit 0; } +command -v git >/dev/null 2>&1 || { echo "SKIP: git"; echo; echo "ALL PASS"; exit 0; } + +T=$(mktemp -d); export BRAIN_DIR="$T" SB_ACTIVE_SLUG=demo +mkdir -p "$T/projects/demo"; : > "$T/projects/demo/PROJECT.md" +R=$(mktemp -d) +( cd "$R" && git init -q && git config user.email t@t && git config user.name t ) +printf '# Readme\nx\n' > "$R/README.md" +mkdir -p "$R/docs"; printf '# G\nx\n' > "$R/docs/guide.md"; printf '# sec\nx\n' > "$R/docs/secret-ignored.md" +printf 'docs/secret-ignored.md\n' > "$R/.gitignore" # git-ignored → must be excluded + +OUT=$(SCAN_ROOT="$R" node "$CLI" --dry-run) +echo "$OUT" | grep -q 'README.md' || fail "dry-run missing README.md ($OUT)" +echo "$OUT" | grep -q 'docs/guide.md' || fail "dry-run missing docs/guide.md" +echo "$OUT" | grep -q 'secret-ignored' && fail "dry-run included a git-ignored file" +[ -z "$(ls -A "$T/projects/demo/raw" 2>/dev/null)" ] || fail "dry-run wrote items" +pass "dry-run previews high-signal docs, excludes git-ignored, writes nothing" + +OUT=$(SCAN_ROOT="$R" node "$CLI") +echo "$OUT" | grep -q 'Captured 2, skipped 0' || fail "capture count wrong ($OUT)" +grep -lq '^captured_by: setup-scan$' "$T/projects/demo/raw"/*.md || fail "items not stamped setup-scan" +pass "capture writes 2 setup-scan items" + +OUT=$(SCAN_ROOT="$R" node "$CLI") +echo "$OUT" | grep -q 'Captured 0, skipped 2' || fail "re-run not idempotent ($OUT)" +pass "re-run dedups (idempotent)" + +rm -rf "$T" "$R" +echo; echo "ALL PASS" +``` + +- [ ] **Step 2: Run it to verify it fails.** + +Run: `bash tests/test-setup-scan.sh` +Expected: FAIL — `setup skill does not invoke raw-scan-cli`. + +- [ ] **Step 3a: Add `Bash(node *)` to the setup skill's allowed-tools.** In `skills/setup/SKILL.md` line 6, append ` Bash(node *)` to the end of the `allowed-tools:` line. + +- [ ] **Step 3b: Insert the scan step.** In `skills/setup/SKILL.md`, replace the `### 6. Confirm` heading line with the new scan step followed by a renumbered Confirm: + +```markdown +### 6. Deep-scan the repo into the raw inbox (preview, then confirm) + +Seed this project's KB by capturing its **high-signal docs** (README, `docs/`, ADRs, +`DESIGN.md`, …) into the raw inbox, where the maintainer later refines them into wiki +notes. Skipped entirely if `SB_SCAN_SKIP=1`. Curation reuses the doc-sources junk + +git-ignore filtering and a low-signal/secret denylist; the inbox dedups by content hash, +so re-running setup only captures new or changed docs. + +```bash +if [ "${SB_SCAN_SKIP:-0}" != "1" ]; then + SCAN_CLI="${CLAUDE_PLUGIN_ROOT}/mcp/dist/tools/raw-scan-cli.bundle.js" + SCAN_ROOT_DIR=$(git rev-parse --show-toplevel 2>/dev/null || pwd) + SCAN_ROOT="$SCAN_ROOT_DIR" node "$SCAN_CLI" --dry-run +fi +``` + +Show the preview list. This writes nothing yet. **Ask the user to confirm** capturing +these into the raw inbox (an impactful action). On a yes: + +```bash +SCAN_ROOT="$SCAN_ROOT_DIR" node "$SCAN_CLI" +``` + +Report the captured/skipped counts and point the user to `/second-brain:capture --list`. +If the preview was empty, say there were no high-signal docs to seed and move on. + +### 7. Confirm +``` + +- [ ] **Step 4: Build the CLI bundle (needed for the e2e), then run the test to verify it passes.** + +Run: `cd mcp && npm run build && cd .. && bash tests/test-setup-scan.sh` +Expected: `ALL PASS`. + +- [ ] **Step 5: Commit.** + +```bash +git add skills/setup/SKILL.md tests/test-setup-scan.sh +git commit -m "feat(kb): setup deep-scan step — seed the raw inbox (SP-3 Task 3)" +``` + +--- + +## Task 4: Ship — gate, version bump, PR + +- [ ] **Step 1: Branch (if not already).** `git checkout -b feat/sp3-setup-deep-scan` (do all of SP-3 on this branch from the start). + +- [ ] **Step 2: Build + full suite.** `cd mcp && npm run build && cd .. && bash tests/run-all.sh` → expect `ALL GREEN` (includes `test-setup-scan`, `test-mcp-typecheck`). + +- [ ] **Step 3: Deep-review gate.** Run `second-brain:code-review-deep --base main`. Fix any confirmed (≥70) finding with TDD; re-run until clean. + +- [ ] **Step 4: Version bump + migration row.** + - `.claude-plugin/plugin.json` + `.claude-plugin/marketplace.json`: `0.24.11` → `0.24.12`. + - `skills/upgrade/SKILL.md`: add a `**0.24.12**` row above `**0.24.11**` (setup deep-scan: curated high-signal repo docs → raw inbox via `/second-brain:setup`'s new step, reusing `filterIgnored` + SP-2 `captureItem`; dry-run preview + confirm; `SB_SCAN_MAX=50`, `SB_SCAN_SKIP=1`; no MCP server tool change, server stays 2.6.4; additive — opt-in via the setup confirm). + +- [ ] **Step 5: Rebuild + verify lockstep + migration-row test.** + +```bash +cd mcp && npm run build && cd .. +bash scripts/validate-plugin.sh +bash tests/test-upgrade-migration-row.sh +``` +Expected: `OK: all plugin files valid` + `PASS: upgrade migration row present for 0.24.12`. + +- [ ] **Step 6: Commit + PR + merge.** + +```bash +git add -A +git commit -m "chore(release): setup deep-scan (SP-3) — bump 0.24.12 + migration row" +git push -u origin feat/sp3-setup-deep-scan +gh pr create --base main --title "feat(kb): setup deep-scan (SP-3)" --body "" +gh pr merge --merge --delete-branch +git checkout main && git pull --ff-only origin main +``` + +--- + +## Self-Review + +**1. Spec coverage:** +- Curation heuristic (rules 1–3 + low-signal + secret + junk/git-ignore) → Task 1 `isHighSignal` + `scanCandidates`. ✓ +- Reuse `filterIgnored` → Task 1 Step 1 export + use. ✓ +- Cap `SB_SCAN_MAX=50` + truncated report → Task 1 `scanCap`/`runScan`. ✓ +- Capture via SP-2 `captureItem` (`captured_by: setup-scan`) → Task 1 `runScan`. ✓ +- Dedup on re-run → Task 1 test + Task 3 e2e. ✓ +- Dry-run preview → Task 1 `runScan({dryRun})` + Task 2 CLI + Task 3 setup step. ✓ +- Folded into setup, confirm-first, `SB_SCAN_SKIP` → Task 3. ✓ +- No new server tool / no schema change → no server.ts/kb-schema edits in any task. ✓ +- Cross-OS (glob + path; bash mawk-safe) → Task 1/2 use `glob`/`path`; Task 3 bash has no awk. ✓ +- git-ignore tested in bash e2e; vitest hermetic (junk only) → Task 1 test (no git) + Task 3 test (`git init`). ✓ + +**2. Placeholder scan:** Task 4 Step 6 `--body ""` is filled at ship time (write a real summary like SP-2's). No other placeholders; all code complete. + +**3. Type consistency:** `scanCandidates(projectRoot): Promise`, `scanCap(): number`, `runScan(projectRoot, brainDir, slug, {dryRun}): Promise` are identical across Task 1 (impl), Task 1 (test), and Task 2 (CLI). `ScanResult` fields (`candidates`, `captured`, `skipped`, `truncated`) match every consumer. `captureItem({kind:'file', source, capturedBy:'setup-scan'})` matches SP-2's `CaptureInput`. The CLI's `resolveSlug` mirrors `raw-capture-cli` (incl. the `..` guard). diff --git a/docs/specs/2026-06-03-setup-deep-scan-design.md b/docs/specs/2026-06-03-setup-deep-scan-design.md new file mode 100644 index 0000000..98ad2df --- /dev/null +++ b/docs/specs/2026-06-03-setup-deep-scan-design.md @@ -0,0 +1,139 @@ +# SP-3 — Setup Deep-Scan — Design + +**Status:** approved (2026-06-03) +**Vision:** consolidation roadmap — sub-project SP-3 of 6 (SP-0 Four Principles ✓, SP-1 project-scoped serving ✓, SP-2 raw inbox ✓). +**Scope chosen:** *Curated high-signal docs → raw inbox.* The deep-scan walks the repo, selects high-signal knowledge prose, and captures it into the project's raw inbox (`captured_by: setup-scan`) for the maintainer (SP-4) to refine into wiki nodes. Folded into the existing `setup` command (the vision keeps two commands: `setup` + `upgrade`). + +--- + +## Problem + +SP-2 built the raw inbox but it starts empty — material only arrives via the manual `/second-brain:capture`. A brand-new project has a wealth of already-written knowledge (README, `docs/`, ADRs, design notes) that should seed the KB. There is no automated producer that surfaces it. SP-3 is that producer: a one-shot, re-runnable deep-scan at setup time. + +This must NOT duplicate the existing `track`/doc-sources system, which references existing local docs **in place** (read-only, searchable — SP-1 local-docs). SP-3 instead **captures** curated docs **into** the raw inbox (staging for refinement into wiki nodes). Different job, different destination. + +## Goals + +1. At setup, find the repo's high-signal knowledge docs and capture them into the active project's raw inbox. +2. Reuse the existing repo-walk + junk/git-ignore filtering (`doc-sources`) rather than re-implementing it. +3. Safe by default: preview before writing, a cap, content-hash dedup on re-run, and secret avoidance (the threat model is credentials-at-risk / supply-chain P0). +4. Stay under the `setup` command (no third command). + +## Non-goals (deferred) + +- **SP-4** maintainer drain (raw → wiki nodes). +- Non-markdown extraction (PDF/code). SP-3 is markdown-only. +- Auto-registering doc-sources / `track` (the user chose raw-only). +- URL/remote scanning. Local files only (offline-first). + +--- + +## Architecture + +``` +setup skill ──(after hot-tier scaffold)──▶ raw-scan-cli --dry-run ▶ preview list (count + paths) + │ (user confirms) + ▼ + raw-scan-cli ▶ captureItem() per candidate + ↳ ~/.second-brain/projects//raw/ +``` + +The walk + curation + capture live in a pure module (`raw-scan.ts`) behind a thin CLI (`raw-scan-cli.ts`), so the `setup` prompt carries no logic. + +### Components (reuse-heavy) + +| Unit | Responsibility | Action | +|---|---|---| +| `mcp/src/tools/doc-sources.ts` | export `filterIgnored` for reuse (junk + git-ignore drop) | Modify (1 line) | +| `mcp/src/tools/raw-scan.ts` | pure: `scanCandidates(projectRoot)` (glob → curate → filter → cap); `runScan(projectRoot, brainDir, slug, {dryRun})` (capture via `captureItem`) | Create | +| `mcp/src/tools/raw-scan.test.ts` | vitest | Create | +| `mcp/src/tools/raw-scan-cli.ts` | thin CLI: `--dry-run` (preview) / capture; resolves slug | Create | +| `mcp/package.json` | register the esbuild bundle | Modify | +| `skills/setup/SKILL.md` | new scan step (preview → confirm → capture) | Modify | +| `tests/test-setup-scan.sh` | CLI/skill end-to-end | Create | + +--- + +## Curation heuristic + +A repo file is a **candidate** iff it is markdown (`.md`/`.markdown`) AND matches a high-signal include rule: + +1. **root-level** `*.md` (e.g. `README.md`, `ARCHITECTURE.md`, `DESIGN.md` at the repo root), OR +2. lives under a **doc directory** — a *directory* segment of its path (case-insensitive, excludes the filename itself) is one of: `docs` `doc` `adr` `adrs` `rfc` `rfcs` `spec` `specs` `decisions` `.ai-docs` `notes`. (So a `notes/` dir is included, but a *file* `src/components/notes.md` is not.) OR +3. **basename** matches (case-insensitive) `README` `ARCHITECTURE` `DESIGN` `CONTRIBUTING` `ROADMAP` anywhere in the tree. + +Candidates are then filtered (in order): + +- **junk + git-ignored** — `filterIgnored(projectRoot, paths)` (reused from `doc-sources`): drops `JUNK_DIRS` (`node_modules .git .venv venv .next dist build`) and any path `git check-ignore` reports. Degrades correctly outside a git repo (junk-skip only). +- **low-signal denylist** — basename matches (case-insensitive) `CHANGELOG` `LICENSE` `LICENCE` `CODE_OF_CONDUCT` or contains `template`. +- **secret denylist** — path matches (case-insensitive) `.env` `*.pem` `*.key` `id_rsa` `*secret*` `*credential*` (defense-in-depth; markdown secrets are rare but the cost of capturing one is high). + +Survivors are sorted byte-stably by path (same as `scanLocations`) and **capped at `SB_SCAN_MAX` (default 50)**. If more than the cap survive, the first `cap` are taken and the remainder count is reported — never silently dropped. + +A stray `src/components/notes.md` matches none of the include rules → not a candidate. `CHANGELOG.md` is included by rule 1 (root) then dropped by the low-signal denylist. + +--- + +## Capture flow + +`runScan(projectRoot, brainDir, slug, { dryRun })`: + +1. `assertSafeSlug(slug)` (reused). +2. `candidates = scanCandidates(projectRoot)` (the heuristic above). +3. If `dryRun` → return `{ candidates, captured: 0, skipped: 0, truncated }` and write **nothing**. +4. Else, for each candidate, `captureItem({ brainDir, slug, kind: 'file', source: , capturedBy: 'setup-scan' })`. SP-2's content-hash **dedup** makes a re-scan skip any doc whose content is unchanged and already has an unprocessed raw item (counted as `skipped`). New/changed docs are captured (`captured`). +5. Return `{ candidates, captured, skipped, truncated }`. + +`captured_by: 'setup-scan'` is already a valid `CapturedBy` value in SP-2 — no schema change. Items carry no `target_node` (pre-node material; the maintainer creates nodes). + +--- + +## Surface — folded into `setup` + +`skills/setup/SKILL.md` gains a step (after the hot-tier scaffold + `projects.jsonl`): + +1. Run `raw-scan-cli --dry-run` for the active slug → show the candidate count + paths (and the truncated remainder, if any). +2. Ask the user to confirm capturing them (impactful action → explicit confirm; matches the step-by-step preference for writes). +3. On confirm, run `raw-scan-cli` (capture) → report `captured` / `skipped (already in inbox)` and point to `/second-brain:capture --list`. +4. If zero candidates, say so and skip. + +Re-runnable: re-running `setup` re-scans; dedup means only new/changed docs are captured. `SB_SCAN_SKIP=1` (or the user declining at the prompt) skips the scan entirely. + +--- + +## CLI + +`raw-scan-cli.ts` (thin, mirrors `raw-capture-cli`): + +``` +node raw-scan-cli.bundle.js --dry-run # preview: print "N candidate(s):" + paths (+ truncated note) +node raw-scan-cli.bundle.js # capture: print "Captured X, skipped Y (already in inbox)." +``` + +- `projectRoot` = `$SCAN_ROOT` env or `process.cwd()` (setup passes the git root). +- `brainDir` = `BRAIN_DIR` env or `~/.second-brain`. +- slug = resolved like `raw-capture-cli` (`SB_ACTIVE_SLUG` > pin-file + `PROJECT.md` > basename(cwd), rejecting `..`). +- No active slug → print the "cd into a project" message and exit (no capture). + +## Error handling + +- Unreadable file → `captureItem` throws on read → caught per-file, counted as skipped, scan continues (never aborts). +- Not a git repo → `filterIgnored` junk-skips only (no `git check-ignore`). +- Empty repo / zero candidates → `{ candidates: [], captured: 0 }`; setup reports "nothing to seed". +- No active project → refuse (no global inbox), same contract as capture. +- Cap exceeded → capture the first `cap`, report the remainder (the `track` system can index the rest in place if the user wants them searchable). + +## Cross-platform + +Glob via the existing `glob` dep (handles separators). Paths via `path`. `filterIgnored` already spawns `git` portably. No bash-only constructs in the module; the `setup` skill's bash stays mawk-safe/portable. Offline (local reads only). + +## Testing (TDD) + +| Test | Covers | +|---|---| +| `raw-scan.test.ts` (vitest) | curation: includes root `README.md` (rule 1), `docs/guide.md` (rule 2 dir), `docs/adr/ADR-001.md` (rule 2 dir), `src/DESIGN.md` (rule 3 basename); excludes `src/components/notes.md` (file named notes, not a `notes/` dir → no rule matches), `CHANGELOG.md` (root rule 1 but low-signal denylist), a git-ignored `docs/ignored.md`, and `docs/secrets.env` (secret denylist + non-md). Cap at `SB_SCAN_MAX`. `dryRun` writes nothing. capture stamps `captured_by: setup-scan`. re-run dedups an unchanged doc (skipped++). | +| `test-setup-scan.sh` (bash) | CLI `--dry-run` lists candidates without writing; capture writes raw items (grep `captured_by: setup-scan`); re-run is idempotent (count unchanged); `setup/SKILL.md` invokes `raw-scan-cli`. | + +## Versioning + +Plugin patch bump + migration row (additive; the scan is opt-in via the setup confirm). MCP server stays unchanged (capture/scan ride standalone CLI bundles — no new server tool). Back-compat: existing `setup` runs gain a scan step that previews and only writes on confirm; declining leaves behaviour unchanged. diff --git a/mcp/dist/tools/doc-sources.d.ts b/mcp/dist/tools/doc-sources.d.ts index b69d278..b8112d5 100644 --- a/mcp/dist/tools/doc-sources.d.ts +++ b/mcp/dist/tools/doc-sources.d.ts @@ -18,6 +18,8 @@ export interface DocEntry { } export declare function assertSafeSlug(slug: string): void; export declare function readConfig(brainDir: string, slug: string): Promise; +/** Drop junk dirs always; then drop git-ignored paths via `git check-ignore` when in a repo. */ +export declare function filterIgnored(projectRoot: string, absPaths: string[]): string[]; export declare function scanLocations(projectRoot: string, locations: string[]): Promise; export interface DocRegistry { generated_at: string; diff --git a/mcp/dist/tools/doc-sources.d.ts.map b/mcp/dist/tools/doc-sources.d.ts.map index 86c544e..e5cb78b 100644 --- a/mcp/dist/tools/doc-sources.d.ts.map +++ b/mcp/dist/tools/doc-sources.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"doc-sources.d.ts","sourceRoot":"","sources":["../../src/tools/doc-sources.ts"],"names":[],"mappings":"AAMA,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAWnD;AAED,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAEzD;AAED,MAAM,WAAW,eAAe;IAAG,SAAS,EAAE,MAAM,EAAE,CAAC;CAAE;AACzD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IACpD,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;CAC/D;AAID,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAIjD;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAKzF;AAgBD,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CA6BjG;AAED,MAAM,WAAW,WAAW;IAAG,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,EAAE,CAAC;CAAE;AAM5F,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAG9F;AAED;;qDAEqD;AACrD,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAW7G;AAgBD,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGrF;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAWpI;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAQzI"} \ No newline at end of file +{"version":3,"file":"doc-sources.d.ts","sourceRoot":"","sources":["../../src/tools/doc-sources.ts"],"names":[],"mappings":"AAMA,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAWnD;AAED,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAEzD;AAED,MAAM,WAAW,eAAe;IAAG,SAAS,EAAE,MAAM,EAAE,CAAC;CAAE;AACzD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IACpD,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;CAC/D;AAID,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAIjD;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAKzF;AAED,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAW/E;AAED,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CA6BjG;AAED,MAAM,WAAW,WAAW;IAAG,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,EAAE,CAAC;CAAE;AAM5F,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAG9F;AAED;;qDAEqD;AACrD,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAW7G;AAgBD,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGrF;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAWpI;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAQzI"} \ No newline at end of file diff --git a/mcp/dist/tools/doc-sources.js b/mcp/dist/tools/doc-sources.js index cfa3eda..93d3b6c 100644 --- a/mcp/dist/tools/doc-sources.js +++ b/mcp/dist/tools/doc-sources.js @@ -41,7 +41,7 @@ export async function readConfig(brainDir, slug) { } } /** Drop junk dirs always; then drop git-ignored paths via `git check-ignore` when in a repo. */ -function filterIgnored(projectRoot, absPaths) { +export function filterIgnored(projectRoot, absPaths) { const nonJunk = absPaths.filter((p) => !relative(projectRoot, p).split('/').some((seg) => JUNK_DIRS.has(seg))); if (nonJunk.length === 0) return []; diff --git a/mcp/dist/tools/doc-sources.js.map b/mcp/dist/tools/doc-sources.js.map index 0cc6928..becf9be 100644 --- a/mcp/dist/tools/doc-sources.js.map +++ b/mcp/dist/tools/doc-sources.js.map @@ -1 +1 @@ -{"version":3,"file":"doc-sources.js","sourceRoot":"","sources":["../../src/tools/doc-sources.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"doc-sources.js","sourceRoot":"","sources":["../../src/tools/doc-sources.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/mcp/dist/tools/raw-scan-cli.bundle.js b/mcp/dist/tools/raw-scan-cli.bundle.js new file mode 100644 index 0000000..8c2558f --- /dev/null +++ b/mcp/dist/tools/raw-scan-cli.bundle.js @@ -0,0 +1,6375 @@ +// src/tools/raw-scan-cli.ts +import { homedir } from "os"; +import { join as join3, basename as basename2, relative as relative3 } from "path"; +import { existsSync, readFileSync } from "fs"; + +// src/tools/raw-scan.ts +import { resolve as resolve2, relative as relative2, sep as sep3 } from "path"; + +// node_modules/balanced-match/dist/esm/index.js +var balanced = (a, b, str) => { + const ma = a instanceof RegExp ? maybeMatch(a, str) : a; + const mb = b instanceof RegExp ? maybeMatch(b, str) : b; + const r = ma !== null && mb != null && range(ma, mb, str); + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + ma.length, r[1]), + post: str.slice(r[1] + mb.length) + }; +}; +var maybeMatch = (reg, str) => { + const m = str.match(reg); + return m ? m[0] : null; +}; +var range = (a, b, str) => { + let begs, beg, left, right = void 0, result; + let ai = str.indexOf(a); + let bi = str.indexOf(b, ai + 1); + let i = ai; + if (ai >= 0 && bi > 0) { + if (a === b) { + return [ai, bi]; + } + begs = []; + left = str.length; + while (i >= 0 && !result) { + if (i === ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length === 1) { + const r = begs.pop(); + if (r !== void 0) + result = [r, bi]; + } else { + beg = begs.pop(); + if (beg !== void 0 && beg < left) { + left = beg; + right = bi; + } + bi = str.indexOf(b, i + 1); + } + i = ai < bi && ai >= 0 ? ai : bi; + } + if (begs.length && right !== void 0) { + result = [left, right]; + } + } + return result; +}; + +// node_modules/brace-expansion/dist/esm/index.js +var escSlash = "\0SLASH" + Math.random() + "\0"; +var escOpen = "\0OPEN" + Math.random() + "\0"; +var escClose = "\0CLOSE" + Math.random() + "\0"; +var escComma = "\0COMMA" + Math.random() + "\0"; +var escPeriod = "\0PERIOD" + Math.random() + "\0"; +var escSlashPattern = new RegExp(escSlash, "g"); +var escOpenPattern = new RegExp(escOpen, "g"); +var escClosePattern = new RegExp(escClose, "g"); +var escCommaPattern = new RegExp(escComma, "g"); +var escPeriodPattern = new RegExp(escPeriod, "g"); +var slashPattern = /\\\\/g; +var openPattern = /\\{/g; +var closePattern = /\\}/g; +var commaPattern = /\\,/g; +var periodPattern = /\\\./g; +var EXPANSION_MAX = 1e5; +function numeric(str) { + return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); +} +function escapeBraces(str) { + return str.replace(slashPattern, escSlash).replace(openPattern, escOpen).replace(closePattern, escClose).replace(commaPattern, escComma).replace(periodPattern, escPeriod); +} +function unescapeBraces(str) { + return str.replace(escSlashPattern, "\\").replace(escOpenPattern, "{").replace(escClosePattern, "}").replace(escCommaPattern, ",").replace(escPeriodPattern, "."); +} +function parseCommaParts(str) { + if (!str) { + return [""]; + } + const parts = []; + const m = balanced("{", "}", str); + if (!m) { + return str.split(","); + } + const { pre, body, post } = m; + const p = pre.split(","); + p[p.length - 1] += "{" + body + "}"; + const postParts = parseCommaParts(post); + if (post.length) { + ; + p[p.length - 1] += postParts.shift(); + p.push.apply(p, postParts); + } + parts.push.apply(parts, p); + return parts; +} +function expand(str, options = {}) { + if (!str) { + return []; + } + const { max = EXPANSION_MAX } = options; + if (str.slice(0, 2) === "{}") { + str = "\\{\\}" + str.slice(2); + } + return expand_(escapeBraces(str), max, true).map(unescapeBraces); +} +function embrace(str) { + return "{" + str + "}"; +} +function isPadded(el) { + return /^-?0\d/.test(el); +} +function lte(i, y) { + return i <= y; +} +function gte(i, y) { + return i >= y; +} +function expand_(str, max, isTop) { + const expansions = []; + const m = balanced("{", "}", str); + if (!m) + return [str]; + const pre = m.pre; + const post = m.post.length ? expand_(m.post, max, false) : [""]; + if (/\$$/.test(m.pre)) { + for (let k = 0; k < post.length && k < max; k++) { + const expansion = pre + "{" + m.body + "}" + post[k]; + expansions.push(expansion); + } + } else { + const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + const isSequence = isNumericSequence || isAlphaSequence; + const isOptions = m.body.indexOf(",") >= 0; + if (!isSequence && !isOptions) { + if (m.post.match(/,(?!,).*\}/)) { + str = m.pre + "{" + m.body + escClose + m.post; + return expand_(str, max, true); + } + return [str]; + } + let n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1 && n[0] !== void 0) { + n = expand_(n[0], max, false).map(embrace); + if (n.length === 1) { + return post.map((p) => m.pre + n[0] + p); + } + } + } + let N; + if (isSequence && n[0] !== void 0 && n[1] !== void 0) { + const x = numeric(n[0]); + const y = numeric(n[1]); + const width = Math.max(n[0].length, n[1].length); + let incr = n.length === 3 && n[2] !== void 0 ? Math.max(Math.abs(numeric(n[2])), 1) : 1; + let test = lte; + const reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + const pad = n.some(isPadded); + N = []; + for (let i = x; test(i, y); i += incr) { + let c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === "\\") { + c = ""; + } + } else { + c = String(i); + if (pad) { + const need = width - c.length; + if (need > 0) { + const z = new Array(need + 1).join("0"); + if (i < 0) { + c = "-" + z + c.slice(1); + } else { + c = z + c; + } + } + } + } + N.push(c); + } + } else { + N = []; + for (let j2 = 0; j2 < n.length; j2++) { + N.push.apply(N, expand_(n[j2], max, false)); + } + } + for (let j2 = 0; j2 < N.length; j2++) { + for (let k = 0; k < post.length && expansions.length < max; k++) { + const expansion = pre + N[j2] + post[k]; + if (!isTop || isSequence || expansion) { + expansions.push(expansion); + } + } + } + } + return expansions; +} + +// node_modules/minimatch/dist/esm/assert-valid-pattern.js +var MAX_PATTERN_LENGTH = 1024 * 64; +var assertValidPattern = (pattern) => { + if (typeof pattern !== "string") { + throw new TypeError("invalid pattern"); + } + if (pattern.length > MAX_PATTERN_LENGTH) { + throw new TypeError("pattern is too long"); + } +}; + +// node_modules/minimatch/dist/esm/brace-expressions.js +var posixClasses = { + "[:alnum:]": ["\\p{L}\\p{Nl}\\p{Nd}", true], + "[:alpha:]": ["\\p{L}\\p{Nl}", true], + "[:ascii:]": ["\\x00-\\x7f", false], + "[:blank:]": ["\\p{Zs}\\t", true], + "[:cntrl:]": ["\\p{Cc}", true], + "[:digit:]": ["\\p{Nd}", true], + "[:graph:]": ["\\p{Z}\\p{C}", true, true], + "[:lower:]": ["\\p{Ll}", true], + "[:print:]": ["\\p{C}", true], + "[:punct:]": ["\\p{P}", true], + "[:space:]": ["\\p{Z}\\t\\r\\n\\v\\f", true], + "[:upper:]": ["\\p{Lu}", true], + "[:word:]": ["\\p{L}\\p{Nl}\\p{Nd}\\p{Pc}", true], + "[:xdigit:]": ["A-Fa-f0-9", false] +}; +var braceEscape = (s) => s.replace(/[[\]\\-]/g, "\\$&"); +var regexpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +var rangesToString = (ranges) => ranges.join(""); +var parseClass = (glob2, position) => { + const pos = position; + if (glob2.charAt(pos) !== "[") { + throw new Error("not in a brace expression"); + } + const ranges = []; + const negs = []; + let i = pos + 1; + let sawStart = false; + let uflag = false; + let escaping = false; + let negate = false; + let endPos = pos; + let rangeStart = ""; + WHILE: while (i < glob2.length) { + const c = glob2.charAt(i); + if ((c === "!" || c === "^") && i === pos + 1) { + negate = true; + i++; + continue; + } + if (c === "]" && sawStart && !escaping) { + endPos = i + 1; + break; + } + sawStart = true; + if (c === "\\") { + if (!escaping) { + escaping = true; + i++; + continue; + } + } + if (c === "[" && !escaping) { + for (const [cls, [unip, u3, neg]] of Object.entries(posixClasses)) { + if (glob2.startsWith(cls, i)) { + if (rangeStart) { + return ["$.", false, glob2.length - pos, true]; + } + i += cls.length; + if (neg) + negs.push(unip); + else + ranges.push(unip); + uflag = uflag || u3; + continue WHILE; + } + } + } + escaping = false; + if (rangeStart) { + if (c > rangeStart) { + ranges.push(braceEscape(rangeStart) + "-" + braceEscape(c)); + } else if (c === rangeStart) { + ranges.push(braceEscape(c)); + } + rangeStart = ""; + i++; + continue; + } + if (glob2.startsWith("-]", i + 1)) { + ranges.push(braceEscape(c + "-")); + i += 2; + continue; + } + if (glob2.startsWith("-", i + 1)) { + rangeStart = c; + i += 2; + continue; + } + ranges.push(braceEscape(c)); + i++; + } + if (endPos < i) { + return ["", false, 0, false]; + } + if (!ranges.length && !negs.length) { + return ["$.", false, glob2.length - pos, true]; + } + if (negs.length === 0 && ranges.length === 1 && /^\\?.$/.test(ranges[0]) && !negate) { + const r = ranges[0].length === 2 ? ranges[0].slice(-1) : ranges[0]; + return [regexpEscape(r), false, endPos - pos, false]; + } + const sranges = "[" + (negate ? "^" : "") + rangesToString(ranges) + "]"; + const snegs = "[" + (negate ? "" : "^") + rangesToString(negs) + "]"; + const comb = ranges.length && negs.length ? "(" + sranges + "|" + snegs + ")" : ranges.length ? sranges : snegs; + return [comb, uflag, endPos - pos, true]; +}; + +// node_modules/minimatch/dist/esm/unescape.js +var unescape = (s, { windowsPathsNoEscape = false, magicalBraces = true } = {}) => { + if (magicalBraces) { + return windowsPathsNoEscape ? s.replace(/\[([^/\\])\]/g, "$1") : s.replace(/((?!\\).|^)\[([^/\\])\]/g, "$1$2").replace(/\\([^/])/g, "$1"); + } + return windowsPathsNoEscape ? s.replace(/\[([^/\\{}])\]/g, "$1") : s.replace(/((?!\\).|^)\[([^/\\{}])\]/g, "$1$2").replace(/\\([^/{}])/g, "$1"); +}; + +// node_modules/minimatch/dist/esm/ast.js +var _a; +var types = /* @__PURE__ */ new Set(["!", "?", "+", "*", "@"]); +var isExtglobType = (c) => types.has(c); +var isExtglobAST = (c) => isExtglobType(c.type); +var adoptionMap = /* @__PURE__ */ new Map([ + ["!", ["@"]], + ["?", ["?", "@"]], + ["@", ["@"]], + ["*", ["*", "+", "?", "@"]], + ["+", ["+", "@"]] +]); +var adoptionWithSpaceMap = /* @__PURE__ */ new Map([ + ["!", ["?"]], + ["@", ["?"]], + ["+", ["?", "*"]] +]); +var adoptionAnyMap = /* @__PURE__ */ new Map([ + ["!", ["?", "@"]], + ["?", ["?", "@"]], + ["@", ["?", "@"]], + ["*", ["*", "+", "?", "@"]], + ["+", ["+", "@", "?", "*"]] +]); +var usurpMap = /* @__PURE__ */ new Map([ + ["!", /* @__PURE__ */ new Map([["!", "@"]])], + [ + "?", + /* @__PURE__ */ new Map([ + ["*", "*"], + ["+", "*"] + ]) + ], + [ + "@", + /* @__PURE__ */ new Map([ + ["!", "!"], + ["?", "?"], + ["@", "@"], + ["*", "*"], + ["+", "+"] + ]) + ], + [ + "+", + /* @__PURE__ */ new Map([ + ["?", "*"], + ["*", "*"] + ]) + ] +]); +var startNoTraversal = "(?!(?:^|/)\\.\\.?(?:$|/))"; +var startNoDot = "(?!\\.)"; +var addPatternStart = /* @__PURE__ */ new Set(["[", "."]); +var justDots = /* @__PURE__ */ new Set(["..", "."]); +var reSpecials = new Set("().*{}+?[]^$\\!"); +var regExpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +var qmark = "[^/]"; +var star = qmark + "*?"; +var starNoEmpty = qmark + "+?"; +var ID = 0; +var AST = class { + type; + #root; + #hasMagic; + #uflag = false; + #parts = []; + #parent; + #parentIndex; + #negs; + #filledNegs = false; + #options; + #toString; + // set to true if it's an extglob with no children + // (which really means one child of '') + #emptyExt = false; + id = ++ID; + get depth() { + return (this.#parent?.depth ?? -1) + 1; + } + [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() { + return { + "@@type": "AST", + id: this.id, + type: this.type, + root: this.#root.id, + parent: this.#parent?.id, + depth: this.depth, + partsLength: this.#parts.length, + parts: this.#parts + }; + } + constructor(type, parent, options = {}) { + this.type = type; + if (type) + this.#hasMagic = true; + this.#parent = parent; + this.#root = this.#parent ? this.#parent.#root : this; + this.#options = this.#root === this ? options : this.#root.#options; + this.#negs = this.#root === this ? [] : this.#root.#negs; + if (type === "!" && !this.#root.#filledNegs) + this.#negs.push(this); + this.#parentIndex = this.#parent ? this.#parent.#parts.length : 0; + } + get hasMagic() { + if (this.#hasMagic !== void 0) + return this.#hasMagic; + for (const p of this.#parts) { + if (typeof p === "string") + continue; + if (p.type || p.hasMagic) + return this.#hasMagic = true; + } + return this.#hasMagic; + } + // reconstructs the pattern + toString() { + return this.#toString !== void 0 ? this.#toString : !this.type ? this.#toString = this.#parts.map((p) => String(p)).join("") : this.#toString = this.type + "(" + this.#parts.map((p) => String(p)).join("|") + ")"; + } + #fillNegs() { + if (this !== this.#root) + throw new Error("should only call on root"); + if (this.#filledNegs) + return this; + this.toString(); + this.#filledNegs = true; + let n; + while (n = this.#negs.pop()) { + if (n.type !== "!") + continue; + let p = n; + let pp = p.#parent; + while (pp) { + for (let i = p.#parentIndex + 1; !pp.type && i < pp.#parts.length; i++) { + for (const part of n.#parts) { + if (typeof part === "string") { + throw new Error("string part in extglob AST??"); + } + part.copyIn(pp.#parts[i]); + } + } + p = pp; + pp = p.#parent; + } + } + return this; + } + push(...parts) { + for (const p of parts) { + if (p === "") + continue; + if (typeof p !== "string" && !(p instanceof _a && p.#parent === this)) { + throw new Error("invalid part: " + p); + } + this.#parts.push(p); + } + } + toJSON() { + const ret = this.type === null ? this.#parts.slice().map((p) => typeof p === "string" ? p : p.toJSON()) : [this.type, ...this.#parts.map((p) => p.toJSON())]; + if (this.isStart() && !this.type) + ret.unshift([]); + if (this.isEnd() && (this === this.#root || this.#root.#filledNegs && this.#parent?.type === "!")) { + ret.push({}); + } + return ret; + } + isStart() { + if (this.#root === this) + return true; + if (!this.#parent?.isStart()) + return false; + if (this.#parentIndex === 0) + return true; + const p = this.#parent; + for (let i = 0; i < this.#parentIndex; i++) { + const pp = p.#parts[i]; + if (!(pp instanceof _a && pp.type === "!")) { + return false; + } + } + return true; + } + isEnd() { + if (this.#root === this) + return true; + if (this.#parent?.type === "!") + return true; + if (!this.#parent?.isEnd()) + return false; + if (!this.type) + return this.#parent?.isEnd(); + const pl = this.#parent ? this.#parent.#parts.length : 0; + return this.#parentIndex === pl - 1; + } + copyIn(part) { + if (typeof part === "string") + this.push(part); + else + this.push(part.clone(this)); + } + clone(parent) { + const c = new _a(this.type, parent); + for (const p of this.#parts) { + c.copyIn(p); + } + return c; + } + static #parseAST(str, ast, pos, opt, extDepth) { + const maxDepth = opt.maxExtglobRecursion ?? 2; + let escaping = false; + let inBrace = false; + let braceStart = -1; + let braceNeg = false; + if (ast.type === null) { + let i2 = pos; + let acc2 = ""; + while (i2 < str.length) { + const c = str.charAt(i2++); + if (escaping || c === "\\") { + escaping = !escaping; + acc2 += c; + continue; + } + if (inBrace) { + if (i2 === braceStart + 1) { + if (c === "^" || c === "!") { + braceNeg = true; + } + } else if (c === "]" && !(i2 === braceStart + 2 && braceNeg)) { + inBrace = false; + } + acc2 += c; + continue; + } else if (c === "[") { + inBrace = true; + braceStart = i2; + braceNeg = false; + acc2 += c; + continue; + } + const doRecurse = !opt.noext && isExtglobType(c) && str.charAt(i2) === "(" && extDepth <= maxDepth; + if (doRecurse) { + ast.push(acc2); + acc2 = ""; + const ext2 = new _a(c, ast); + i2 = _a.#parseAST(str, ext2, i2, opt, extDepth + 1); + ast.push(ext2); + continue; + } + acc2 += c; + } + ast.push(acc2); + return i2; + } + let i = pos + 1; + let part = new _a(null, ast); + const parts = []; + let acc = ""; + while (i < str.length) { + const c = str.charAt(i++); + if (escaping || c === "\\") { + escaping = !escaping; + acc += c; + continue; + } + if (inBrace) { + if (i === braceStart + 1) { + if (c === "^" || c === "!") { + braceNeg = true; + } + } else if (c === "]" && !(i === braceStart + 2 && braceNeg)) { + inBrace = false; + } + acc += c; + continue; + } else if (c === "[") { + inBrace = true; + braceStart = i; + braceNeg = false; + acc += c; + continue; + } + const doRecurse = !opt.noext && isExtglobType(c) && str.charAt(i) === "(" && /* c8 ignore start - the maxDepth is sufficient here */ + (extDepth <= maxDepth || ast && ast.#canAdoptType(c)); + if (doRecurse) { + const depthAdd = ast && ast.#canAdoptType(c) ? 0 : 1; + part.push(acc); + acc = ""; + const ext2 = new _a(c, part); + part.push(ext2); + i = _a.#parseAST(str, ext2, i, opt, extDepth + depthAdd); + continue; + } + if (c === "|") { + part.push(acc); + acc = ""; + parts.push(part); + part = new _a(null, ast); + continue; + } + if (c === ")") { + if (acc === "" && ast.#parts.length === 0) { + ast.#emptyExt = true; + } + part.push(acc); + acc = ""; + ast.push(...parts, part); + return i; + } + acc += c; + } + ast.type = null; + ast.#hasMagic = void 0; + ast.#parts = [str.substring(pos - 1)]; + return i; + } + #canAdoptWithSpace(child) { + return this.#canAdopt(child, adoptionWithSpaceMap); + } + #canAdopt(child, map = adoptionMap) { + if (!child || typeof child !== "object" || child.type !== null || child.#parts.length !== 1 || this.type === null) { + return false; + } + const gc = child.#parts[0]; + if (!gc || typeof gc !== "object" || gc.type === null) { + return false; + } + return this.#canAdoptType(gc.type, map); + } + #canAdoptType(c, map = adoptionAnyMap) { + return !!map.get(this.type)?.includes(c); + } + #adoptWithSpace(child, index) { + const gc = child.#parts[0]; + const blank = new _a(null, gc, this.options); + blank.#parts.push(""); + gc.push(blank); + this.#adopt(child, index); + } + #adopt(child, index) { + const gc = child.#parts[0]; + this.#parts.splice(index, 1, ...gc.#parts); + for (const p of gc.#parts) { + if (typeof p === "object") + p.#parent = this; + } + this.#toString = void 0; + } + #canUsurpType(c) { + const m = usurpMap.get(this.type); + return !!m?.has(c); + } + #canUsurp(child) { + if (!child || typeof child !== "object" || child.type !== null || child.#parts.length !== 1 || this.type === null || this.#parts.length !== 1) { + return false; + } + const gc = child.#parts[0]; + if (!gc || typeof gc !== "object" || gc.type === null) { + return false; + } + return this.#canUsurpType(gc.type); + } + #usurp(child) { + const m = usurpMap.get(this.type); + const gc = child.#parts[0]; + const nt = m?.get(gc.type); + if (!nt) + return false; + this.#parts = gc.#parts; + for (const p of this.#parts) { + if (typeof p === "object") { + p.#parent = this; + } + } + this.type = nt; + this.#toString = void 0; + this.#emptyExt = false; + } + static fromGlob(pattern, options = {}) { + const ast = new _a(null, void 0, options); + _a.#parseAST(pattern, ast, 0, options, 0); + return ast; + } + // returns the regular expression if there's magic, or the unescaped + // string if not. + toMMPattern() { + if (this !== this.#root) + return this.#root.toMMPattern(); + const glob2 = this.toString(); + const [re, body, hasMagic2, uflag] = this.toRegExpSource(); + const anyMagic = hasMagic2 || this.#hasMagic || this.#options.nocase && !this.#options.nocaseMagicOnly && glob2.toUpperCase() !== glob2.toLowerCase(); + if (!anyMagic) { + return body; + } + const flags = (this.#options.nocase ? "i" : "") + (uflag ? "u" : ""); + return Object.assign(new RegExp(`^${re}$`, flags), { + _src: re, + _glob: glob2 + }); + } + get options() { + return this.#options; + } + // returns the string match, the regexp source, whether there's magic + // in the regexp (so a regular expression is required) and whether or + // not the uflag is needed for the regular expression (for posix classes) + // TODO: instead of injecting the start/end at this point, just return + // the BODY of the regexp, along with the start/end portions suitable + // for binding the start/end in either a joined full-path makeRe context + // (where we bind to (^|/), or a standalone matchPart context (where + // we bind to ^, and not /). Otherwise slashes get duped! + // + // In part-matching mode, the start is: + // - if not isStart: nothing + // - if traversal possible, but not allowed: ^(?!\.\.?$) + // - if dots allowed or not possible: ^ + // - if dots possible and not allowed: ^(?!\.) + // end is: + // - if not isEnd(): nothing + // - else: $ + // + // In full-path matching mode, we put the slash at the START of the + // pattern, so start is: + // - if first pattern: same as part-matching mode + // - if not isStart(): nothing + // - if traversal possible, but not allowed: /(?!\.\.?(?:$|/)) + // - if dots allowed or not possible: / + // - if dots possible and not allowed: /(?!\.) + // end is: + // - if last pattern, same as part-matching mode + // - else nothing + // + // Always put the (?:$|/) on negated tails, though, because that has to be + // there to bind the end of the negated pattern portion, and it's easier to + // just stick it in now rather than try to inject it later in the middle of + // the pattern. + // + // We can just always return the same end, and leave it up to the caller + // to know whether it's going to be used joined or in parts. + // And, if the start is adjusted slightly, can do the same there: + // - if not isStart: nothing + // - if traversal possible, but not allowed: (?:/|^)(?!\.\.?$) + // - if dots allowed or not possible: (?:/|^) + // - if dots possible and not allowed: (?:/|^)(?!\.) + // + // But it's better to have a simpler binding without a conditional, for + // performance, so probably better to return both start options. + // + // Then the caller just ignores the end if it's not the first pattern, + // and the start always gets applied. + // + // But that's always going to be $ if it's the ending pattern, or nothing, + // so the caller can just attach $ at the end of the pattern when building. + // + // So the todo is: + // - better detect what kind of start is needed + // - return both flavors of starting pattern + // - attach $ at the end of the pattern when creating the actual RegExp + // + // Ah, but wait, no, that all only applies to the root when the first pattern + // is not an extglob. If the first pattern IS an extglob, then we need all + // that dot prevention biz to live in the extglob portions, because eg + // +(*|.x*) can match .xy but not .yx. + // + // So, return the two flavors if it's #root and the first child is not an + // AST, otherwise leave it to the child AST to handle it, and there, + // use the (?:^|/) style of start binding. + // + // Even simplified further: + // - Since the start for a join is eg /(?!\.) and the start for a part + // is ^(?!\.), we can just prepend (?!\.) to the pattern (either root + // or start or whatever) and prepend ^ or / at the Regexp construction. + toRegExpSource(allowDot) { + const dot = allowDot ?? !!this.#options.dot; + if (this.#root === this) { + this.#flatten(); + this.#fillNegs(); + } + if (!isExtglobAST(this)) { + const noEmpty = this.isStart() && this.isEnd() && !this.#parts.some((s) => typeof s !== "string"); + const src = this.#parts.map((p) => { + const [re, _, hasMagic2, uflag] = typeof p === "string" ? _a.#parseGlob(p, this.#hasMagic, noEmpty) : p.toRegExpSource(allowDot); + this.#hasMagic = this.#hasMagic || hasMagic2; + this.#uflag = this.#uflag || uflag; + return re; + }).join(""); + let start2 = ""; + if (this.isStart()) { + if (typeof this.#parts[0] === "string") { + const dotTravAllowed = this.#parts.length === 1 && justDots.has(this.#parts[0]); + if (!dotTravAllowed) { + const aps = addPatternStart; + const needNoTrav = ( + // dots are allowed, and the pattern starts with [ or . + dot && aps.has(src.charAt(0)) || // the pattern starts with \., and then [ or . + src.startsWith("\\.") && aps.has(src.charAt(2)) || // the pattern starts with \.\., and then [ or . + src.startsWith("\\.\\.") && aps.has(src.charAt(4)) + ); + const needNoDot = !dot && !allowDot && aps.has(src.charAt(0)); + start2 = needNoTrav ? startNoTraversal : needNoDot ? startNoDot : ""; + } + } + } + let end = ""; + if (this.isEnd() && this.#root.#filledNegs && this.#parent?.type === "!") { + end = "(?:$|\\/)"; + } + const final2 = start2 + src + end; + return [ + final2, + unescape(src), + this.#hasMagic = !!this.#hasMagic, + this.#uflag + ]; + } + const repeated = this.type === "*" || this.type === "+"; + const start = this.type === "!" ? "(?:(?!(?:" : "(?:"; + let body = this.#partsToRegExp(dot); + if (this.isStart() && this.isEnd() && !body && this.type !== "!") { + const s = this.toString(); + const me = this; + me.#parts = [s]; + me.type = null; + me.#hasMagic = void 0; + return [s, unescape(this.toString()), false, false]; + } + let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot ? "" : this.#partsToRegExp(true); + if (bodyDotAllowed === body) { + bodyDotAllowed = ""; + } + if (bodyDotAllowed) { + body = `(?:${body})(?:${bodyDotAllowed})*?`; + } + let final = ""; + if (this.type === "!" && this.#emptyExt) { + final = (this.isStart() && !dot ? startNoDot : "") + starNoEmpty; + } else { + const close = this.type === "!" ? ( + // !() must match something,but !(x) can match '' + "))" + (this.isStart() && !dot && !allowDot ? startNoDot : "") + star + ")" + ) : this.type === "@" ? ")" : this.type === "?" ? ")?" : this.type === "+" && bodyDotAllowed ? ")" : this.type === "*" && bodyDotAllowed ? `)?` : `)${this.type}`; + final = start + body + close; + } + return [ + final, + unescape(body), + this.#hasMagic = !!this.#hasMagic, + this.#uflag + ]; + } + #flatten() { + if (!isExtglobAST(this)) { + for (const p of this.#parts) { + if (typeof p === "object") { + p.#flatten(); + } + } + } else { + let iterations = 0; + let done = false; + do { + done = true; + for (let i = 0; i < this.#parts.length; i++) { + const c = this.#parts[i]; + if (typeof c === "object") { + c.#flatten(); + if (this.#canAdopt(c)) { + done = false; + this.#adopt(c, i); + } else if (this.#canAdoptWithSpace(c)) { + done = false; + this.#adoptWithSpace(c, i); + } else if (this.#canUsurp(c)) { + done = false; + this.#usurp(c); + } + } + } + } while (!done && ++iterations < 10); + } + this.#toString = void 0; + } + #partsToRegExp(dot) { + return this.#parts.map((p) => { + if (typeof p === "string") { + throw new Error("string type in extglob ast??"); + } + const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot); + this.#uflag = this.#uflag || uflag; + return re; + }).filter((p) => !(this.isStart() && this.isEnd()) || !!p).join("|"); + } + static #parseGlob(glob2, hasMagic2, noEmpty = false) { + let escaping = false; + let re = ""; + let uflag = false; + let inStar = false; + for (let i = 0; i < glob2.length; i++) { + const c = glob2.charAt(i); + if (escaping) { + escaping = false; + re += (reSpecials.has(c) ? "\\" : "") + c; + continue; + } + if (c === "*") { + if (inStar) + continue; + inStar = true; + re += noEmpty && /^[*]+$/.test(glob2) ? starNoEmpty : star; + hasMagic2 = true; + continue; + } else { + inStar = false; + } + if (c === "\\") { + if (i === glob2.length - 1) { + re += "\\\\"; + } else { + escaping = true; + } + continue; + } + if (c === "[") { + const [src, needUflag, consumed, magic] = parseClass(glob2, i); + if (consumed) { + re += src; + uflag = uflag || needUflag; + i += consumed - 1; + hasMagic2 = hasMagic2 || magic; + continue; + } + } + if (c === "?") { + re += qmark; + hasMagic2 = true; + continue; + } + re += regExpEscape(c); + } + return [re, unescape(glob2), !!hasMagic2, uflag]; + } +}; +_a = AST; + +// node_modules/minimatch/dist/esm/escape.js +var escape = (s, { windowsPathsNoEscape = false, magicalBraces = false } = {}) => { + if (magicalBraces) { + return windowsPathsNoEscape ? s.replace(/[?*()[\]{}]/g, "[$&]") : s.replace(/[?*()[\]\\{}]/g, "\\$&"); + } + return windowsPathsNoEscape ? s.replace(/[?*()[\]]/g, "[$&]") : s.replace(/[?*()[\]\\]/g, "\\$&"); +}; + +// node_modules/minimatch/dist/esm/index.js +var minimatch = (p, pattern, options = {}) => { + assertValidPattern(pattern); + if (!options.nocomment && pattern.charAt(0) === "#") { + return false; + } + return new Minimatch(pattern, options).match(p); +}; +var starDotExtRE = /^\*+([^+@!?*[(]*)$/; +var starDotExtTest = (ext2) => (f) => !f.startsWith(".") && f.endsWith(ext2); +var starDotExtTestDot = (ext2) => (f) => f.endsWith(ext2); +var starDotExtTestNocase = (ext2) => { + ext2 = ext2.toLowerCase(); + return (f) => !f.startsWith(".") && f.toLowerCase().endsWith(ext2); +}; +var starDotExtTestNocaseDot = (ext2) => { + ext2 = ext2.toLowerCase(); + return (f) => f.toLowerCase().endsWith(ext2); +}; +var starDotStarRE = /^\*+\.\*+$/; +var starDotStarTest = (f) => !f.startsWith(".") && f.includes("."); +var starDotStarTestDot = (f) => f !== "." && f !== ".." && f.includes("."); +var dotStarRE = /^\.\*+$/; +var dotStarTest = (f) => f !== "." && f !== ".." && f.startsWith("."); +var starRE = /^\*+$/; +var starTest = (f) => f.length !== 0 && !f.startsWith("."); +var starTestDot = (f) => f.length !== 0 && f !== "." && f !== ".."; +var qmarksRE = /^\?+([^+@!?*[(]*)?$/; +var qmarksTestNocase = ([$0, ext2 = ""]) => { + const noext = qmarksTestNoExt([$0]); + if (!ext2) + return noext; + ext2 = ext2.toLowerCase(); + return (f) => noext(f) && f.toLowerCase().endsWith(ext2); +}; +var qmarksTestNocaseDot = ([$0, ext2 = ""]) => { + const noext = qmarksTestNoExtDot([$0]); + if (!ext2) + return noext; + ext2 = ext2.toLowerCase(); + return (f) => noext(f) && f.toLowerCase().endsWith(ext2); +}; +var qmarksTestDot = ([$0, ext2 = ""]) => { + const noext = qmarksTestNoExtDot([$0]); + return !ext2 ? noext : (f) => noext(f) && f.endsWith(ext2); +}; +var qmarksTest = ([$0, ext2 = ""]) => { + const noext = qmarksTestNoExt([$0]); + return !ext2 ? noext : (f) => noext(f) && f.endsWith(ext2); +}; +var qmarksTestNoExt = ([$0]) => { + const len = $0.length; + return (f) => f.length === len && !f.startsWith("."); +}; +var qmarksTestNoExtDot = ([$0]) => { + const len = $0.length; + return (f) => f.length === len && f !== "." && f !== ".."; +}; +var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix"; +var path = { + win32: { sep: "\\" }, + posix: { sep: "/" } +}; +var sep = defaultPlatform === "win32" ? path.win32.sep : path.posix.sep; +minimatch.sep = sep; +var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **"); +minimatch.GLOBSTAR = GLOBSTAR; +var qmark2 = "[^/]"; +var star2 = qmark2 + "*?"; +var twoStarDot = "(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?"; +var twoStarNoDot = "(?:(?!(?:\\/|^)\\.).)*?"; +var filter = (pattern, options = {}) => (p) => minimatch(p, pattern, options); +minimatch.filter = filter; +var ext = (a, b = {}) => Object.assign({}, a, b); +var defaults = (def) => { + if (!def || typeof def !== "object" || !Object.keys(def).length) { + return minimatch; + } + const orig = minimatch; + const m = (p, pattern, options = {}) => orig(p, pattern, ext(def, options)); + return Object.assign(m, { + Minimatch: class Minimatch extends orig.Minimatch { + constructor(pattern, options = {}) { + super(pattern, ext(def, options)); + } + static defaults(options) { + return orig.defaults(ext(def, options)).Minimatch; + } + }, + AST: class AST extends orig.AST { + /* c8 ignore start */ + constructor(type, parent, options = {}) { + super(type, parent, ext(def, options)); + } + /* c8 ignore stop */ + static fromGlob(pattern, options = {}) { + return orig.AST.fromGlob(pattern, ext(def, options)); + } + }, + unescape: (s, options = {}) => orig.unescape(s, ext(def, options)), + escape: (s, options = {}) => orig.escape(s, ext(def, options)), + filter: (pattern, options = {}) => orig.filter(pattern, ext(def, options)), + defaults: (options) => orig.defaults(ext(def, options)), + makeRe: (pattern, options = {}) => orig.makeRe(pattern, ext(def, options)), + braceExpand: (pattern, options = {}) => orig.braceExpand(pattern, ext(def, options)), + match: (list, pattern, options = {}) => orig.match(list, pattern, ext(def, options)), + sep: orig.sep, + GLOBSTAR + }); +}; +minimatch.defaults = defaults; +var braceExpand = (pattern, options = {}) => { + assertValidPattern(pattern); + if (options.nobrace || !/\{(?:(?!\{).)*\}/.test(pattern)) { + return [pattern]; + } + return expand(pattern, { max: options.braceExpandMax }); +}; +minimatch.braceExpand = braceExpand; +var makeRe = (pattern, options = {}) => new Minimatch(pattern, options).makeRe(); +minimatch.makeRe = makeRe; +var match = (list, pattern, options = {}) => { + const mm = new Minimatch(pattern, options); + list = list.filter((f) => mm.match(f)); + if (mm.options.nonull && !list.length) { + list.push(pattern); + } + return list; +}; +minimatch.match = match; +var globMagic = /[?*]|[+@!]\(.*?\)|\[|\]/; +var regExpEscape2 = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +var Minimatch = class { + options; + set; + pattern; + windowsPathsNoEscape; + nonegate; + negate; + comment; + empty; + preserveMultipleSlashes; + partial; + globSet; + globParts; + nocase; + isWindows; + platform; + windowsNoMagicRoot; + maxGlobstarRecursion; + regexp; + constructor(pattern, options = {}) { + assertValidPattern(pattern); + options = options || {}; + this.options = options; + this.maxGlobstarRecursion = options.maxGlobstarRecursion ?? 200; + this.pattern = pattern; + this.platform = options.platform || defaultPlatform; + this.isWindows = this.platform === "win32"; + const awe = "allowWindowsEscape"; + this.windowsPathsNoEscape = !!options.windowsPathsNoEscape || options[awe] === false; + if (this.windowsPathsNoEscape) { + this.pattern = this.pattern.replace(/\\/g, "/"); + } + this.preserveMultipleSlashes = !!options.preserveMultipleSlashes; + this.regexp = null; + this.negate = false; + this.nonegate = !!options.nonegate; + this.comment = false; + this.empty = false; + this.partial = !!options.partial; + this.nocase = !!this.options.nocase; + this.windowsNoMagicRoot = options.windowsNoMagicRoot !== void 0 ? options.windowsNoMagicRoot : !!(this.isWindows && this.nocase); + this.globSet = []; + this.globParts = []; + this.set = []; + this.make(); + } + hasMagic() { + if (this.options.magicalBraces && this.set.length > 1) { + return true; + } + for (const pattern of this.set) { + for (const part of pattern) { + if (typeof part !== "string") + return true; + } + } + return false; + } + debug(..._) { + } + make() { + const pattern = this.pattern; + const options = this.options; + if (!options.nocomment && pattern.charAt(0) === "#") { + this.comment = true; + return; + } + if (!pattern) { + this.empty = true; + return; + } + this.parseNegate(); + this.globSet = [...new Set(this.braceExpand())]; + if (options.debug) { + this.debug = (...args) => console.error(...args); + } + this.debug(this.pattern, this.globSet); + const rawGlobParts = this.globSet.map((s) => this.slashSplit(s)); + this.globParts = this.preprocess(rawGlobParts); + this.debug(this.pattern, this.globParts); + let set = this.globParts.map((s, _, __) => { + if (this.isWindows && this.windowsNoMagicRoot) { + const isUNC = s[0] === "" && s[1] === "" && (s[2] === "?" || !globMagic.test(s[2])) && !globMagic.test(s[3]); + const isDrive = /^[a-z]:/i.test(s[0]); + if (isUNC) { + return [ + ...s.slice(0, 4), + ...s.slice(4).map((ss) => this.parse(ss)) + ]; + } else if (isDrive) { + return [s[0], ...s.slice(1).map((ss) => this.parse(ss))]; + } + } + return s.map((ss) => this.parse(ss)); + }); + this.debug(this.pattern, set); + this.set = set.filter((s) => s.indexOf(false) === -1); + if (this.isWindows) { + for (let i = 0; i < this.set.length; i++) { + const p = this.set[i]; + if (p[0] === "" && p[1] === "" && this.globParts[i][2] === "?" && typeof p[3] === "string" && /^[a-z]:$/i.test(p[3])) { + p[2] = "?"; + } + } + } + this.debug(this.pattern, this.set); + } + // various transforms to equivalent pattern sets that are + // faster to process in a filesystem walk. The goal is to + // eliminate what we can, and push all ** patterns as far + // to the right as possible, even if it increases the number + // of patterns that we have to process. + preprocess(globParts) { + if (this.options.noglobstar) { + for (const partset of globParts) { + for (let j2 = 0; j2 < partset.length; j2++) { + if (partset[j2] === "**") { + partset[j2] = "*"; + } + } + } + } + const { optimizationLevel = 1 } = this.options; + if (optimizationLevel >= 2) { + globParts = this.firstPhasePreProcess(globParts); + globParts = this.secondPhasePreProcess(globParts); + } else if (optimizationLevel >= 1) { + globParts = this.levelOneOptimize(globParts); + } else { + globParts = this.adjascentGlobstarOptimize(globParts); + } + return globParts; + } + // just get rid of adjascent ** portions + adjascentGlobstarOptimize(globParts) { + return globParts.map((parts) => { + let gs = -1; + while (-1 !== (gs = parts.indexOf("**", gs + 1))) { + let i = gs; + while (parts[i + 1] === "**") { + i++; + } + if (i !== gs) { + parts.splice(gs, i - gs); + } + } + return parts; + }); + } + // get rid of adjascent ** and resolve .. portions + levelOneOptimize(globParts) { + return globParts.map((parts) => { + parts = parts.reduce((set, part) => { + const prev = set[set.length - 1]; + if (part === "**" && prev === "**") { + return set; + } + if (part === "..") { + if (prev && prev !== ".." && prev !== "." && prev !== "**") { + set.pop(); + return set; + } + } + set.push(part); + return set; + }, []); + return parts.length === 0 ? [""] : parts; + }); + } + levelTwoFileOptimize(parts) { + if (!Array.isArray(parts)) { + parts = this.slashSplit(parts); + } + let didSomething = false; + do { + didSomething = false; + if (!this.preserveMultipleSlashes) { + for (let i = 1; i < parts.length - 1; i++) { + const p = parts[i]; + if (i === 1 && p === "" && parts[0] === "") + continue; + if (p === "." || p === "") { + didSomething = true; + parts.splice(i, 1); + i--; + } + } + if (parts[0] === "." && parts.length === 2 && (parts[1] === "." || parts[1] === "")) { + didSomething = true; + parts.pop(); + } + } + let dd = 0; + while (-1 !== (dd = parts.indexOf("..", dd + 1))) { + const p = parts[dd - 1]; + if (p && p !== "." && p !== ".." && p !== "**" && !(this.isWindows && /^[a-z]:$/i.test(p))) { + didSomething = true; + parts.splice(dd - 1, 2); + dd -= 2; + } + } + } while (didSomething); + return parts.length === 0 ? [""] : parts; + } + // First phase: single-pattern processing + //
 is 1 or more portions
+  //  is 1 or more portions
+  // 

is any portion other than ., .., '', or ** + // is . or '' + // + // **/.. is *brutal* for filesystem walking performance, because + // it effectively resets the recursive walk each time it occurs, + // and ** cannot be reduced out by a .. pattern part like a regexp + // or most strings (other than .., ., and '') can be. + // + //

/**/../

/

/ -> {

/../

/

/,

/**/

/

/} + //

// -> 
/
+  // 
/

/../ ->

/
+  // **/**/ -> **/
+  //
+  // **/*/ -> */**/ <== not valid because ** doesn't follow
+  // this WOULD be allowed if ** did follow symlinks, or * didn't
+  firstPhasePreProcess(globParts) {
+    let didSomething = false;
+    do {
+      didSomething = false;
+      for (let parts of globParts) {
+        let gs = -1;
+        while (-1 !== (gs = parts.indexOf("**", gs + 1))) {
+          let gss = gs;
+          while (parts[gss + 1] === "**") {
+            gss++;
+          }
+          if (gss > gs) {
+            parts.splice(gs + 1, gss - gs);
+          }
+          let next = parts[gs + 1];
+          const p = parts[gs + 2];
+          const p2 = parts[gs + 3];
+          if (next !== "..")
+            continue;
+          if (!p || p === "." || p === ".." || !p2 || p2 === "." || p2 === "..") {
+            continue;
+          }
+          didSomething = true;
+          parts.splice(gs, 1);
+          const other = parts.slice(0);
+          other[gs] = "**";
+          globParts.push(other);
+          gs--;
+        }
+        if (!this.preserveMultipleSlashes) {
+          for (let i = 1; i < parts.length - 1; i++) {
+            const p = parts[i];
+            if (i === 1 && p === "" && parts[0] === "")
+              continue;
+            if (p === "." || p === "") {
+              didSomething = true;
+              parts.splice(i, 1);
+              i--;
+            }
+          }
+          if (parts[0] === "." && parts.length === 2 && (parts[1] === "." || parts[1] === "")) {
+            didSomething = true;
+            parts.pop();
+          }
+        }
+        let dd = 0;
+        while (-1 !== (dd = parts.indexOf("..", dd + 1))) {
+          const p = parts[dd - 1];
+          if (p && p !== "." && p !== ".." && p !== "**") {
+            didSomething = true;
+            const needDot = dd === 1 && parts[dd + 1] === "**";
+            const splin = needDot ? ["."] : [];
+            parts.splice(dd - 1, 2, ...splin);
+            if (parts.length === 0)
+              parts.push("");
+            dd -= 2;
+          }
+        }
+      }
+    } while (didSomething);
+    return globParts;
+  }
+  // second phase: multi-pattern dedupes
+  // {
/*/,
/

/} ->

/*/
+  // {
/,
/} -> 
/
+  // {
/**/,
/} -> 
/**/
+  //
+  // {
/**/,
/**/

/} ->

/**/
+  // ^-- not valid because ** doens't follow symlinks
+  secondPhasePreProcess(globParts) {
+    for (let i = 0; i < globParts.length - 1; i++) {
+      for (let j2 = i + 1; j2 < globParts.length; j2++) {
+        const matched = this.partsMatch(globParts[i], globParts[j2], !this.preserveMultipleSlashes);
+        if (matched) {
+          globParts[i] = [];
+          globParts[j2] = matched;
+          break;
+        }
+      }
+    }
+    return globParts.filter((gs) => gs.length);
+  }
+  partsMatch(a, b, emptyGSMatch = false) {
+    let ai = 0;
+    let bi = 0;
+    let result = [];
+    let which = "";
+    while (ai < a.length && bi < b.length) {
+      if (a[ai] === b[bi]) {
+        result.push(which === "b" ? b[bi] : a[ai]);
+        ai++;
+        bi++;
+      } else if (emptyGSMatch && a[ai] === "**" && b[bi] === a[ai + 1]) {
+        result.push(a[ai]);
+        ai++;
+      } else if (emptyGSMatch && b[bi] === "**" && a[ai] === b[bi + 1]) {
+        result.push(b[bi]);
+        bi++;
+      } else if (a[ai] === "*" && b[bi] && (this.options.dot || !b[bi].startsWith(".")) && b[bi] !== "**") {
+        if (which === "b")
+          return false;
+        which = "a";
+        result.push(a[ai]);
+        ai++;
+        bi++;
+      } else if (b[bi] === "*" && a[ai] && (this.options.dot || !a[ai].startsWith(".")) && a[ai] !== "**") {
+        if (which === "a")
+          return false;
+        which = "b";
+        result.push(b[bi]);
+        ai++;
+        bi++;
+      } else {
+        return false;
+      }
+    }
+    return a.length === b.length && result;
+  }
+  parseNegate() {
+    if (this.nonegate)
+      return;
+    const pattern = this.pattern;
+    let negate = false;
+    let negateOffset = 0;
+    for (let i = 0; i < pattern.length && pattern.charAt(i) === "!"; i++) {
+      negate = !negate;
+      negateOffset++;
+    }
+    if (negateOffset)
+      this.pattern = pattern.slice(negateOffset);
+    this.negate = negate;
+  }
+  // set partial to true to test if, for example,
+  // "/a/b" matches the start of "/*/b/*/d"
+  // Partial means, if you run out of file before you run
+  // out of pattern, then that's fine, as long as all
+  // the parts match.
+  matchOne(file, pattern, partial = false) {
+    let fileStartIndex = 0;
+    let patternStartIndex = 0;
+    if (this.isWindows) {
+      const fileDrive = typeof file[0] === "string" && /^[a-z]:$/i.test(file[0]);
+      const fileUNC = !fileDrive && file[0] === "" && file[1] === "" && file[2] === "?" && /^[a-z]:$/i.test(file[3]);
+      const patternDrive = typeof pattern[0] === "string" && /^[a-z]:$/i.test(pattern[0]);
+      const patternUNC = !patternDrive && pattern[0] === "" && pattern[1] === "" && pattern[2] === "?" && typeof pattern[3] === "string" && /^[a-z]:$/i.test(pattern[3]);
+      const fdi = fileUNC ? 3 : fileDrive ? 0 : void 0;
+      const pdi = patternUNC ? 3 : patternDrive ? 0 : void 0;
+      if (typeof fdi === "number" && typeof pdi === "number") {
+        const [fd, pd] = [
+          file[fdi],
+          pattern[pdi]
+        ];
+        if (fd.toLowerCase() === pd.toLowerCase()) {
+          pattern[pdi] = fd;
+          patternStartIndex = pdi;
+          fileStartIndex = fdi;
+        }
+      }
+    }
+    const { optimizationLevel = 1 } = this.options;
+    if (optimizationLevel >= 2) {
+      file = this.levelTwoFileOptimize(file);
+    }
+    if (pattern.includes(GLOBSTAR)) {
+      return this.#matchGlobstar(file, pattern, partial, fileStartIndex, patternStartIndex);
+    }
+    return this.#matchOne(file, pattern, partial, fileStartIndex, patternStartIndex);
+  }
+  #matchGlobstar(file, pattern, partial, fileIndex, patternIndex) {
+    const firstgs = pattern.indexOf(GLOBSTAR, patternIndex);
+    const lastgs = pattern.lastIndexOf(GLOBSTAR);
+    const [head, body, tail] = partial ? [
+      pattern.slice(patternIndex, firstgs),
+      pattern.slice(firstgs + 1),
+      []
+    ] : [
+      pattern.slice(patternIndex, firstgs),
+      pattern.slice(firstgs + 1, lastgs),
+      pattern.slice(lastgs + 1)
+    ];
+    if (head.length) {
+      const fileHead = file.slice(fileIndex, fileIndex + head.length);
+      if (!this.#matchOne(fileHead, head, partial, 0, 0)) {
+        return false;
+      }
+      fileIndex += head.length;
+      patternIndex += head.length;
+    }
+    let fileTailMatch = 0;
+    if (tail.length) {
+      if (tail.length + fileIndex > file.length)
+        return false;
+      let tailStart = file.length - tail.length;
+      if (this.#matchOne(file, tail, partial, tailStart, 0)) {
+        fileTailMatch = tail.length;
+      } else {
+        if (file[file.length - 1] !== "" || fileIndex + tail.length === file.length) {
+          return false;
+        }
+        tailStart--;
+        if (!this.#matchOne(file, tail, partial, tailStart, 0)) {
+          return false;
+        }
+        fileTailMatch = tail.length + 1;
+      }
+    }
+    if (!body.length) {
+      let sawSome = !!fileTailMatch;
+      for (let i2 = fileIndex; i2 < file.length - fileTailMatch; i2++) {
+        const f = String(file[i2]);
+        sawSome = true;
+        if (f === "." || f === ".." || !this.options.dot && f.startsWith(".")) {
+          return false;
+        }
+      }
+      return partial || sawSome;
+    }
+    const bodySegments = [[[], 0]];
+    let currentBody = bodySegments[0];
+    let nonGsParts = 0;
+    const nonGsPartsSums = [0];
+    for (const b of body) {
+      if (b === GLOBSTAR) {
+        nonGsPartsSums.push(nonGsParts);
+        currentBody = [[], 0];
+        bodySegments.push(currentBody);
+      } else {
+        currentBody[0].push(b);
+        nonGsParts++;
+      }
+    }
+    let i = bodySegments.length - 1;
+    const fileLength = file.length - fileTailMatch;
+    for (const b of bodySegments) {
+      b[1] = fileLength - (nonGsPartsSums[i--] + b[0].length);
+    }
+    return !!this.#matchGlobStarBodySections(file, bodySegments, fileIndex, 0, partial, 0, !!fileTailMatch);
+  }
+  // return false for "nope, not matching"
+  // return null for "not matching, cannot keep trying"
+  #matchGlobStarBodySections(file, bodySegments, fileIndex, bodyIndex, partial, globStarDepth, sawTail) {
+    const bs = bodySegments[bodyIndex];
+    if (!bs) {
+      for (let i = fileIndex; i < file.length; i++) {
+        sawTail = true;
+        const f = file[i];
+        if (f === "." || f === ".." || !this.options.dot && f.startsWith(".")) {
+          return false;
+        }
+      }
+      return sawTail;
+    }
+    const [body, after] = bs;
+    while (fileIndex <= after) {
+      const m = this.#matchOne(file.slice(0, fileIndex + body.length), body, partial, fileIndex, 0);
+      if (m && globStarDepth < this.maxGlobstarRecursion) {
+        const sub = this.#matchGlobStarBodySections(file, bodySegments, fileIndex + body.length, bodyIndex + 1, partial, globStarDepth + 1, sawTail);
+        if (sub !== false) {
+          return sub;
+        }
+      }
+      const f = file[fileIndex];
+      if (f === "." || f === ".." || !this.options.dot && f.startsWith(".")) {
+        return false;
+      }
+      fileIndex++;
+    }
+    return partial || null;
+  }
+  #matchOne(file, pattern, partial, fileIndex, patternIndex) {
+    let fi;
+    let pi;
+    let pl;
+    let fl;
+    for (fi = fileIndex, pi = patternIndex, fl = file.length, pl = pattern.length; fi < fl && pi < pl; fi++, pi++) {
+      this.debug("matchOne loop");
+      let p = pattern[pi];
+      let f = file[fi];
+      this.debug(pattern, p, f);
+      if (p === false || p === GLOBSTAR) {
+        return false;
+      }
+      let hit;
+      if (typeof p === "string") {
+        hit = f === p;
+        this.debug("string match", p, f, hit);
+      } else {
+        hit = p.test(f);
+        this.debug("pattern match", p, f, hit);
+      }
+      if (!hit)
+        return false;
+    }
+    if (fi === fl && pi === pl) {
+      return true;
+    } else if (fi === fl) {
+      return partial;
+    } else if (pi === pl) {
+      return fi === fl - 1 && file[fi] === "";
+    } else {
+      throw new Error("wtf?");
+    }
+  }
+  braceExpand() {
+    return braceExpand(this.pattern, this.options);
+  }
+  parse(pattern) {
+    assertValidPattern(pattern);
+    const options = this.options;
+    if (pattern === "**")
+      return GLOBSTAR;
+    if (pattern === "")
+      return "";
+    let m;
+    let fastTest = null;
+    if (m = pattern.match(starRE)) {
+      fastTest = options.dot ? starTestDot : starTest;
+    } else if (m = pattern.match(starDotExtRE)) {
+      fastTest = (options.nocase ? options.dot ? starDotExtTestNocaseDot : starDotExtTestNocase : options.dot ? starDotExtTestDot : starDotExtTest)(m[1]);
+    } else if (m = pattern.match(qmarksRE)) {
+      fastTest = (options.nocase ? options.dot ? qmarksTestNocaseDot : qmarksTestNocase : options.dot ? qmarksTestDot : qmarksTest)(m);
+    } else if (m = pattern.match(starDotStarRE)) {
+      fastTest = options.dot ? starDotStarTestDot : starDotStarTest;
+    } else if (m = pattern.match(dotStarRE)) {
+      fastTest = dotStarTest;
+    }
+    const re = AST.fromGlob(pattern, this.options).toMMPattern();
+    if (fastTest && typeof re === "object") {
+      Reflect.defineProperty(re, "test", { value: fastTest });
+    }
+    return re;
+  }
+  makeRe() {
+    if (this.regexp || this.regexp === false)
+      return this.regexp;
+    const set = this.set;
+    if (!set.length) {
+      this.regexp = false;
+      return this.regexp;
+    }
+    const options = this.options;
+    const twoStar = options.noglobstar ? star2 : options.dot ? twoStarDot : twoStarNoDot;
+    const flags = new Set(options.nocase ? ["i"] : []);
+    let re = set.map((pattern) => {
+      const pp = pattern.map((p) => {
+        if (p instanceof RegExp) {
+          for (const f of p.flags.split(""))
+            flags.add(f);
+        }
+        return typeof p === "string" ? regExpEscape2(p) : p === GLOBSTAR ? GLOBSTAR : p._src;
+      });
+      pp.forEach((p, i) => {
+        const next = pp[i + 1];
+        const prev = pp[i - 1];
+        if (p !== GLOBSTAR || prev === GLOBSTAR) {
+          return;
+        }
+        if (prev === void 0) {
+          if (next !== void 0 && next !== GLOBSTAR) {
+            pp[i + 1] = "(?:\\/|" + twoStar + "\\/)?" + next;
+          } else {
+            pp[i] = twoStar;
+          }
+        } else if (next === void 0) {
+          pp[i - 1] = prev + "(?:\\/|\\/" + twoStar + ")?";
+        } else if (next !== GLOBSTAR) {
+          pp[i - 1] = prev + "(?:\\/|\\/" + twoStar + "\\/)" + next;
+          pp[i + 1] = GLOBSTAR;
+        }
+      });
+      const filtered = pp.filter((p) => p !== GLOBSTAR);
+      if (this.partial && filtered.length >= 1) {
+        const prefixes = [];
+        for (let i = 1; i <= filtered.length; i++) {
+          prefixes.push(filtered.slice(0, i).join("/"));
+        }
+        return "(?:" + prefixes.join("|") + ")";
+      }
+      return filtered.join("/");
+    }).join("|");
+    const [open, close] = set.length > 1 ? ["(?:", ")"] : ["", ""];
+    re = "^" + open + re + close + "$";
+    if (this.partial) {
+      re = "^(?:\\/|" + open + re.slice(1, -1) + close + ")$";
+    }
+    if (this.negate)
+      re = "^(?!" + re + ").+$";
+    try {
+      this.regexp = new RegExp(re, [...flags].join(""));
+    } catch {
+      this.regexp = false;
+    }
+    return this.regexp;
+  }
+  slashSplit(p) {
+    if (this.preserveMultipleSlashes) {
+      return p.split("/");
+    } else if (this.isWindows && /^\/\/[^/]+/.test(p)) {
+      return ["", ...p.split(/\/+/)];
+    } else {
+      return p.split(/\/+/);
+    }
+  }
+  match(f, partial = this.partial) {
+    this.debug("match", f, this.pattern);
+    if (this.comment) {
+      return false;
+    }
+    if (this.empty) {
+      return f === "";
+    }
+    if (f === "/" && partial) {
+      return true;
+    }
+    const options = this.options;
+    if (this.isWindows) {
+      f = f.split("\\").join("/");
+    }
+    const ff = this.slashSplit(f);
+    this.debug(this.pattern, "split", ff);
+    const set = this.set;
+    this.debug(this.pattern, "set", set);
+    let filename = ff[ff.length - 1];
+    if (!filename) {
+      for (let i = ff.length - 2; !filename && i >= 0; i--) {
+        filename = ff[i];
+      }
+    }
+    for (const pattern of set) {
+      let file = ff;
+      if (options.matchBase && pattern.length === 1) {
+        file = [filename];
+      }
+      const hit = this.matchOne(file, pattern, partial);
+      if (hit) {
+        if (options.flipNegate) {
+          return true;
+        }
+        return !this.negate;
+      }
+    }
+    if (options.flipNegate) {
+      return false;
+    }
+    return this.negate;
+  }
+  static defaults(def) {
+    return minimatch.defaults(def).Minimatch;
+  }
+};
+minimatch.AST = AST;
+minimatch.Minimatch = Minimatch;
+minimatch.escape = escape;
+minimatch.unescape = unescape;
+
+// node_modules/glob/dist/esm/glob.js
+import { fileURLToPath as fileURLToPath2 } from "node:url";
+
+// node_modules/lru-cache/dist/esm/node/index.min.js
+import { tracingChannel as j, channel as I } from "node:diagnostics_channel";
+var S = I("lru-cache:metrics");
+var W = j("lru-cache");
+var D = () => S.hasSubscribers || W.hasSubscribers;
+var G = typeof performance == "object" && performance && typeof performance.now == "function" ? performance : Date;
+var M = /* @__PURE__ */ new Set();
+var C = typeof process == "object" && process ? process : {};
+var P = (u3, e, t, i) => {
+  typeof C.emitWarning == "function" ? C.emitWarning(u3, e, t, i) : console.error(`[${t}] ${e}: ${u3}`);
+};
+var H = (u3) => !M.has(u3);
+var F = (u3) => !!u3 && u3 === Math.floor(u3) && u3 > 0 && isFinite(u3);
+var U = (u3) => F(u3) ? u3 <= Math.pow(2, 8) ? Uint8Array : u3 <= Math.pow(2, 16) ? Uint16Array : u3 <= Math.pow(2, 32) ? Uint32Array : u3 <= Number.MAX_SAFE_INTEGER ? O : null : null;
+var O = class extends Array {
+  constructor(e) {
+    super(e), this.fill(0);
+  }
+};
+var R = class u {
+  heap;
+  length;
+  static #o = false;
+  static create(e) {
+    let t = U(e);
+    if (!t) return [];
+    u.#o = true;
+    let i = new u(e, t);
+    return u.#o = false, i;
+  }
+  constructor(e, t) {
+    if (!u.#o) throw new TypeError("instantiate Stack using Stack.create(n)");
+    this.heap = new t(e), this.length = 0;
+  }
+  push(e) {
+    this.heap[this.length++] = e;
+  }
+  pop() {
+    return this.heap[--this.length];
+  }
+};
+var L = class u2 {
+  #o;
+  #u;
+  #w;
+  #D;
+  #S;
+  #M;
+  #U;
+  #m;
+  get perf() {
+    return this.#m;
+  }
+  ttl;
+  ttlResolution;
+  ttlAutopurge;
+  updateAgeOnGet;
+  updateAgeOnHas;
+  allowStale;
+  noDisposeOnSet;
+  noUpdateTTL;
+  maxEntrySize;
+  sizeCalculation;
+  noDeleteOnFetchRejection;
+  noDeleteOnStaleGet;
+  allowStaleOnFetchAbort;
+  allowStaleOnFetchRejection;
+  ignoreFetchAbort;
+  #n;
+  #b;
+  #s;
+  #i;
+  #t;
+  #a;
+  #c;
+  #l;
+  #h;
+  #y;
+  #r;
+  #_;
+  #F;
+  #d;
+  #g;
+  #T;
+  #W;
+  #f;
+  #j;
+  static unsafeExposeInternals(e) {
+    return { starts: e.#F, ttls: e.#d, autopurgeTimers: e.#g, sizes: e.#_, keyMap: e.#s, keyList: e.#i, valList: e.#t, next: e.#a, prev: e.#c, get head() {
+      return e.#l;
+    }, get tail() {
+      return e.#h;
+    }, free: e.#y, isBackgroundFetch: (t) => e.#e(t), backgroundFetch: (t, i, s, n) => e.#P(t, i, s, n), moveToTail: (t) => e.#L(t), indexes: (t) => e.#A(t), rindexes: (t) => e.#z(t), isStale: (t) => e.#p(t) };
+  }
+  get max() {
+    return this.#o;
+  }
+  get maxSize() {
+    return this.#u;
+  }
+  get calculatedSize() {
+    return this.#b;
+  }
+  get size() {
+    return this.#n;
+  }
+  get fetchMethod() {
+    return this.#M;
+  }
+  get memoMethod() {
+    return this.#U;
+  }
+  get dispose() {
+    return this.#w;
+  }
+  get onInsert() {
+    return this.#D;
+  }
+  get disposeAfter() {
+    return this.#S;
+  }
+  constructor(e) {
+    let { max: t = 0, ttl: i, ttlResolution: s = 1, ttlAutopurge: n, updateAgeOnGet: o, updateAgeOnHas: r, allowStale: h, dispose: l, onInsert: c, disposeAfter: f, noDisposeOnSet: g, noUpdateTTL: p, maxSize: T = 0, maxEntrySize: w = 0, sizeCalculation: y, fetchMethod: a, memoMethod: m, noDeleteOnFetchRejection: _, noDeleteOnStaleGet: b, allowStaleOnFetchRejection: d, allowStaleOnFetchAbort: A, ignoreFetchAbort: z, perf: x } = e;
+    if (x !== void 0 && typeof x?.now != "function") throw new TypeError("perf option must have a now() method if specified");
+    if (this.#m = x ?? G, t !== 0 && !F(t)) throw new TypeError("max option must be a nonnegative integer");
+    let v = t ? U(t) : Array;
+    if (!v) throw new Error("invalid max value: " + t);
+    if (this.#o = t, this.#u = T, this.maxEntrySize = w || this.#u, this.sizeCalculation = y, this.sizeCalculation) {
+      if (!this.#u && !this.maxEntrySize) throw new TypeError("cannot set sizeCalculation without setting maxSize or maxEntrySize");
+      if (typeof this.sizeCalculation != "function") throw new TypeError("sizeCalculation set to non-function");
+    }
+    if (m !== void 0 && typeof m != "function") throw new TypeError("memoMethod must be a function if defined");
+    if (this.#U = m, a !== void 0 && typeof a != "function") throw new TypeError("fetchMethod must be a function if specified");
+    if (this.#M = a, this.#W = !!a, this.#s = /* @__PURE__ */ new Map(), this.#i = Array.from({ length: t }).fill(void 0), this.#t = Array.from({ length: t }).fill(void 0), this.#a = new v(t), this.#c = new v(t), this.#l = 0, this.#h = 0, this.#y = R.create(t), this.#n = 0, this.#b = 0, typeof l == "function" && (this.#w = l), typeof c == "function" && (this.#D = c), typeof f == "function" ? (this.#S = f, this.#r = []) : (this.#S = void 0, this.#r = void 0), this.#T = !!this.#w, this.#j = !!this.#D, this.#f = !!this.#S, this.noDisposeOnSet = !!g, this.noUpdateTTL = !!p, this.noDeleteOnFetchRejection = !!_, this.allowStaleOnFetchRejection = !!d, this.allowStaleOnFetchAbort = !!A, this.ignoreFetchAbort = !!z, this.maxEntrySize !== 0) {
+      if (this.#u !== 0 && !F(this.#u)) throw new TypeError("maxSize must be a positive integer if specified");
+      if (!F(this.maxEntrySize)) throw new TypeError("maxEntrySize must be a positive integer if specified");
+      this.#X();
+    }
+    if (this.allowStale = !!h, this.noDeleteOnStaleGet = !!b, this.updateAgeOnGet = !!o, this.updateAgeOnHas = !!r, this.ttlResolution = F(s) || s === 0 ? s : 1, this.ttlAutopurge = !!n, this.ttl = i || 0, this.ttl) {
+      if (!F(this.ttl)) throw new TypeError("ttl must be a positive integer if specified");
+      this.#H();
+    }
+    if (this.#o === 0 && this.ttl === 0 && this.#u === 0) throw new TypeError("At least one of max, maxSize, or ttl is required");
+    if (!this.ttlAutopurge && !this.#o && !this.#u) {
+      let E = "LRU_CACHE_UNBOUNDED";
+      H(E) && (M.add(E), P("TTL caching without ttlAutopurge, max, or maxSize can result in unbounded memory consumption.", "UnboundedCacheWarning", E, u2));
+    }
+  }
+  getRemainingTTL(e) {
+    return this.#s.has(e) ? 1 / 0 : 0;
+  }
+  #H() {
+    let e = new O(this.#o), t = new O(this.#o);
+    this.#d = e, this.#F = t;
+    let i = this.ttlAutopurge ? Array.from({ length: this.#o }) : void 0;
+    this.#g = i, this.#N = (r, h, l = this.#m.now()) => {
+      t[r] = h !== 0 ? l : 0, e[r] = h, s(r, h);
+    }, this.#x = (r) => {
+      t[r] = e[r] !== 0 ? this.#m.now() : 0, s(r, e[r]);
+    };
+    let s = this.ttlAutopurge ? (r, h) => {
+      if (i?.[r] && (clearTimeout(i[r]), i[r] = void 0), h && h !== 0 && i) {
+        let l = setTimeout(() => {
+          this.#p(r) && this.#v(this.#i[r], "expire");
+        }, h + 1);
+        l.unref && l.unref(), i[r] = l;
+      }
+    } : () => {
+    };
+    this.#E = (r, h) => {
+      if (e[h]) {
+        let l = e[h], c = t[h];
+        if (!l || !c) return;
+        r.ttl = l, r.start = c, r.now = n || o();
+        let f = r.now - c;
+        r.remainingTTL = l - f;
+      }
+    };
+    let n = 0, o = () => {
+      let r = this.#m.now();
+      if (this.ttlResolution > 0) {
+        n = r;
+        let h = setTimeout(() => n = 0, this.ttlResolution);
+        h.unref && h.unref();
+      }
+      return r;
+    };
+    this.getRemainingTTL = (r) => {
+      let h = this.#s.get(r);
+      if (h === void 0) return 0;
+      let l = e[h], c = t[h];
+      if (!l || !c) return 1 / 0;
+      let f = (n || o()) - c;
+      return l - f;
+    }, this.#p = (r) => {
+      let h = t[r], l = e[r];
+      return !!l && !!h && (n || o()) - h > l;
+    };
+  }
+  #x = () => {
+  };
+  #E = () => {
+  };
+  #N = () => {
+  };
+  #p = () => false;
+  #X() {
+    let e = new O(this.#o);
+    this.#b = 0, this.#_ = e, this.#R = (t) => {
+      this.#b -= e[t], e[t] = 0;
+    }, this.#k = (t, i, s, n) => {
+      if (this.#e(i)) return 0;
+      if (!F(s)) if (n) {
+        if (typeof n != "function") throw new TypeError("sizeCalculation must be a function");
+        if (s = n(i, t), !F(s)) throw new TypeError("sizeCalculation return invalid (expect positive integer)");
+      } else throw new TypeError("invalid size value (must be positive integer). When maxSize or maxEntrySize is used, sizeCalculation or size must be set.");
+      return s;
+    }, this.#I = (t, i, s) => {
+      if (e[t] = i, this.#u) {
+        let n = this.#u - e[t];
+        for (; this.#b > n; ) this.#G(true);
+      }
+      this.#b += e[t], s && (s.entrySize = i, s.totalCalculatedSize = this.#b);
+    };
+  }
+  #R = (e) => {
+  };
+  #I = (e, t, i) => {
+  };
+  #k = (e, t, i, s) => {
+    if (i || s) throw new TypeError("cannot set size without setting maxSize or maxEntrySize on cache");
+    return 0;
+  };
+  *#A({ allowStale: e = this.allowStale } = {}) {
+    if (this.#n) for (let t = this.#h; this.#V(t) && ((e || !this.#p(t)) && (yield t), t !== this.#l); ) t = this.#c[t];
+  }
+  *#z({ allowStale: e = this.allowStale } = {}) {
+    if (this.#n) for (let t = this.#l; this.#V(t) && ((e || !this.#p(t)) && (yield t), t !== this.#h); ) t = this.#a[t];
+  }
+  #V(e) {
+    return e !== void 0 && this.#s.get(this.#i[e]) === e;
+  }
+  *entries() {
+    for (let e of this.#A()) this.#t[e] !== void 0 && this.#i[e] !== void 0 && !this.#e(this.#t[e]) && (yield [this.#i[e], this.#t[e]]);
+  }
+  *rentries() {
+    for (let e of this.#z()) this.#t[e] !== void 0 && this.#i[e] !== void 0 && !this.#e(this.#t[e]) && (yield [this.#i[e], this.#t[e]]);
+  }
+  *keys() {
+    for (let e of this.#A()) {
+      let t = this.#i[e];
+      t !== void 0 && !this.#e(this.#t[e]) && (yield t);
+    }
+  }
+  *rkeys() {
+    for (let e of this.#z()) {
+      let t = this.#i[e];
+      t !== void 0 && !this.#e(this.#t[e]) && (yield t);
+    }
+  }
+  *values() {
+    for (let e of this.#A()) this.#t[e] !== void 0 && !this.#e(this.#t[e]) && (yield this.#t[e]);
+  }
+  *rvalues() {
+    for (let e of this.#z()) this.#t[e] !== void 0 && !this.#e(this.#t[e]) && (yield this.#t[e]);
+  }
+  [Symbol.iterator]() {
+    return this.entries();
+  }
+  [Symbol.toStringTag] = "LRUCache";
+  find(e, t = {}) {
+    for (let i of this.#A()) {
+      let s = this.#t[i], n = this.#e(s) ? s.__staleWhileFetching : s;
+      if (n !== void 0 && e(n, this.#i[i], this)) return this.#C(this.#i[i], t);
+    }
+  }
+  forEach(e, t = this) {
+    for (let i of this.#A()) {
+      let s = this.#t[i], n = this.#e(s) ? s.__staleWhileFetching : s;
+      n !== void 0 && e.call(t, n, this.#i[i], this);
+    }
+  }
+  rforEach(e, t = this) {
+    for (let i of this.#z()) {
+      let s = this.#t[i], n = this.#e(s) ? s.__staleWhileFetching : s;
+      n !== void 0 && e.call(t, n, this.#i[i], this);
+    }
+  }
+  purgeStale() {
+    let e = false;
+    for (let t of this.#z({ allowStale: true })) this.#p(t) && (this.#v(this.#i[t], "expire"), e = true);
+    return e;
+  }
+  info(e) {
+    let t = this.#s.get(e);
+    if (t === void 0) return;
+    let i = this.#t[t], s = this.#e(i) ? i.__staleWhileFetching : i;
+    if (s === void 0) return;
+    let n = { value: s };
+    if (this.#d && this.#F) {
+      let o = this.#d[t], r = this.#F[t];
+      if (o && r) {
+        let h = o - (this.#m.now() - r);
+        n.ttl = h, n.start = Date.now();
+      }
+    }
+    return this.#_ && (n.size = this.#_[t]), n;
+  }
+  dump() {
+    let e = [];
+    for (let t of this.#A({ allowStale: true })) {
+      let i = this.#i[t], s = this.#t[t], n = this.#e(s) ? s.__staleWhileFetching : s;
+      if (n === void 0 || i === void 0) continue;
+      let o = { value: n };
+      if (this.#d && this.#F) {
+        o.ttl = this.#d[t];
+        let r = this.#m.now() - this.#F[t];
+        o.start = Math.floor(Date.now() - r);
+      }
+      this.#_ && (o.size = this.#_[t]), e.unshift([i, o]);
+    }
+    return e;
+  }
+  load(e) {
+    this.clear();
+    for (let [t, i] of e) {
+      if (i.start) {
+        let s = Date.now() - i.start;
+        i.start = this.#m.now() - s;
+      }
+      this.#O(t, i.value, i);
+    }
+  }
+  set(e, t, i = {}) {
+    let { status: s = S.hasSubscribers ? {} : void 0 } = i;
+    i.status = s, s && (s.op = "set", s.key = e, t !== void 0 && (s.value = t));
+    let n = this.#O(e, t, i);
+    return s && S.hasSubscribers && S.publish(s), n;
+  }
+  #O(e, t, i = {}) {
+    let { ttl: s = this.ttl, start: n, noDisposeOnSet: o = this.noDisposeOnSet, sizeCalculation: r = this.sizeCalculation, status: h } = i;
+    if (t === void 0) return h && (h.set = "deleted"), this.delete(e), this;
+    let { noUpdateTTL: l = this.noUpdateTTL } = i;
+    h && !this.#e(t) && (h.value = t);
+    let c = this.#k(e, t, i.size || 0, r, h);
+    if (this.maxEntrySize && c > this.maxEntrySize) return this.#v(e, "set"), h && (h.set = "miss", h.maxEntrySizeExceeded = true), this;
+    let f = this.#n === 0 ? void 0 : this.#s.get(e);
+    if (f === void 0) f = this.#n === 0 ? this.#h : this.#y.length !== 0 ? this.#y.pop() : this.#n === this.#o ? this.#G(false) : this.#n, this.#i[f] = e, this.#t[f] = t, this.#s.set(e, f), this.#a[this.#h] = f, this.#c[f] = this.#h, this.#h = f, this.#n++, this.#I(f, c, h), h && (h.set = "add"), l = false, this.#j && this.#D?.(t, e, "add");
+    else {
+      this.#L(f);
+      let g = this.#t[f];
+      if (t !== g) {
+        if (this.#W && this.#e(g)) {
+          g.__abortController.abort(new Error("replaced"));
+          let { __staleWhileFetching: p } = g;
+          p !== void 0 && !o && (this.#T && this.#w?.(p, e, "set"), this.#f && this.#r?.push([p, e, "set"]));
+        } else o || (this.#T && this.#w?.(g, e, "set"), this.#f && this.#r?.push([g, e, "set"]));
+        if (this.#R(f), this.#I(f, c, h), this.#t[f] = t, h) {
+          h.set = "replace";
+          let p = g && this.#e(g) ? g.__staleWhileFetching : g;
+          p !== void 0 && (h.oldValue = p);
+        }
+      } else h && (h.set = "update");
+      this.#j && this.onInsert?.(t, e, t === g ? "update" : "replace");
+    }
+    if (s !== 0 && !this.#d && this.#H(), this.#d && (l || this.#N(f, s, n), h && this.#E(h, f)), !o && this.#f && this.#r) {
+      let g = this.#r, p;
+      for (; p = g?.shift(); ) this.#S?.(...p);
+    }
+    return this;
+  }
+  pop() {
+    try {
+      for (; this.#n; ) {
+        let e = this.#t[this.#l];
+        if (this.#G(true), this.#e(e)) {
+          if (e.__staleWhileFetching) return e.__staleWhileFetching;
+        } else if (e !== void 0) return e;
+      }
+    } finally {
+      if (this.#f && this.#r) {
+        let e = this.#r, t;
+        for (; t = e?.shift(); ) this.#S?.(...t);
+      }
+    }
+  }
+  #G(e) {
+    let t = this.#l, i = this.#i[t], s = this.#t[t];
+    return this.#W && this.#e(s) ? s.__abortController.abort(new Error("evicted")) : (this.#T || this.#f) && (this.#T && this.#w?.(s, i, "evict"), this.#f && this.#r?.push([s, i, "evict"])), this.#R(t), this.#g?.[t] && (clearTimeout(this.#g[t]), this.#g[t] = void 0), e && (this.#i[t] = void 0, this.#t[t] = void 0, this.#y.push(t)), this.#n === 1 ? (this.#l = this.#h = 0, this.#y.length = 0) : this.#l = this.#a[t], this.#s.delete(i), this.#n--, t;
+  }
+  has(e, t = {}) {
+    let { status: i = S.hasSubscribers ? {} : void 0 } = t;
+    t.status = i, i && (i.op = "has", i.key = e);
+    let s = this.#Y(e, t);
+    return S.hasSubscribers && S.publish(i), s;
+  }
+  #Y(e, t = {}) {
+    let { updateAgeOnHas: i = this.updateAgeOnHas, status: s } = t, n = this.#s.get(e);
+    if (n !== void 0) {
+      let o = this.#t[n];
+      if (this.#e(o) && o.__staleWhileFetching === void 0) return false;
+      if (this.#p(n)) s && (s.has = "stale", this.#E(s, n));
+      else return i && this.#x(n), s && (s.has = "hit", this.#E(s, n)), true;
+    } else s && (s.has = "miss");
+    return false;
+  }
+  peek(e, t = {}) {
+    let { status: i = D() ? {} : void 0 } = t;
+    i && (i.op = "peek", i.key = e), t.status = i;
+    let s = this.#J(e, t);
+    return S.hasSubscribers && S.publish(i), s;
+  }
+  #J(e, t) {
+    let { status: i, allowStale: s = this.allowStale } = t, n = this.#s.get(e);
+    if (n === void 0 || !s && this.#p(n)) {
+      i && (i.peek = n === void 0 ? "miss" : "stale");
+      return;
+    }
+    let o = this.#t[n], r = this.#e(o) ? o.__staleWhileFetching : o;
+    return i && (r !== void 0 ? (i.peek = "hit", i.value = r) : i.peek = "miss"), r;
+  }
+  #P(e, t, i, s) {
+    let n = t === void 0 ? void 0 : this.#t[t];
+    if (this.#e(n)) return n;
+    let o = new AbortController(), { signal: r } = i;
+    r?.addEventListener("abort", () => o.abort(r.reason), { signal: o.signal });
+    let h = { signal: o.signal, options: i, context: s }, l = (w, y = false) => {
+      let { aborted: a } = o.signal, m = i.ignoreFetchAbort && w !== void 0, _ = i.ignoreFetchAbort || !!(i.allowStaleOnFetchAbort && w !== void 0);
+      if (i.status && (a && !y ? (i.status.fetchAborted = true, i.status.fetchError = o.signal.reason, m && (i.status.fetchAbortIgnored = true)) : i.status.fetchResolved = true), a && !m && !y) return f(o.signal.reason, _);
+      let b = p, d = this.#t[t];
+      return (d === p || d === void 0 && m && y) && (w === void 0 ? b.__staleWhileFetching !== void 0 ? this.#t[t] = b.__staleWhileFetching : this.#v(e, "fetch") : (i.status && (i.status.fetchUpdated = true), this.#O(e, w, h.options))), w;
+    }, c = (w) => (i.status && (i.status.fetchRejected = true, i.status.fetchError = w), f(w, false)), f = (w, y) => {
+      let { aborted: a } = o.signal, m = a && i.allowStaleOnFetchAbort, _ = m || i.allowStaleOnFetchRejection, b = _ || i.noDeleteOnFetchRejection, d = p;
+      if (this.#t[t] === p && (!b || !y && d.__staleWhileFetching === void 0 ? this.#v(e, "fetch") : m || (this.#t[t] = d.__staleWhileFetching)), _) return i.status && d.__staleWhileFetching !== void 0 && (i.status.returnedStale = true), d.__staleWhileFetching;
+      if (d.__returned === d) throw w;
+    }, g = (w, y) => {
+      let a = this.#M?.(e, n, h);
+      a && a instanceof Promise && a.then((m) => w(m === void 0 ? void 0 : m), y), o.signal.addEventListener("abort", () => {
+        (!i.ignoreFetchAbort || i.allowStaleOnFetchAbort) && (w(void 0), i.allowStaleOnFetchAbort && (w = (m) => l(m, true)));
+      });
+    };
+    i.status && (i.status.fetchDispatched = true);
+    let p = new Promise(g).then(l, c), T = Object.assign(p, { __abortController: o, __staleWhileFetching: n, __returned: void 0 });
+    return t === void 0 ? (this.#O(e, T, { ...h.options, status: void 0 }), t = this.#s.get(e)) : this.#t[t] = T, T;
+  }
+  #e(e) {
+    if (!this.#W) return false;
+    let t = e;
+    return !!t && t instanceof Promise && t.hasOwnProperty("__staleWhileFetching") && t.__abortController instanceof AbortController;
+  }
+  fetch(e, t = {}) {
+    let i = W.hasSubscribers, { status: s = D() ? {} : void 0 } = t;
+    t.status = s, s && t.context && (s.context = t.context);
+    let n = this.#B(e, t);
+    return s && D() && i && (s.trace = true, W.tracePromise(() => n, s).catch(() => {
+    })), n;
+  }
+  async #B(e, t = {}) {
+    let { allowStale: i = this.allowStale, updateAgeOnGet: s = this.updateAgeOnGet, noDeleteOnStaleGet: n = this.noDeleteOnStaleGet, ttl: o = this.ttl, noDisposeOnSet: r = this.noDisposeOnSet, size: h = 0, sizeCalculation: l = this.sizeCalculation, noUpdateTTL: c = this.noUpdateTTL, noDeleteOnFetchRejection: f = this.noDeleteOnFetchRejection, allowStaleOnFetchRejection: g = this.allowStaleOnFetchRejection, ignoreFetchAbort: p = this.ignoreFetchAbort, allowStaleOnFetchAbort: T = this.allowStaleOnFetchAbort, context: w, forceRefresh: y = false, status: a, signal: m } = t;
+    if (a && (a.op = "fetch", a.key = e, y && (a.forceRefresh = true)), !this.#W) return a && (a.fetch = "get"), this.#C(e, { allowStale: i, updateAgeOnGet: s, noDeleteOnStaleGet: n, status: a });
+    let _ = { allowStale: i, updateAgeOnGet: s, noDeleteOnStaleGet: n, ttl: o, noDisposeOnSet: r, size: h, sizeCalculation: l, noUpdateTTL: c, noDeleteOnFetchRejection: f, allowStaleOnFetchRejection: g, allowStaleOnFetchAbort: T, ignoreFetchAbort: p, status: a, signal: m }, b = this.#s.get(e);
+    if (b === void 0) {
+      a && (a.fetch = "miss");
+      let d = this.#P(e, b, _, w);
+      return d.__returned = d;
+    } else {
+      let d = this.#t[b];
+      if (this.#e(d)) {
+        let E = i && d.__staleWhileFetching !== void 0;
+        return a && (a.fetch = "inflight", E && (a.returnedStale = true)), E ? d.__staleWhileFetching : d.__returned = d;
+      }
+      let A = this.#p(b);
+      if (!y && !A) return a && (a.fetch = "hit"), this.#L(b), s && this.#x(b), a && this.#E(a, b), d;
+      let z = this.#P(e, b, _, w), v = z.__staleWhileFetching !== void 0 && i;
+      return a && (a.fetch = A ? "stale" : "refresh", v && A && (a.returnedStale = true)), v ? z.__staleWhileFetching : z.__returned = z;
+    }
+  }
+  forceFetch(e, t = {}) {
+    let i = W.hasSubscribers, { status: s = D() ? {} : void 0 } = t;
+    t.status = s, s && t.context && (s.context = t.context);
+    let n = this.#K(e, t);
+    return s && D() && i && (s.trace = true, W.tracePromise(() => n, s).catch(() => {
+    })), n;
+  }
+  async #K(e, t = {}) {
+    let i = await this.#B(e, t);
+    if (i === void 0) throw new Error("fetch() returned undefined");
+    return i;
+  }
+  memo(e, t = {}) {
+    let { status: i = S.hasSubscribers ? {} : void 0 } = t;
+    t.status = i, i && (i.op = "memo", i.key = e, t.context && (i.context = t.context));
+    let s = this.#Q(e, t);
+    return i && (i.value = s), S.hasSubscribers && S.publish(i), s;
+  }
+  #Q(e, t = {}) {
+    let i = this.#U;
+    if (!i) throw new Error("no memoMethod provided to constructor");
+    let { context: s, status: n, forceRefresh: o, ...r } = t;
+    n && o && (n.forceRefresh = true);
+    let h = this.#C(e, r), l = o || h === void 0;
+    if (n && (n.memo = l ? "miss" : "hit", l || (n.value = h)), !l) return h;
+    let c = i(e, h, { options: r, context: s });
+    return n && (n.value = c), this.#O(e, c, r), c;
+  }
+  get(e, t = {}) {
+    let { status: i = S.hasSubscribers ? {} : void 0 } = t;
+    t.status = i, i && (i.op = "get", i.key = e);
+    let s = this.#C(e, t);
+    return i && (s !== void 0 && (i.value = s), S.hasSubscribers && S.publish(i)), s;
+  }
+  #C(e, t = {}) {
+    let { allowStale: i = this.allowStale, updateAgeOnGet: s = this.updateAgeOnGet, noDeleteOnStaleGet: n = this.noDeleteOnStaleGet, status: o } = t, r = this.#s.get(e);
+    if (r === void 0) {
+      o && (o.get = "miss");
+      return;
+    }
+    let h = this.#t[r], l = this.#e(h);
+    return o && this.#E(o, r), this.#p(r) ? l ? (o && (o.get = "stale-fetching"), i && h.__staleWhileFetching !== void 0 ? (o && (o.returnedStale = true), h.__staleWhileFetching) : void 0) : (n || this.#v(e, "expire"), o && (o.get = "stale"), i ? (o && (o.returnedStale = true), h) : void 0) : (o && (o.get = l ? "fetching" : "hit"), this.#L(r), s && this.#x(r), l ? h.__staleWhileFetching : h);
+  }
+  #$(e, t) {
+    this.#c[t] = e, this.#a[e] = t;
+  }
+  #L(e) {
+    e !== this.#h && (e === this.#l ? this.#l = this.#a[e] : this.#$(this.#c[e], this.#a[e]), this.#$(this.#h, e), this.#h = e);
+  }
+  delete(e) {
+    return this.#v(e, "delete");
+  }
+  #v(e, t) {
+    S.hasSubscribers && S.publish({ op: "delete", delete: t, key: e });
+    let i = false;
+    if (this.#n !== 0) {
+      let s = this.#s.get(e);
+      if (s !== void 0) if (this.#g?.[s] && (clearTimeout(this.#g?.[s]), this.#g[s] = void 0), i = true, this.#n === 1) this.#q(t);
+      else {
+        this.#R(s);
+        let n = this.#t[s];
+        if (this.#e(n) ? n.__abortController.abort(new Error("deleted")) : (this.#T || this.#f) && (this.#T && this.#w?.(n, e, t), this.#f && this.#r?.push([n, e, t])), this.#s.delete(e), this.#i[s] = void 0, this.#t[s] = void 0, s === this.#h) this.#h = this.#c[s];
+        else if (s === this.#l) this.#l = this.#a[s];
+        else {
+          let o = this.#c[s];
+          this.#a[o] = this.#a[s];
+          let r = this.#a[s];
+          this.#c[r] = this.#c[s];
+        }
+        this.#n--, this.#y.push(s);
+      }
+    }
+    if (this.#f && this.#r?.length) {
+      let s = this.#r, n;
+      for (; n = s?.shift(); ) this.#S?.(...n);
+    }
+    return i;
+  }
+  clear() {
+    return this.#q("delete");
+  }
+  #q(e) {
+    for (let t of this.#z({ allowStale: true })) {
+      let i = this.#t[t];
+      if (this.#e(i)) i.__abortController.abort(new Error("deleted"));
+      else {
+        let s = this.#i[t];
+        this.#T && this.#w?.(i, s, e), this.#f && this.#r?.push([i, s, e]);
+      }
+    }
+    if (this.#s.clear(), this.#t.fill(void 0), this.#i.fill(void 0), this.#d && this.#F) {
+      this.#d.fill(0), this.#F.fill(0);
+      for (let t of this.#g ?? []) t !== void 0 && clearTimeout(t);
+      this.#g?.fill(void 0);
+    }
+    if (this.#_ && this.#_.fill(0), this.#l = 0, this.#h = 0, this.#y.length = 0, this.#b = 0, this.#n = 0, this.#f && this.#r) {
+      let t = this.#r, i;
+      for (; i = t?.shift(); ) this.#S?.(...i);
+    }
+  }
+};
+
+// node_modules/path-scurry/dist/esm/index.js
+import { posix, win32 } from "node:path";
+import { fileURLToPath } from "node:url";
+import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSync as rps } from "fs";
+import * as actualFS from "node:fs";
+import { lstat, readdir, readlink, realpath } from "node:fs/promises";
+
+// node_modules/minipass/dist/esm/index.js
+import { EventEmitter } from "node:events";
+import Stream from "node:stream";
+import { StringDecoder } from "node:string_decoder";
+var proc = typeof process === "object" && process ? process : {
+  stdout: null,
+  stderr: null
+};
+var isStream = (s) => !!s && typeof s === "object" && (s instanceof Minipass || s instanceof Stream || isReadable(s) || isWritable(s));
+var isReadable = (s) => !!s && typeof s === "object" && s instanceof EventEmitter && typeof s.pipe === "function" && // node core Writable streams have a pipe() method, but it throws
+s.pipe !== Stream.Writable.prototype.pipe;
+var isWritable = (s) => !!s && typeof s === "object" && s instanceof EventEmitter && typeof s.write === "function" && typeof s.end === "function";
+var EOF = /* @__PURE__ */ Symbol("EOF");
+var MAYBE_EMIT_END = /* @__PURE__ */ Symbol("maybeEmitEnd");
+var EMITTED_END = /* @__PURE__ */ Symbol("emittedEnd");
+var EMITTING_END = /* @__PURE__ */ Symbol("emittingEnd");
+var EMITTED_ERROR = /* @__PURE__ */ Symbol("emittedError");
+var CLOSED = /* @__PURE__ */ Symbol("closed");
+var READ = /* @__PURE__ */ Symbol("read");
+var FLUSH = /* @__PURE__ */ Symbol("flush");
+var FLUSHCHUNK = /* @__PURE__ */ Symbol("flushChunk");
+var ENCODING = /* @__PURE__ */ Symbol("encoding");
+var DECODER = /* @__PURE__ */ Symbol("decoder");
+var FLOWING = /* @__PURE__ */ Symbol("flowing");
+var PAUSED = /* @__PURE__ */ Symbol("paused");
+var RESUME = /* @__PURE__ */ Symbol("resume");
+var BUFFER = /* @__PURE__ */ Symbol("buffer");
+var PIPES = /* @__PURE__ */ Symbol("pipes");
+var BUFFERLENGTH = /* @__PURE__ */ Symbol("bufferLength");
+var BUFFERPUSH = /* @__PURE__ */ Symbol("bufferPush");
+var BUFFERSHIFT = /* @__PURE__ */ Symbol("bufferShift");
+var OBJECTMODE = /* @__PURE__ */ Symbol("objectMode");
+var DESTROYED = /* @__PURE__ */ Symbol("destroyed");
+var ERROR = /* @__PURE__ */ Symbol("error");
+var EMITDATA = /* @__PURE__ */ Symbol("emitData");
+var EMITEND = /* @__PURE__ */ Symbol("emitEnd");
+var EMITEND2 = /* @__PURE__ */ Symbol("emitEnd2");
+var ASYNC = /* @__PURE__ */ Symbol("async");
+var ABORT = /* @__PURE__ */ Symbol("abort");
+var ABORTED = /* @__PURE__ */ Symbol("aborted");
+var SIGNAL = /* @__PURE__ */ Symbol("signal");
+var DATALISTENERS = /* @__PURE__ */ Symbol("dataListeners");
+var DISCARDED = /* @__PURE__ */ Symbol("discarded");
+var defer = (fn) => Promise.resolve().then(fn);
+var nodefer = (fn) => fn();
+var isEndish = (ev) => ev === "end" || ev === "finish" || ev === "prefinish";
+var isArrayBufferLike = (b) => b instanceof ArrayBuffer || !!b && typeof b === "object" && b.constructor && b.constructor.name === "ArrayBuffer" && b.byteLength >= 0;
+var isArrayBufferView = (b) => !Buffer.isBuffer(b) && ArrayBuffer.isView(b);
+var Pipe = class {
+  src;
+  dest;
+  opts;
+  ondrain;
+  constructor(src, dest, opts) {
+    this.src = src;
+    this.dest = dest;
+    this.opts = opts;
+    this.ondrain = () => src[RESUME]();
+    this.dest.on("drain", this.ondrain);
+  }
+  unpipe() {
+    this.dest.removeListener("drain", this.ondrain);
+  }
+  // only here for the prototype
+  /* c8 ignore start */
+  proxyErrors(_er) {
+  }
+  /* c8 ignore stop */
+  end() {
+    this.unpipe();
+    if (this.opts.end)
+      this.dest.end();
+  }
+};
+var PipeProxyErrors = class extends Pipe {
+  unpipe() {
+    this.src.removeListener("error", this.proxyErrors);
+    super.unpipe();
+  }
+  constructor(src, dest, opts) {
+    super(src, dest, opts);
+    this.proxyErrors = (er) => this.dest.emit("error", er);
+    src.on("error", this.proxyErrors);
+  }
+};
+var isObjectModeOptions = (o) => !!o.objectMode;
+var isEncodingOptions = (o) => !o.objectMode && !!o.encoding && o.encoding !== "buffer";
+var Minipass = class extends EventEmitter {
+  [FLOWING] = false;
+  [PAUSED] = false;
+  [PIPES] = [];
+  [BUFFER] = [];
+  [OBJECTMODE];
+  [ENCODING];
+  [ASYNC];
+  [DECODER];
+  [EOF] = false;
+  [EMITTED_END] = false;
+  [EMITTING_END] = false;
+  [CLOSED] = false;
+  [EMITTED_ERROR] = null;
+  [BUFFERLENGTH] = 0;
+  [DESTROYED] = false;
+  [SIGNAL];
+  [ABORTED] = false;
+  [DATALISTENERS] = 0;
+  [DISCARDED] = false;
+  /**
+   * true if the stream can be written
+   */
+  writable = true;
+  /**
+   * true if the stream can be read
+   */
+  readable = true;
+  /**
+   * If `RType` is Buffer, then options do not need to be provided.
+   * Otherwise, an options object must be provided to specify either
+   * {@link Minipass.SharedOptions.objectMode} or
+   * {@link Minipass.SharedOptions.encoding}, as appropriate.
+   */
+  constructor(...args) {
+    const options = args[0] || {};
+    super();
+    if (options.objectMode && typeof options.encoding === "string") {
+      throw new TypeError("Encoding and objectMode may not be used together");
+    }
+    if (isObjectModeOptions(options)) {
+      this[OBJECTMODE] = true;
+      this[ENCODING] = null;
+    } else if (isEncodingOptions(options)) {
+      this[ENCODING] = options.encoding;
+      this[OBJECTMODE] = false;
+    } else {
+      this[OBJECTMODE] = false;
+      this[ENCODING] = null;
+    }
+    this[ASYNC] = !!options.async;
+    this[DECODER] = this[ENCODING] ? new StringDecoder(this[ENCODING]) : null;
+    if (options && options.debugExposeBuffer === true) {
+      Object.defineProperty(this, "buffer", { get: () => this[BUFFER] });
+    }
+    if (options && options.debugExposePipes === true) {
+      Object.defineProperty(this, "pipes", { get: () => this[PIPES] });
+    }
+    const { signal } = options;
+    if (signal) {
+      this[SIGNAL] = signal;
+      if (signal.aborted) {
+        this[ABORT]();
+      } else {
+        signal.addEventListener("abort", () => this[ABORT]());
+      }
+    }
+  }
+  /**
+   * The amount of data stored in the buffer waiting to be read.
+   *
+   * For Buffer strings, this will be the total byte length.
+   * For string encoding streams, this will be the string character length,
+   * according to JavaScript's `string.length` logic.
+   * For objectMode streams, this is a count of the items waiting to be
+   * emitted.
+   */
+  get bufferLength() {
+    return this[BUFFERLENGTH];
+  }
+  /**
+   * The `BufferEncoding` currently in use, or `null`
+   */
+  get encoding() {
+    return this[ENCODING];
+  }
+  /**
+   * @deprecated - This is a read only property
+   */
+  set encoding(_enc) {
+    throw new Error("Encoding must be set at instantiation time");
+  }
+  /**
+   * @deprecated - Encoding may only be set at instantiation time
+   */
+  setEncoding(_enc) {
+    throw new Error("Encoding must be set at instantiation time");
+  }
+  /**
+   * True if this is an objectMode stream
+   */
+  get objectMode() {
+    return this[OBJECTMODE];
+  }
+  /**
+   * @deprecated - This is a read-only property
+   */
+  set objectMode(_om) {
+    throw new Error("objectMode must be set at instantiation time");
+  }
+  /**
+   * true if this is an async stream
+   */
+  get ["async"]() {
+    return this[ASYNC];
+  }
+  /**
+   * Set to true to make this stream async.
+   *
+   * Once set, it cannot be unset, as this would potentially cause incorrect
+   * behavior.  Ie, a sync stream can be made async, but an async stream
+   * cannot be safely made sync.
+   */
+  set ["async"](a) {
+    this[ASYNC] = this[ASYNC] || !!a;
+  }
+  // drop everything and get out of the flow completely
+  [ABORT]() {
+    this[ABORTED] = true;
+    this.emit("abort", this[SIGNAL]?.reason);
+    this.destroy(this[SIGNAL]?.reason);
+  }
+  /**
+   * True if the stream has been aborted.
+   */
+  get aborted() {
+    return this[ABORTED];
+  }
+  /**
+   * No-op setter. Stream aborted status is set via the AbortSignal provided
+   * in the constructor options.
+   */
+  set aborted(_) {
+  }
+  write(chunk, encoding, cb) {
+    if (this[ABORTED])
+      return false;
+    if (this[EOF])
+      throw new Error("write after end");
+    if (this[DESTROYED]) {
+      this.emit("error", Object.assign(new Error("Cannot call write after a stream was destroyed"), { code: "ERR_STREAM_DESTROYED" }));
+      return true;
+    }
+    if (typeof encoding === "function") {
+      cb = encoding;
+      encoding = "utf8";
+    }
+    if (!encoding)
+      encoding = "utf8";
+    const fn = this[ASYNC] ? defer : nodefer;
+    if (!this[OBJECTMODE] && !Buffer.isBuffer(chunk)) {
+      if (isArrayBufferView(chunk)) {
+        chunk = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
+      } else if (isArrayBufferLike(chunk)) {
+        chunk = Buffer.from(chunk);
+      } else if (typeof chunk !== "string") {
+        throw new Error("Non-contiguous data written to non-objectMode stream");
+      }
+    }
+    if (this[OBJECTMODE]) {
+      if (this[FLOWING] && this[BUFFERLENGTH] !== 0)
+        this[FLUSH](true);
+      if (this[FLOWING])
+        this.emit("data", chunk);
+      else
+        this[BUFFERPUSH](chunk);
+      if (this[BUFFERLENGTH] !== 0)
+        this.emit("readable");
+      if (cb)
+        fn(cb);
+      return this[FLOWING];
+    }
+    if (!chunk.length) {
+      if (this[BUFFERLENGTH] !== 0)
+        this.emit("readable");
+      if (cb)
+        fn(cb);
+      return this[FLOWING];
+    }
+    if (typeof chunk === "string" && // unless it is a string already ready for us to use
+    !(encoding === this[ENCODING] && !this[DECODER]?.lastNeed)) {
+      chunk = Buffer.from(chunk, encoding);
+    }
+    if (Buffer.isBuffer(chunk) && this[ENCODING]) {
+      chunk = this[DECODER].write(chunk);
+    }
+    if (this[FLOWING] && this[BUFFERLENGTH] !== 0)
+      this[FLUSH](true);
+    if (this[FLOWING])
+      this.emit("data", chunk);
+    else
+      this[BUFFERPUSH](chunk);
+    if (this[BUFFERLENGTH] !== 0)
+      this.emit("readable");
+    if (cb)
+      fn(cb);
+    return this[FLOWING];
+  }
+  /**
+   * Low-level explicit read method.
+   *
+   * In objectMode, the argument is ignored, and one item is returned if
+   * available.
+   *
+   * `n` is the number of bytes (or in the case of encoding streams,
+   * characters) to consume. If `n` is not provided, then the entire buffer
+   * is returned, or `null` is returned if no data is available.
+   *
+   * If `n` is greater that the amount of data in the internal buffer,
+   * then `null` is returned.
+   */
+  read(n) {
+    if (this[DESTROYED])
+      return null;
+    this[DISCARDED] = false;
+    if (this[BUFFERLENGTH] === 0 || n === 0 || n && n > this[BUFFERLENGTH]) {
+      this[MAYBE_EMIT_END]();
+      return null;
+    }
+    if (this[OBJECTMODE])
+      n = null;
+    if (this[BUFFER].length > 1 && !this[OBJECTMODE]) {
+      this[BUFFER] = [
+        this[ENCODING] ? this[BUFFER].join("") : Buffer.concat(this[BUFFER], this[BUFFERLENGTH])
+      ];
+    }
+    const ret = this[READ](n || null, this[BUFFER][0]);
+    this[MAYBE_EMIT_END]();
+    return ret;
+  }
+  [READ](n, chunk) {
+    if (this[OBJECTMODE])
+      this[BUFFERSHIFT]();
+    else {
+      const c = chunk;
+      if (n === c.length || n === null)
+        this[BUFFERSHIFT]();
+      else if (typeof c === "string") {
+        this[BUFFER][0] = c.slice(n);
+        chunk = c.slice(0, n);
+        this[BUFFERLENGTH] -= n;
+      } else {
+        this[BUFFER][0] = c.subarray(n);
+        chunk = c.subarray(0, n);
+        this[BUFFERLENGTH] -= n;
+      }
+    }
+    this.emit("data", chunk);
+    if (!this[BUFFER].length && !this[EOF])
+      this.emit("drain");
+    return chunk;
+  }
+  end(chunk, encoding, cb) {
+    if (typeof chunk === "function") {
+      cb = chunk;
+      chunk = void 0;
+    }
+    if (typeof encoding === "function") {
+      cb = encoding;
+      encoding = "utf8";
+    }
+    if (chunk !== void 0)
+      this.write(chunk, encoding);
+    if (cb)
+      this.once("end", cb);
+    this[EOF] = true;
+    this.writable = false;
+    if (this[FLOWING] || !this[PAUSED])
+      this[MAYBE_EMIT_END]();
+    return this;
+  }
+  // don't let the internal resume be overwritten
+  [RESUME]() {
+    if (this[DESTROYED])
+      return;
+    if (!this[DATALISTENERS] && !this[PIPES].length) {
+      this[DISCARDED] = true;
+    }
+    this[PAUSED] = false;
+    this[FLOWING] = true;
+    this.emit("resume");
+    if (this[BUFFER].length)
+      this[FLUSH]();
+    else if (this[EOF])
+      this[MAYBE_EMIT_END]();
+    else
+      this.emit("drain");
+  }
+  /**
+   * Resume the stream if it is currently in a paused state
+   *
+   * If called when there are no pipe destinations or `data` event listeners,
+   * this will place the stream in a "discarded" state, where all data will
+   * be thrown away. The discarded state is removed if a pipe destination or
+   * data handler is added, if pause() is called, or if any synchronous or
+   * asynchronous iteration is started.
+   */
+  resume() {
+    return this[RESUME]();
+  }
+  /**
+   * Pause the stream
+   */
+  pause() {
+    this[FLOWING] = false;
+    this[PAUSED] = true;
+    this[DISCARDED] = false;
+  }
+  /**
+   * true if the stream has been forcibly destroyed
+   */
+  get destroyed() {
+    return this[DESTROYED];
+  }
+  /**
+   * true if the stream is currently in a flowing state, meaning that
+   * any writes will be immediately emitted.
+   */
+  get flowing() {
+    return this[FLOWING];
+  }
+  /**
+   * true if the stream is currently in a paused state
+   */
+  get paused() {
+    return this[PAUSED];
+  }
+  [BUFFERPUSH](chunk) {
+    if (this[OBJECTMODE])
+      this[BUFFERLENGTH] += 1;
+    else
+      this[BUFFERLENGTH] += chunk.length;
+    this[BUFFER].push(chunk);
+  }
+  [BUFFERSHIFT]() {
+    if (this[OBJECTMODE])
+      this[BUFFERLENGTH] -= 1;
+    else
+      this[BUFFERLENGTH] -= this[BUFFER][0].length;
+    return this[BUFFER].shift();
+  }
+  [FLUSH](noDrain = false) {
+    do {
+    } while (this[FLUSHCHUNK](this[BUFFERSHIFT]()) && this[BUFFER].length);
+    if (!noDrain && !this[BUFFER].length && !this[EOF])
+      this.emit("drain");
+  }
+  [FLUSHCHUNK](chunk) {
+    this.emit("data", chunk);
+    return this[FLOWING];
+  }
+  /**
+   * Pipe all data emitted by this stream into the destination provided.
+   *
+   * Triggers the flow of data.
+   */
+  pipe(dest, opts) {
+    if (this[DESTROYED])
+      return dest;
+    this[DISCARDED] = false;
+    const ended = this[EMITTED_END];
+    opts = opts || {};
+    if (dest === proc.stdout || dest === proc.stderr)
+      opts.end = false;
+    else
+      opts.end = opts.end !== false;
+    opts.proxyErrors = !!opts.proxyErrors;
+    if (ended) {
+      if (opts.end)
+        dest.end();
+    } else {
+      this[PIPES].push(!opts.proxyErrors ? new Pipe(this, dest, opts) : new PipeProxyErrors(this, dest, opts));
+      if (this[ASYNC])
+        defer(() => this[RESUME]());
+      else
+        this[RESUME]();
+    }
+    return dest;
+  }
+  /**
+   * Fully unhook a piped destination stream.
+   *
+   * If the destination stream was the only consumer of this stream (ie,
+   * there are no other piped destinations or `'data'` event listeners)
+   * then the flow of data will stop until there is another consumer or
+   * {@link Minipass#resume} is explicitly called.
+   */
+  unpipe(dest) {
+    const p = this[PIPES].find((p2) => p2.dest === dest);
+    if (p) {
+      if (this[PIPES].length === 1) {
+        if (this[FLOWING] && this[DATALISTENERS] === 0) {
+          this[FLOWING] = false;
+        }
+        this[PIPES] = [];
+      } else
+        this[PIPES].splice(this[PIPES].indexOf(p), 1);
+      p.unpipe();
+    }
+  }
+  /**
+   * Alias for {@link Minipass#on}
+   */
+  addListener(ev, handler) {
+    return this.on(ev, handler);
+  }
+  /**
+   * Mostly identical to `EventEmitter.on`, with the following
+   * behavior differences to prevent data loss and unnecessary hangs:
+   *
+   * - Adding a 'data' event handler will trigger the flow of data
+   *
+   * - Adding a 'readable' event handler when there is data waiting to be read
+   *   will cause 'readable' to be emitted immediately.
+   *
+   * - Adding an 'endish' event handler ('end', 'finish', etc.) which has
+   *   already passed will cause the event to be emitted immediately and all
+   *   handlers removed.
+   *
+   * - Adding an 'error' event handler after an error has been emitted will
+   *   cause the event to be re-emitted immediately with the error previously
+   *   raised.
+   */
+  on(ev, handler) {
+    const ret = super.on(ev, handler);
+    if (ev === "data") {
+      this[DISCARDED] = false;
+      this[DATALISTENERS]++;
+      if (!this[PIPES].length && !this[FLOWING]) {
+        this[RESUME]();
+      }
+    } else if (ev === "readable" && this[BUFFERLENGTH] !== 0) {
+      super.emit("readable");
+    } else if (isEndish(ev) && this[EMITTED_END]) {
+      super.emit(ev);
+      this.removeAllListeners(ev);
+    } else if (ev === "error" && this[EMITTED_ERROR]) {
+      const h = handler;
+      if (this[ASYNC])
+        defer(() => h.call(this, this[EMITTED_ERROR]));
+      else
+        h.call(this, this[EMITTED_ERROR]);
+    }
+    return ret;
+  }
+  /**
+   * Alias for {@link Minipass#off}
+   */
+  removeListener(ev, handler) {
+    return this.off(ev, handler);
+  }
+  /**
+   * Mostly identical to `EventEmitter.off`
+   *
+   * If a 'data' event handler is removed, and it was the last consumer
+   * (ie, there are no pipe destinations or other 'data' event listeners),
+   * then the flow of data will stop until there is another consumer or
+   * {@link Minipass#resume} is explicitly called.
+   */
+  off(ev, handler) {
+    const ret = super.off(ev, handler);
+    if (ev === "data") {
+      this[DATALISTENERS] = this.listeners("data").length;
+      if (this[DATALISTENERS] === 0 && !this[DISCARDED] && !this[PIPES].length) {
+        this[FLOWING] = false;
+      }
+    }
+    return ret;
+  }
+  /**
+   * Mostly identical to `EventEmitter.removeAllListeners`
+   *
+   * If all 'data' event handlers are removed, and they were the last consumer
+   * (ie, there are no pipe destinations), then the flow of data will stop
+   * until there is another consumer or {@link Minipass#resume} is explicitly
+   * called.
+   */
+  removeAllListeners(ev) {
+    const ret = super.removeAllListeners(ev);
+    if (ev === "data" || ev === void 0) {
+      this[DATALISTENERS] = 0;
+      if (!this[DISCARDED] && !this[PIPES].length) {
+        this[FLOWING] = false;
+      }
+    }
+    return ret;
+  }
+  /**
+   * true if the 'end' event has been emitted
+   */
+  get emittedEnd() {
+    return this[EMITTED_END];
+  }
+  [MAYBE_EMIT_END]() {
+    if (!this[EMITTING_END] && !this[EMITTED_END] && !this[DESTROYED] && this[BUFFER].length === 0 && this[EOF]) {
+      this[EMITTING_END] = true;
+      this.emit("end");
+      this.emit("prefinish");
+      this.emit("finish");
+      if (this[CLOSED])
+        this.emit("close");
+      this[EMITTING_END] = false;
+    }
+  }
+  /**
+   * Mostly identical to `EventEmitter.emit`, with the following
+   * behavior differences to prevent data loss and unnecessary hangs:
+   *
+   * If the stream has been destroyed, and the event is something other
+   * than 'close' or 'error', then `false` is returned and no handlers
+   * are called.
+   *
+   * If the event is 'end', and has already been emitted, then the event
+   * is ignored. If the stream is in a paused or non-flowing state, then
+   * the event will be deferred until data flow resumes. If the stream is
+   * async, then handlers will be called on the next tick rather than
+   * immediately.
+   *
+   * If the event is 'close', and 'end' has not yet been emitted, then
+   * the event will be deferred until after 'end' is emitted.
+   *
+   * If the event is 'error', and an AbortSignal was provided for the stream,
+   * and there are no listeners, then the event is ignored, matching the
+   * behavior of node core streams in the presense of an AbortSignal.
+   *
+   * If the event is 'finish' or 'prefinish', then all listeners will be
+   * removed after emitting the event, to prevent double-firing.
+   */
+  emit(ev, ...args) {
+    const data = args[0];
+    if (ev !== "error" && ev !== "close" && ev !== DESTROYED && this[DESTROYED]) {
+      return false;
+    } else if (ev === "data") {
+      return !this[OBJECTMODE] && !data ? false : this[ASYNC] ? (defer(() => this[EMITDATA](data)), true) : this[EMITDATA](data);
+    } else if (ev === "end") {
+      return this[EMITEND]();
+    } else if (ev === "close") {
+      this[CLOSED] = true;
+      if (!this[EMITTED_END] && !this[DESTROYED])
+        return false;
+      const ret2 = super.emit("close");
+      this.removeAllListeners("close");
+      return ret2;
+    } else if (ev === "error") {
+      this[EMITTED_ERROR] = data;
+      super.emit(ERROR, data);
+      const ret2 = !this[SIGNAL] || this.listeners("error").length ? super.emit("error", data) : false;
+      this[MAYBE_EMIT_END]();
+      return ret2;
+    } else if (ev === "resume") {
+      const ret2 = super.emit("resume");
+      this[MAYBE_EMIT_END]();
+      return ret2;
+    } else if (ev === "finish" || ev === "prefinish") {
+      const ret2 = super.emit(ev);
+      this.removeAllListeners(ev);
+      return ret2;
+    }
+    const ret = super.emit(ev, ...args);
+    this[MAYBE_EMIT_END]();
+    return ret;
+  }
+  [EMITDATA](data) {
+    for (const p of this[PIPES]) {
+      if (p.dest.write(data) === false)
+        this.pause();
+    }
+    const ret = this[DISCARDED] ? false : super.emit("data", data);
+    this[MAYBE_EMIT_END]();
+    return ret;
+  }
+  [EMITEND]() {
+    if (this[EMITTED_END])
+      return false;
+    this[EMITTED_END] = true;
+    this.readable = false;
+    return this[ASYNC] ? (defer(() => this[EMITEND2]()), true) : this[EMITEND2]();
+  }
+  [EMITEND2]() {
+    if (this[DECODER]) {
+      const data = this[DECODER].end();
+      if (data) {
+        for (const p of this[PIPES]) {
+          p.dest.write(data);
+        }
+        if (!this[DISCARDED])
+          super.emit("data", data);
+      }
+    }
+    for (const p of this[PIPES]) {
+      p.end();
+    }
+    const ret = super.emit("end");
+    this.removeAllListeners("end");
+    return ret;
+  }
+  /**
+   * Return a Promise that resolves to an array of all emitted data once
+   * the stream ends.
+   */
+  async collect() {
+    const buf = Object.assign([], {
+      dataLength: 0
+    });
+    if (!this[OBJECTMODE])
+      buf.dataLength = 0;
+    const p = this.promise();
+    this.on("data", (c) => {
+      buf.push(c);
+      if (!this[OBJECTMODE])
+        buf.dataLength += c.length;
+    });
+    await p;
+    return buf;
+  }
+  /**
+   * Return a Promise that resolves to the concatenation of all emitted data
+   * once the stream ends.
+   *
+   * Not allowed on objectMode streams.
+   */
+  async concat() {
+    if (this[OBJECTMODE]) {
+      throw new Error("cannot concat in objectMode");
+    }
+    const buf = await this.collect();
+    return this[ENCODING] ? buf.join("") : Buffer.concat(buf, buf.dataLength);
+  }
+  /**
+   * Return a void Promise that resolves once the stream ends.
+   */
+  async promise() {
+    return new Promise((resolve3, reject) => {
+      this.on(DESTROYED, () => reject(new Error("stream destroyed")));
+      this.on("error", (er) => reject(er));
+      this.on("end", () => resolve3());
+    });
+  }
+  /**
+   * Asynchronous `for await of` iteration.
+   *
+   * This will continue emitting all chunks until the stream terminates.
+   */
+  [Symbol.asyncIterator]() {
+    this[DISCARDED] = false;
+    let stopped = false;
+    const stop = async () => {
+      this.pause();
+      stopped = true;
+      return { value: void 0, done: true };
+    };
+    const next = () => {
+      if (stopped)
+        return stop();
+      const res = this.read();
+      if (res !== null)
+        return Promise.resolve({ done: false, value: res });
+      if (this[EOF])
+        return stop();
+      let resolve3;
+      let reject;
+      const onerr = (er) => {
+        this.off("data", ondata);
+        this.off("end", onend);
+        this.off(DESTROYED, ondestroy);
+        stop();
+        reject(er);
+      };
+      const ondata = (value) => {
+        this.off("error", onerr);
+        this.off("end", onend);
+        this.off(DESTROYED, ondestroy);
+        this.pause();
+        resolve3({ value, done: !!this[EOF] });
+      };
+      const onend = () => {
+        this.off("error", onerr);
+        this.off("data", ondata);
+        this.off(DESTROYED, ondestroy);
+        stop();
+        resolve3({ done: true, value: void 0 });
+      };
+      const ondestroy = () => onerr(new Error("stream destroyed"));
+      return new Promise((res2, rej) => {
+        reject = rej;
+        resolve3 = res2;
+        this.once(DESTROYED, ondestroy);
+        this.once("error", onerr);
+        this.once("end", onend);
+        this.once("data", ondata);
+      });
+    };
+    return {
+      next,
+      throw: stop,
+      return: stop,
+      [Symbol.asyncIterator]() {
+        return this;
+      },
+      [Symbol.asyncDispose]: async () => {
+      }
+    };
+  }
+  /**
+   * Synchronous `for of` iteration.
+   *
+   * The iteration will terminate when the internal buffer runs out, even
+   * if the stream has not yet terminated.
+   */
+  [Symbol.iterator]() {
+    this[DISCARDED] = false;
+    let stopped = false;
+    const stop = () => {
+      this.pause();
+      this.off(ERROR, stop);
+      this.off(DESTROYED, stop);
+      this.off("end", stop);
+      stopped = true;
+      return { done: true, value: void 0 };
+    };
+    const next = () => {
+      if (stopped)
+        return stop();
+      const value = this.read();
+      return value === null ? stop() : { done: false, value };
+    };
+    this.once("end", stop);
+    this.once(ERROR, stop);
+    this.once(DESTROYED, stop);
+    return {
+      next,
+      throw: stop,
+      return: stop,
+      [Symbol.iterator]() {
+        return this;
+      },
+      [Symbol.dispose]: () => {
+      }
+    };
+  }
+  /**
+   * Destroy a stream, preventing it from being used for any further purpose.
+   *
+   * If the stream has a `close()` method, then it will be called on
+   * destruction.
+   *
+   * After destruction, any attempt to write data, read data, or emit most
+   * events will be ignored.
+   *
+   * If an error argument is provided, then it will be emitted in an
+   * 'error' event.
+   */
+  destroy(er) {
+    if (this[DESTROYED]) {
+      if (er)
+        this.emit("error", er);
+      else
+        this.emit(DESTROYED);
+      return this;
+    }
+    this[DESTROYED] = true;
+    this[DISCARDED] = true;
+    this[BUFFER].length = 0;
+    this[BUFFERLENGTH] = 0;
+    const wc = this;
+    if (typeof wc.close === "function" && !this[CLOSED])
+      wc.close();
+    if (er)
+      this.emit("error", er);
+    else
+      this.emit(DESTROYED);
+    return this;
+  }
+  /**
+   * Alias for {@link isStream}
+   *
+   * Former export location, maintained for backwards compatibility.
+   *
+   * @deprecated
+   */
+  static get isStream() {
+    return isStream;
+  }
+};
+
+// node_modules/path-scurry/dist/esm/index.js
+var realpathSync = rps.native;
+var defaultFS = {
+  lstatSync,
+  readdir: readdirCB,
+  readdirSync,
+  readlinkSync,
+  realpathSync,
+  promises: {
+    lstat,
+    readdir,
+    readlink,
+    realpath
+  }
+};
+var fsFromOption = (fsOption) => !fsOption || fsOption === defaultFS || fsOption === actualFS ? defaultFS : {
+  ...defaultFS,
+  ...fsOption,
+  promises: {
+    ...defaultFS.promises,
+    ...fsOption.promises || {}
+  }
+};
+var uncDriveRegexp = /^\\\\\?\\([a-z]:)\\?$/i;
+var uncToDrive = (rootPath) => rootPath.replace(/\//g, "\\").replace(uncDriveRegexp, "$1\\");
+var eitherSep = /[\\\/]/;
+var UNKNOWN = 0;
+var IFIFO = 1;
+var IFCHR = 2;
+var IFDIR = 4;
+var IFBLK = 6;
+var IFREG = 8;
+var IFLNK = 10;
+var IFSOCK = 12;
+var IFMT = 15;
+var IFMT_UNKNOWN = ~IFMT;
+var READDIR_CALLED = 16;
+var LSTAT_CALLED = 32;
+var ENOTDIR = 64;
+var ENOENT = 128;
+var ENOREADLINK = 256;
+var ENOREALPATH = 512;
+var ENOCHILD = ENOTDIR | ENOENT | ENOREALPATH;
+var TYPEMASK = 1023;
+var entToType = (s) => s.isFile() ? IFREG : s.isDirectory() ? IFDIR : s.isSymbolicLink() ? IFLNK : s.isCharacterDevice() ? IFCHR : s.isBlockDevice() ? IFBLK : s.isSocket() ? IFSOCK : s.isFIFO() ? IFIFO : UNKNOWN;
+var normalizeCache = new L({ max: 2 ** 12 });
+var normalize = (s) => {
+  const c = normalizeCache.get(s);
+  if (c)
+    return c;
+  const n = s.normalize("NFKD");
+  normalizeCache.set(s, n);
+  return n;
+};
+var normalizeNocaseCache = new L({ max: 2 ** 12 });
+var normalizeNocase = (s) => {
+  const c = normalizeNocaseCache.get(s);
+  if (c)
+    return c;
+  const n = normalize(s.toLowerCase());
+  normalizeNocaseCache.set(s, n);
+  return n;
+};
+var ResolveCache = class extends L {
+  constructor() {
+    super({ max: 256 });
+  }
+};
+var ChildrenCache = class extends L {
+  constructor(maxSize = 16 * 1024) {
+    super({
+      maxSize,
+      // parent + children
+      sizeCalculation: (a) => a.length + 1
+    });
+  }
+};
+var setAsCwd = /* @__PURE__ */ Symbol("PathScurry setAsCwd");
+var PathBase = class {
+  /**
+   * the basename of this path
+   *
+   * **Important**: *always* test the path name against any test string
+   * usingthe {@link isNamed} method, and not by directly comparing this
+   * string. Otherwise, unicode path strings that the system sees as identical
+   * will not be properly treated as the same path, leading to incorrect
+   * behavior and possible security issues.
+   */
+  name;
+  /**
+   * the Path entry corresponding to the path root.
+   *
+   * @internal
+   */
+  root;
+  /**
+   * All roots found within the current PathScurry family
+   *
+   * @internal
+   */
+  roots;
+  /**
+   * a reference to the parent path, or undefined in the case of root entries
+   *
+   * @internal
+   */
+  parent;
+  /**
+   * boolean indicating whether paths are compared case-insensitively
+   * @internal
+   */
+  nocase;
+  /**
+   * boolean indicating that this path is the current working directory
+   * of the PathScurry collection that contains it.
+   */
+  isCWD = false;
+  // potential default fs override
+  #fs;
+  // Stats fields
+  #dev;
+  get dev() {
+    return this.#dev;
+  }
+  #mode;
+  get mode() {
+    return this.#mode;
+  }
+  #nlink;
+  get nlink() {
+    return this.#nlink;
+  }
+  #uid;
+  get uid() {
+    return this.#uid;
+  }
+  #gid;
+  get gid() {
+    return this.#gid;
+  }
+  #rdev;
+  get rdev() {
+    return this.#rdev;
+  }
+  #blksize;
+  get blksize() {
+    return this.#blksize;
+  }
+  #ino;
+  get ino() {
+    return this.#ino;
+  }
+  #size;
+  get size() {
+    return this.#size;
+  }
+  #blocks;
+  get blocks() {
+    return this.#blocks;
+  }
+  #atimeMs;
+  get atimeMs() {
+    return this.#atimeMs;
+  }
+  #mtimeMs;
+  get mtimeMs() {
+    return this.#mtimeMs;
+  }
+  #ctimeMs;
+  get ctimeMs() {
+    return this.#ctimeMs;
+  }
+  #birthtimeMs;
+  get birthtimeMs() {
+    return this.#birthtimeMs;
+  }
+  #atime;
+  get atime() {
+    return this.#atime;
+  }
+  #mtime;
+  get mtime() {
+    return this.#mtime;
+  }
+  #ctime;
+  get ctime() {
+    return this.#ctime;
+  }
+  #birthtime;
+  get birthtime() {
+    return this.#birthtime;
+  }
+  #matchName;
+  #depth;
+  #fullpath;
+  #fullpathPosix;
+  #relative;
+  #relativePosix;
+  #type;
+  #children;
+  #linkTarget;
+  #realpath;
+  /**
+   * This property is for compatibility with the Dirent class as of
+   * Node v20, where Dirent['parentPath'] refers to the path of the
+   * directory that was passed to readdir. For root entries, it's the path
+   * to the entry itself.
+   */
+  get parentPath() {
+    return (this.parent || this).fullpath();
+  }
+  /* c8 ignore start */
+  /**
+   * Deprecated alias for Dirent['parentPath'] Somewhat counterintuitively,
+   * this property refers to the *parent* path, not the path object itself.
+   *
+   * @deprecated
+   */
+  get path() {
+    return this.parentPath;
+  }
+  /* c8 ignore stop */
+  /**
+   * Do not create new Path objects directly.  They should always be accessed
+   * via the PathScurry class or other methods on the Path class.
+   *
+   * @internal
+   */
+  constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
+    this.name = name;
+    this.#matchName = nocase ? normalizeNocase(name) : normalize(name);
+    this.#type = type & TYPEMASK;
+    this.nocase = nocase;
+    this.roots = roots;
+    this.root = root || this;
+    this.#children = children;
+    this.#fullpath = opts.fullpath;
+    this.#relative = opts.relative;
+    this.#relativePosix = opts.relativePosix;
+    this.parent = opts.parent;
+    if (this.parent) {
+      this.#fs = this.parent.#fs;
+    } else {
+      this.#fs = fsFromOption(opts.fs);
+    }
+  }
+  /**
+   * Returns the depth of the Path object from its root.
+   *
+   * For example, a path at `/foo/bar` would have a depth of 2.
+   */
+  depth() {
+    if (this.#depth !== void 0)
+      return this.#depth;
+    if (!this.parent)
+      return this.#depth = 0;
+    return this.#depth = this.parent.depth() + 1;
+  }
+  /**
+   * @internal
+   */
+  childrenCache() {
+    return this.#children;
+  }
+  /**
+   * Get the Path object referenced by the string path, resolved from this Path
+   */
+  resolve(path2) {
+    if (!path2) {
+      return this;
+    }
+    const rootPath = this.getRootString(path2);
+    const dir = path2.substring(rootPath.length);
+    const dirParts = dir.split(this.splitSep);
+    const result = rootPath ? this.getRoot(rootPath).#resolveParts(dirParts) : this.#resolveParts(dirParts);
+    return result;
+  }
+  #resolveParts(dirParts) {
+    let p = this;
+    for (const part of dirParts) {
+      p = p.child(part);
+    }
+    return p;
+  }
+  /**
+   * Returns the cached children Path objects, if still available.  If they
+   * have fallen out of the cache, then returns an empty array, and resets the
+   * READDIR_CALLED bit, so that future calls to readdir() will require an fs
+   * lookup.
+   *
+   * @internal
+   */
+  children() {
+    const cached = this.#children.get(this);
+    if (cached) {
+      return cached;
+    }
+    const children = Object.assign([], { provisional: 0 });
+    this.#children.set(this, children);
+    this.#type &= ~READDIR_CALLED;
+    return children;
+  }
+  /**
+   * Resolves a path portion and returns or creates the child Path.
+   *
+   * Returns `this` if pathPart is `''` or `'.'`, or `parent` if pathPart is
+   * `'..'`.
+   *
+   * This should not be called directly.  If `pathPart` contains any path
+   * separators, it will lead to unsafe undefined behavior.
+   *
+   * Use `Path.resolve()` instead.
+   *
+   * @internal
+   */
+  child(pathPart, opts) {
+    if (pathPart === "" || pathPart === ".") {
+      return this;
+    }
+    if (pathPart === "..") {
+      return this.parent || this;
+    }
+    const children = this.children();
+    const name = this.nocase ? normalizeNocase(pathPart) : normalize(pathPart);
+    for (const p of children) {
+      if (p.#matchName === name) {
+        return p;
+      }
+    }
+    const s = this.parent ? this.sep : "";
+    const fullpath = this.#fullpath ? this.#fullpath + s + pathPart : void 0;
+    const pchild = this.newChild(pathPart, UNKNOWN, {
+      ...opts,
+      parent: this,
+      fullpath
+    });
+    if (!this.canReaddir()) {
+      pchild.#type |= ENOENT;
+    }
+    children.push(pchild);
+    return pchild;
+  }
+  /**
+   * The relative path from the cwd. If it does not share an ancestor with
+   * the cwd, then this ends up being equivalent to the fullpath()
+   */
+  relative() {
+    if (this.isCWD)
+      return "";
+    if (this.#relative !== void 0) {
+      return this.#relative;
+    }
+    const name = this.name;
+    const p = this.parent;
+    if (!p) {
+      return this.#relative = this.name;
+    }
+    const pv = p.relative();
+    return pv + (!pv || !p.parent ? "" : this.sep) + name;
+  }
+  /**
+   * The relative path from the cwd, using / as the path separator.
+   * If it does not share an ancestor with
+   * the cwd, then this ends up being equivalent to the fullpathPosix()
+   * On posix systems, this is identical to relative().
+   */
+  relativePosix() {
+    if (this.sep === "/")
+      return this.relative();
+    if (this.isCWD)
+      return "";
+    if (this.#relativePosix !== void 0)
+      return this.#relativePosix;
+    const name = this.name;
+    const p = this.parent;
+    if (!p) {
+      return this.#relativePosix = this.fullpathPosix();
+    }
+    const pv = p.relativePosix();
+    return pv + (!pv || !p.parent ? "" : "/") + name;
+  }
+  /**
+   * The fully resolved path string for this Path entry
+   */
+  fullpath() {
+    if (this.#fullpath !== void 0) {
+      return this.#fullpath;
+    }
+    const name = this.name;
+    const p = this.parent;
+    if (!p) {
+      return this.#fullpath = this.name;
+    }
+    const pv = p.fullpath();
+    const fp = pv + (!p.parent ? "" : this.sep) + name;
+    return this.#fullpath = fp;
+  }
+  /**
+   * On platforms other than windows, this is identical to fullpath.
+   *
+   * On windows, this is overridden to return the forward-slash form of the
+   * full UNC path.
+   */
+  fullpathPosix() {
+    if (this.#fullpathPosix !== void 0)
+      return this.#fullpathPosix;
+    if (this.sep === "/")
+      return this.#fullpathPosix = this.fullpath();
+    if (!this.parent) {
+      const p2 = this.fullpath().replace(/\\/g, "/");
+      if (/^[a-z]:\//i.test(p2)) {
+        return this.#fullpathPosix = `//?/${p2}`;
+      } else {
+        return this.#fullpathPosix = p2;
+      }
+    }
+    const p = this.parent;
+    const pfpp = p.fullpathPosix();
+    const fpp = pfpp + (!pfpp || !p.parent ? "" : "/") + this.name;
+    return this.#fullpathPosix = fpp;
+  }
+  /**
+   * Is the Path of an unknown type?
+   *
+   * Note that we might know *something* about it if there has been a previous
+   * filesystem operation, for example that it does not exist, or is not a
+   * link, or whether it has child entries.
+   */
+  isUnknown() {
+    return (this.#type & IFMT) === UNKNOWN;
+  }
+  isType(type) {
+    return this[`is${type}`]();
+  }
+  getType() {
+    return this.isUnknown() ? "Unknown" : this.isDirectory() ? "Directory" : this.isFile() ? "File" : this.isSymbolicLink() ? "SymbolicLink" : this.isFIFO() ? "FIFO" : this.isCharacterDevice() ? "CharacterDevice" : this.isBlockDevice() ? "BlockDevice" : (
+      /* c8 ignore start */
+      this.isSocket() ? "Socket" : "Unknown"
+    );
+  }
+  /**
+   * Is the Path a regular file?
+   */
+  isFile() {
+    return (this.#type & IFMT) === IFREG;
+  }
+  /**
+   * Is the Path a directory?
+   */
+  isDirectory() {
+    return (this.#type & IFMT) === IFDIR;
+  }
+  /**
+   * Is the path a character device?
+   */
+  isCharacterDevice() {
+    return (this.#type & IFMT) === IFCHR;
+  }
+  /**
+   * Is the path a block device?
+   */
+  isBlockDevice() {
+    return (this.#type & IFMT) === IFBLK;
+  }
+  /**
+   * Is the path a FIFO pipe?
+   */
+  isFIFO() {
+    return (this.#type & IFMT) === IFIFO;
+  }
+  /**
+   * Is the path a socket?
+   */
+  isSocket() {
+    return (this.#type & IFMT) === IFSOCK;
+  }
+  /**
+   * Is the path a symbolic link?
+   */
+  isSymbolicLink() {
+    return (this.#type & IFLNK) === IFLNK;
+  }
+  /**
+   * Return the entry if it has been subject of a successful lstat, or
+   * undefined otherwise.
+   *
+   * Does not read the filesystem, so an undefined result *could* simply
+   * mean that we haven't called lstat on it.
+   */
+  lstatCached() {
+    return this.#type & LSTAT_CALLED ? this : void 0;
+  }
+  /**
+   * Return the cached link target if the entry has been the subject of a
+   * successful readlink, or undefined otherwise.
+   *
+   * Does not read the filesystem, so an undefined result *could* just mean we
+   * don't have any cached data. Only use it if you are very sure that a
+   * readlink() has been called at some point.
+   */
+  readlinkCached() {
+    return this.#linkTarget;
+  }
+  /**
+   * Returns the cached realpath target if the entry has been the subject
+   * of a successful realpath, or undefined otherwise.
+   *
+   * Does not read the filesystem, so an undefined result *could* just mean we
+   * don't have any cached data. Only use it if you are very sure that a
+   * realpath() has been called at some point.
+   */
+  realpathCached() {
+    return this.#realpath;
+  }
+  /**
+   * Returns the cached child Path entries array if the entry has been the
+   * subject of a successful readdir(), or [] otherwise.
+   *
+   * Does not read the filesystem, so an empty array *could* just mean we
+   * don't have any cached data. Only use it if you are very sure that a
+   * readdir() has been called recently enough to still be valid.
+   */
+  readdirCached() {
+    const children = this.children();
+    return children.slice(0, children.provisional);
+  }
+  /**
+   * Return true if it's worth trying to readlink.  Ie, we don't (yet) have
+   * any indication that readlink will definitely fail.
+   *
+   * Returns false if the path is known to not be a symlink, if a previous
+   * readlink failed, or if the entry does not exist.
+   */
+  canReadlink() {
+    if (this.#linkTarget)
+      return true;
+    if (!this.parent)
+      return false;
+    const ifmt = this.#type & IFMT;
+    return !(ifmt !== UNKNOWN && ifmt !== IFLNK || this.#type & ENOREADLINK || this.#type & ENOENT);
+  }
+  /**
+   * Return true if readdir has previously been successfully called on this
+   * path, indicating that cachedReaddir() is likely valid.
+   */
+  calledReaddir() {
+    return !!(this.#type & READDIR_CALLED);
+  }
+  /**
+   * Returns true if the path is known to not exist. That is, a previous lstat
+   * or readdir failed to verify its existence when that would have been
+   * expected, or a parent entry was marked either enoent or enotdir.
+   */
+  isENOENT() {
+    return !!(this.#type & ENOENT);
+  }
+  /**
+   * Return true if the path is a match for the given path name.  This handles
+   * case sensitivity and unicode normalization.
+   *
+   * Note: even on case-sensitive systems, it is **not** safe to test the
+   * equality of the `.name` property to determine whether a given pathname
+   * matches, due to unicode normalization mismatches.
+   *
+   * Always use this method instead of testing the `path.name` property
+   * directly.
+   */
+  isNamed(n) {
+    return !this.nocase ? this.#matchName === normalize(n) : this.#matchName === normalizeNocase(n);
+  }
+  /**
+   * Return the Path object corresponding to the target of a symbolic link.
+   *
+   * If the Path is not a symbolic link, or if the readlink call fails for any
+   * reason, `undefined` is returned.
+   *
+   * Result is cached, and thus may be outdated if the filesystem is mutated.
+   */
+  async readlink() {
+    const target = this.#linkTarget;
+    if (target) {
+      return target;
+    }
+    if (!this.canReadlink()) {
+      return void 0;
+    }
+    if (!this.parent) {
+      return void 0;
+    }
+    try {
+      const read = await this.#fs.promises.readlink(this.fullpath());
+      const linkTarget = (await this.parent.realpath())?.resolve(read);
+      if (linkTarget) {
+        return this.#linkTarget = linkTarget;
+      }
+    } catch (er) {
+      this.#readlinkFail(er.code);
+      return void 0;
+    }
+  }
+  /**
+   * Synchronous {@link PathBase.readlink}
+   */
+  readlinkSync() {
+    const target = this.#linkTarget;
+    if (target) {
+      return target;
+    }
+    if (!this.canReadlink()) {
+      return void 0;
+    }
+    if (!this.parent) {
+      return void 0;
+    }
+    try {
+      const read = this.#fs.readlinkSync(this.fullpath());
+      const linkTarget = this.parent.realpathSync()?.resolve(read);
+      if (linkTarget) {
+        return this.#linkTarget = linkTarget;
+      }
+    } catch (er) {
+      this.#readlinkFail(er.code);
+      return void 0;
+    }
+  }
+  #readdirSuccess(children) {
+    this.#type |= READDIR_CALLED;
+    for (let p = children.provisional; p < children.length; p++) {
+      const c = children[p];
+      if (c)
+        c.#markENOENT();
+    }
+  }
+  #markENOENT() {
+    if (this.#type & ENOENT)
+      return;
+    this.#type = (this.#type | ENOENT) & IFMT_UNKNOWN;
+    this.#markChildrenENOENT();
+  }
+  #markChildrenENOENT() {
+    const children = this.children();
+    children.provisional = 0;
+    for (const p of children) {
+      p.#markENOENT();
+    }
+  }
+  #markENOREALPATH() {
+    this.#type |= ENOREALPATH;
+    this.#markENOTDIR();
+  }
+  // save the information when we know the entry is not a dir
+  #markENOTDIR() {
+    if (this.#type & ENOTDIR)
+      return;
+    let t = this.#type;
+    if ((t & IFMT) === IFDIR)
+      t &= IFMT_UNKNOWN;
+    this.#type = t | ENOTDIR;
+    this.#markChildrenENOENT();
+  }
+  #readdirFail(code = "") {
+    if (code === "ENOTDIR" || code === "EPERM") {
+      this.#markENOTDIR();
+    } else if (code === "ENOENT") {
+      this.#markENOENT();
+    } else {
+      this.children().provisional = 0;
+    }
+  }
+  #lstatFail(code = "") {
+    if (code === "ENOTDIR") {
+      const p = this.parent;
+      p.#markENOTDIR();
+    } else if (code === "ENOENT") {
+      this.#markENOENT();
+    }
+  }
+  #readlinkFail(code = "") {
+    let ter = this.#type;
+    ter |= ENOREADLINK;
+    if (code === "ENOENT")
+      ter |= ENOENT;
+    if (code === "EINVAL" || code === "UNKNOWN") {
+      ter &= IFMT_UNKNOWN;
+    }
+    this.#type = ter;
+    if (code === "ENOTDIR" && this.parent) {
+      this.parent.#markENOTDIR();
+    }
+  }
+  #readdirAddChild(e, c) {
+    return this.#readdirMaybePromoteChild(e, c) || this.#readdirAddNewChild(e, c);
+  }
+  #readdirAddNewChild(e, c) {
+    const type = entToType(e);
+    const child = this.newChild(e.name, type, { parent: this });
+    const ifmt = child.#type & IFMT;
+    if (ifmt !== IFDIR && ifmt !== IFLNK && ifmt !== UNKNOWN) {
+      child.#type |= ENOTDIR;
+    }
+    c.unshift(child);
+    c.provisional++;
+    return child;
+  }
+  #readdirMaybePromoteChild(e, c) {
+    for (let p = c.provisional; p < c.length; p++) {
+      const pchild = c[p];
+      const name = this.nocase ? normalizeNocase(e.name) : normalize(e.name);
+      if (name !== pchild.#matchName) {
+        continue;
+      }
+      return this.#readdirPromoteChild(e, pchild, p, c);
+    }
+  }
+  #readdirPromoteChild(e, p, index, c) {
+    const v = p.name;
+    p.#type = p.#type & IFMT_UNKNOWN | entToType(e);
+    if (v !== e.name)
+      p.name = e.name;
+    if (index !== c.provisional) {
+      if (index === c.length - 1)
+        c.pop();
+      else
+        c.splice(index, 1);
+      c.unshift(p);
+    }
+    c.provisional++;
+    return p;
+  }
+  /**
+   * Call lstat() on this Path, and update all known information that can be
+   * determined.
+   *
+   * Note that unlike `fs.lstat()`, the returned value does not contain some
+   * information, such as `mode`, `dev`, `nlink`, and `ino`.  If that
+   * information is required, you will need to call `fs.lstat` yourself.
+   *
+   * If the Path refers to a nonexistent file, or if the lstat call fails for
+   * any reason, `undefined` is returned.  Otherwise the updated Path object is
+   * returned.
+   *
+   * Results are cached, and thus may be out of date if the filesystem is
+   * mutated.
+   */
+  async lstat() {
+    if ((this.#type & ENOENT) === 0) {
+      try {
+        this.#applyStat(await this.#fs.promises.lstat(this.fullpath()));
+        return this;
+      } catch (er) {
+        this.#lstatFail(er.code);
+      }
+    }
+  }
+  /**
+   * synchronous {@link PathBase.lstat}
+   */
+  lstatSync() {
+    if ((this.#type & ENOENT) === 0) {
+      try {
+        this.#applyStat(this.#fs.lstatSync(this.fullpath()));
+        return this;
+      } catch (er) {
+        this.#lstatFail(er.code);
+      }
+    }
+  }
+  #applyStat(st) {
+    const { atime, atimeMs, birthtime, birthtimeMs, blksize, blocks, ctime, ctimeMs, dev, gid, ino, mode, mtime, mtimeMs, nlink, rdev, size, uid } = st;
+    this.#atime = atime;
+    this.#atimeMs = atimeMs;
+    this.#birthtime = birthtime;
+    this.#birthtimeMs = birthtimeMs;
+    this.#blksize = blksize;
+    this.#blocks = blocks;
+    this.#ctime = ctime;
+    this.#ctimeMs = ctimeMs;
+    this.#dev = dev;
+    this.#gid = gid;
+    this.#ino = ino;
+    this.#mode = mode;
+    this.#mtime = mtime;
+    this.#mtimeMs = mtimeMs;
+    this.#nlink = nlink;
+    this.#rdev = rdev;
+    this.#size = size;
+    this.#uid = uid;
+    const ifmt = entToType(st);
+    this.#type = this.#type & IFMT_UNKNOWN | ifmt | LSTAT_CALLED;
+    if (ifmt !== UNKNOWN && ifmt !== IFDIR && ifmt !== IFLNK) {
+      this.#type |= ENOTDIR;
+    }
+  }
+  #onReaddirCB = [];
+  #readdirCBInFlight = false;
+  #callOnReaddirCB(children) {
+    this.#readdirCBInFlight = false;
+    const cbs = this.#onReaddirCB.slice();
+    this.#onReaddirCB.length = 0;
+    cbs.forEach((cb) => cb(null, children));
+  }
+  /**
+   * Standard node-style callback interface to get list of directory entries.
+   *
+   * If the Path cannot or does not contain any children, then an empty array
+   * is returned.
+   *
+   * Results are cached, and thus may be out of date if the filesystem is
+   * mutated.
+   *
+   * @param cb The callback called with (er, entries).  Note that the `er`
+   * param is somewhat extraneous, as all readdir() errors are handled and
+   * simply result in an empty set of entries being returned.
+   * @param allowZalgo Boolean indicating that immediately known results should
+   * *not* be deferred with `queueMicrotask`. Defaults to `false`. Release
+   * zalgo at your peril, the dark pony lord is devious and unforgiving.
+   */
+  readdirCB(cb, allowZalgo = false) {
+    if (!this.canReaddir()) {
+      if (allowZalgo)
+        cb(null, []);
+      else
+        queueMicrotask(() => cb(null, []));
+      return;
+    }
+    const children = this.children();
+    if (this.calledReaddir()) {
+      const c = children.slice(0, children.provisional);
+      if (allowZalgo)
+        cb(null, c);
+      else
+        queueMicrotask(() => cb(null, c));
+      return;
+    }
+    this.#onReaddirCB.push(cb);
+    if (this.#readdirCBInFlight) {
+      return;
+    }
+    this.#readdirCBInFlight = true;
+    const fullpath = this.fullpath();
+    this.#fs.readdir(fullpath, { withFileTypes: true }, (er, entries) => {
+      if (er) {
+        this.#readdirFail(er.code);
+        children.provisional = 0;
+      } else {
+        for (const e of entries) {
+          this.#readdirAddChild(e, children);
+        }
+        this.#readdirSuccess(children);
+      }
+      this.#callOnReaddirCB(children.slice(0, children.provisional));
+      return;
+    });
+  }
+  #asyncReaddirInFlight;
+  /**
+   * Return an array of known child entries.
+   *
+   * If the Path cannot or does not contain any children, then an empty array
+   * is returned.
+   *
+   * Results are cached, and thus may be out of date if the filesystem is
+   * mutated.
+   */
+  async readdir() {
+    if (!this.canReaddir()) {
+      return [];
+    }
+    const children = this.children();
+    if (this.calledReaddir()) {
+      return children.slice(0, children.provisional);
+    }
+    const fullpath = this.fullpath();
+    if (this.#asyncReaddirInFlight) {
+      await this.#asyncReaddirInFlight;
+    } else {
+      let resolve3 = () => {
+      };
+      this.#asyncReaddirInFlight = new Promise((res) => resolve3 = res);
+      try {
+        for (const e of await this.#fs.promises.readdir(fullpath, {
+          withFileTypes: true
+        })) {
+          this.#readdirAddChild(e, children);
+        }
+        this.#readdirSuccess(children);
+      } catch (er) {
+        this.#readdirFail(er.code);
+        children.provisional = 0;
+      }
+      this.#asyncReaddirInFlight = void 0;
+      resolve3();
+    }
+    return children.slice(0, children.provisional);
+  }
+  /**
+   * synchronous {@link PathBase.readdir}
+   */
+  readdirSync() {
+    if (!this.canReaddir()) {
+      return [];
+    }
+    const children = this.children();
+    if (this.calledReaddir()) {
+      return children.slice(0, children.provisional);
+    }
+    const fullpath = this.fullpath();
+    try {
+      for (const e of this.#fs.readdirSync(fullpath, {
+        withFileTypes: true
+      })) {
+        this.#readdirAddChild(e, children);
+      }
+      this.#readdirSuccess(children);
+    } catch (er) {
+      this.#readdirFail(er.code);
+      children.provisional = 0;
+    }
+    return children.slice(0, children.provisional);
+  }
+  canReaddir() {
+    if (this.#type & ENOCHILD)
+      return false;
+    const ifmt = IFMT & this.#type;
+    if (!(ifmt === UNKNOWN || ifmt === IFDIR || ifmt === IFLNK)) {
+      return false;
+    }
+    return true;
+  }
+  shouldWalk(dirs, walkFilter) {
+    return (this.#type & IFDIR) === IFDIR && !(this.#type & ENOCHILD) && !dirs.has(this) && (!walkFilter || walkFilter(this));
+  }
+  /**
+   * Return the Path object corresponding to path as resolved
+   * by realpath(3).
+   *
+   * If the realpath call fails for any reason, `undefined` is returned.
+   *
+   * Result is cached, and thus may be outdated if the filesystem is mutated.
+   * On success, returns a Path object.
+   */
+  async realpath() {
+    if (this.#realpath)
+      return this.#realpath;
+    if ((ENOREALPATH | ENOREADLINK | ENOENT) & this.#type)
+      return void 0;
+    try {
+      const rp = await this.#fs.promises.realpath(this.fullpath());
+      return this.#realpath = this.resolve(rp);
+    } catch (_) {
+      this.#markENOREALPATH();
+    }
+  }
+  /**
+   * Synchronous {@link realpath}
+   */
+  realpathSync() {
+    if (this.#realpath)
+      return this.#realpath;
+    if ((ENOREALPATH | ENOREADLINK | ENOENT) & this.#type)
+      return void 0;
+    try {
+      const rp = this.#fs.realpathSync(this.fullpath());
+      return this.#realpath = this.resolve(rp);
+    } catch (_) {
+      this.#markENOREALPATH();
+    }
+  }
+  /**
+   * Internal method to mark this Path object as the scurry cwd,
+   * called by {@link PathScurry#chdir}
+   *
+   * @internal
+   */
+  [setAsCwd](oldCwd) {
+    if (oldCwd === this)
+      return;
+    oldCwd.isCWD = false;
+    this.isCWD = true;
+    const changed = /* @__PURE__ */ new Set([]);
+    let rp = [];
+    let p = this;
+    while (p && p.parent) {
+      changed.add(p);
+      p.#relative = rp.join(this.sep);
+      p.#relativePosix = rp.join("/");
+      p = p.parent;
+      rp.push("..");
+    }
+    p = oldCwd;
+    while (p && p.parent && !changed.has(p)) {
+      p.#relative = void 0;
+      p.#relativePosix = void 0;
+      p = p.parent;
+    }
+  }
+};
+var PathWin32 = class _PathWin32 extends PathBase {
+  /**
+   * Separator for generating path strings.
+   */
+  sep = "\\";
+  /**
+   * Separator for parsing path strings.
+   */
+  splitSep = eitherSep;
+  /**
+   * Do not create new Path objects directly.  They should always be accessed
+   * via the PathScurry class or other methods on the Path class.
+   *
+   * @internal
+   */
+  constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
+    super(name, type, root, roots, nocase, children, opts);
+  }
+  /**
+   * @internal
+   */
+  newChild(name, type = UNKNOWN, opts = {}) {
+    return new _PathWin32(name, type, this.root, this.roots, this.nocase, this.childrenCache(), opts);
+  }
+  /**
+   * @internal
+   */
+  getRootString(path2) {
+    return win32.parse(path2).root;
+  }
+  /**
+   * @internal
+   */
+  getRoot(rootPath) {
+    rootPath = uncToDrive(rootPath.toUpperCase());
+    if (rootPath === this.root.name) {
+      return this.root;
+    }
+    for (const [compare, root] of Object.entries(this.roots)) {
+      if (this.sameRoot(rootPath, compare)) {
+        return this.roots[rootPath] = root;
+      }
+    }
+    return this.roots[rootPath] = new PathScurryWin32(rootPath, this).root;
+  }
+  /**
+   * @internal
+   */
+  sameRoot(rootPath, compare = this.root.name) {
+    rootPath = rootPath.toUpperCase().replace(/\//g, "\\").replace(uncDriveRegexp, "$1\\");
+    return rootPath === compare;
+  }
+};
+var PathPosix = class _PathPosix extends PathBase {
+  /**
+   * separator for parsing path strings
+   */
+  splitSep = "/";
+  /**
+   * separator for generating path strings
+   */
+  sep = "/";
+  /**
+   * Do not create new Path objects directly.  They should always be accessed
+   * via the PathScurry class or other methods on the Path class.
+   *
+   * @internal
+   */
+  constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
+    super(name, type, root, roots, nocase, children, opts);
+  }
+  /**
+   * @internal
+   */
+  getRootString(path2) {
+    return path2.startsWith("/") ? "/" : "";
+  }
+  /**
+   * @internal
+   */
+  getRoot(_rootPath) {
+    return this.root;
+  }
+  /**
+   * @internal
+   */
+  newChild(name, type = UNKNOWN, opts = {}) {
+    return new _PathPosix(name, type, this.root, this.roots, this.nocase, this.childrenCache(), opts);
+  }
+};
+var PathScurryBase = class {
+  /**
+   * The root Path entry for the current working directory of this Scurry
+   */
+  root;
+  /**
+   * The string path for the root of this Scurry's current working directory
+   */
+  rootPath;
+  /**
+   * A collection of all roots encountered, referenced by rootPath
+   */
+  roots;
+  /**
+   * The Path entry corresponding to this PathScurry's current working directory.
+   */
+  cwd;
+  #resolveCache;
+  #resolvePosixCache;
+  #children;
+  /**
+   * Perform path comparisons case-insensitively.
+   *
+   * Defaults true on Darwin and Windows systems, false elsewhere.
+   */
+  nocase;
+  #fs;
+  /**
+   * This class should not be instantiated directly.
+   *
+   * Use PathScurryWin32, PathScurryDarwin, PathScurryPosix, or PathScurry
+   *
+   * @internal
+   */
+  constructor(cwd = process.cwd(), pathImpl, sep4, { nocase, childrenCacheSize = 16 * 1024, fs: fs2 = defaultFS } = {}) {
+    this.#fs = fsFromOption(fs2);
+    if (cwd instanceof URL || cwd.startsWith("file://")) {
+      cwd = fileURLToPath(cwd);
+    }
+    const cwdPath = pathImpl.resolve(cwd);
+    this.roots = /* @__PURE__ */ Object.create(null);
+    this.rootPath = this.parseRootPath(cwdPath);
+    this.#resolveCache = new ResolveCache();
+    this.#resolvePosixCache = new ResolveCache();
+    this.#children = new ChildrenCache(childrenCacheSize);
+    const split = cwdPath.substring(this.rootPath.length).split(sep4);
+    if (split.length === 1 && !split[0]) {
+      split.pop();
+    }
+    if (nocase === void 0) {
+      throw new TypeError("must provide nocase setting to PathScurryBase ctor");
+    }
+    this.nocase = nocase;
+    this.root = this.newRoot(this.#fs);
+    this.roots[this.rootPath] = this.root;
+    let prev = this.root;
+    let len = split.length - 1;
+    const joinSep = pathImpl.sep;
+    let abs = this.rootPath;
+    let sawFirst = false;
+    for (const part of split) {
+      const l = len--;
+      prev = prev.child(part, {
+        relative: new Array(l).fill("..").join(joinSep),
+        relativePosix: new Array(l).fill("..").join("/"),
+        fullpath: abs += (sawFirst ? "" : joinSep) + part
+      });
+      sawFirst = true;
+    }
+    this.cwd = prev;
+  }
+  /**
+   * Get the depth of a provided path, string, or the cwd
+   */
+  depth(path2 = this.cwd) {
+    if (typeof path2 === "string") {
+      path2 = this.cwd.resolve(path2);
+    }
+    return path2.depth();
+  }
+  /**
+   * Return the cache of child entries.  Exposed so subclasses can create
+   * child Path objects in a platform-specific way.
+   *
+   * @internal
+   */
+  childrenCache() {
+    return this.#children;
+  }
+  /**
+   * Resolve one or more path strings to a resolved string
+   *
+   * Same interface as require('path').resolve.
+   *
+   * Much faster than path.resolve() when called multiple times for the same
+   * path, because the resolved Path objects are cached.  Much slower
+   * otherwise.
+   */
+  resolve(...paths) {
+    let r = "";
+    for (let i = paths.length - 1; i >= 0; i--) {
+      const p = paths[i];
+      if (!p || p === ".")
+        continue;
+      r = r ? `${p}/${r}` : p;
+      if (this.isAbsolute(p)) {
+        break;
+      }
+    }
+    const cached = this.#resolveCache.get(r);
+    if (cached !== void 0) {
+      return cached;
+    }
+    const result = this.cwd.resolve(r).fullpath();
+    this.#resolveCache.set(r, result);
+    return result;
+  }
+  /**
+   * Resolve one or more path strings to a resolved string, returning
+   * the posix path.  Identical to .resolve() on posix systems, but on
+   * windows will return a forward-slash separated UNC path.
+   *
+   * Same interface as require('path').resolve.
+   *
+   * Much faster than path.resolve() when called multiple times for the same
+   * path, because the resolved Path objects are cached.  Much slower
+   * otherwise.
+   */
+  resolvePosix(...paths) {
+    let r = "";
+    for (let i = paths.length - 1; i >= 0; i--) {
+      const p = paths[i];
+      if (!p || p === ".")
+        continue;
+      r = r ? `${p}/${r}` : p;
+      if (this.isAbsolute(p)) {
+        break;
+      }
+    }
+    const cached = this.#resolvePosixCache.get(r);
+    if (cached !== void 0) {
+      return cached;
+    }
+    const result = this.cwd.resolve(r).fullpathPosix();
+    this.#resolvePosixCache.set(r, result);
+    return result;
+  }
+  /**
+   * find the relative path from the cwd to the supplied path string or entry
+   */
+  relative(entry = this.cwd) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    }
+    return entry.relative();
+  }
+  /**
+   * find the relative path from the cwd to the supplied path string or
+   * entry, using / as the path delimiter, even on Windows.
+   */
+  relativePosix(entry = this.cwd) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    }
+    return entry.relativePosix();
+  }
+  /**
+   * Return the basename for the provided string or Path object
+   */
+  basename(entry = this.cwd) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    }
+    return entry.name;
+  }
+  /**
+   * Return the dirname for the provided string or Path object
+   */
+  dirname(entry = this.cwd) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    }
+    return (entry.parent || entry).fullpath();
+  }
+  async readdir(entry = this.cwd, opts = {
+    withFileTypes: true
+  }) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      opts = entry;
+      entry = this.cwd;
+    }
+    const { withFileTypes } = opts;
+    if (!entry.canReaddir()) {
+      return [];
+    } else {
+      const p = await entry.readdir();
+      return withFileTypes ? p : p.map((e) => e.name);
+    }
+  }
+  readdirSync(entry = this.cwd, opts = {
+    withFileTypes: true
+  }) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      opts = entry;
+      entry = this.cwd;
+    }
+    const { withFileTypes = true } = opts;
+    if (!entry.canReaddir()) {
+      return [];
+    } else if (withFileTypes) {
+      return entry.readdirSync();
+    } else {
+      return entry.readdirSync().map((e) => e.name);
+    }
+  }
+  /**
+   * Call lstat() on the string or Path object, and update all known
+   * information that can be determined.
+   *
+   * Note that unlike `fs.lstat()`, the returned value does not contain some
+   * information, such as `mode`, `dev`, `nlink`, and `ino`.  If that
+   * information is required, you will need to call `fs.lstat` yourself.
+   *
+   * If the Path refers to a nonexistent file, or if the lstat call fails for
+   * any reason, `undefined` is returned.  Otherwise the updated Path object is
+   * returned.
+   *
+   * Results are cached, and thus may be out of date if the filesystem is
+   * mutated.
+   */
+  async lstat(entry = this.cwd) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    }
+    return entry.lstat();
+  }
+  /**
+   * synchronous {@link PathScurryBase.lstat}
+   */
+  lstatSync(entry = this.cwd) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    }
+    return entry.lstatSync();
+  }
+  async readlink(entry = this.cwd, { withFileTypes } = {
+    withFileTypes: false
+  }) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      withFileTypes = entry.withFileTypes;
+      entry = this.cwd;
+    }
+    const e = await entry.readlink();
+    return withFileTypes ? e : e?.fullpath();
+  }
+  readlinkSync(entry = this.cwd, { withFileTypes } = {
+    withFileTypes: false
+  }) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      withFileTypes = entry.withFileTypes;
+      entry = this.cwd;
+    }
+    const e = entry.readlinkSync();
+    return withFileTypes ? e : e?.fullpath();
+  }
+  async realpath(entry = this.cwd, { withFileTypes } = {
+    withFileTypes: false
+  }) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      withFileTypes = entry.withFileTypes;
+      entry = this.cwd;
+    }
+    const e = await entry.realpath();
+    return withFileTypes ? e : e?.fullpath();
+  }
+  realpathSync(entry = this.cwd, { withFileTypes } = {
+    withFileTypes: false
+  }) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      withFileTypes = entry.withFileTypes;
+      entry = this.cwd;
+    }
+    const e = entry.realpathSync();
+    return withFileTypes ? e : e?.fullpath();
+  }
+  async walk(entry = this.cwd, opts = {}) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      opts = entry;
+      entry = this.cwd;
+    }
+    const { withFileTypes = true, follow = false, filter: filter2, walkFilter } = opts;
+    const results = [];
+    if (!filter2 || filter2(entry)) {
+      results.push(withFileTypes ? entry : entry.fullpath());
+    }
+    const dirs = /* @__PURE__ */ new Set();
+    const walk = (dir, cb) => {
+      dirs.add(dir);
+      dir.readdirCB((er, entries) => {
+        if (er) {
+          return cb(er);
+        }
+        let len = entries.length;
+        if (!len)
+          return cb();
+        const next = () => {
+          if (--len === 0) {
+            cb();
+          }
+        };
+        for (const e of entries) {
+          if (!filter2 || filter2(e)) {
+            results.push(withFileTypes ? e : e.fullpath());
+          }
+          if (follow && e.isSymbolicLink()) {
+            e.realpath().then((r) => r?.isUnknown() ? r.lstat() : r).then((r) => r?.shouldWalk(dirs, walkFilter) ? walk(r, next) : next());
+          } else {
+            if (e.shouldWalk(dirs, walkFilter)) {
+              walk(e, next);
+            } else {
+              next();
+            }
+          }
+        }
+      }, true);
+    };
+    const start = entry;
+    return new Promise((res, rej) => {
+      walk(start, (er) => {
+        if (er)
+          return rej(er);
+        res(results);
+      });
+    });
+  }
+  walkSync(entry = this.cwd, opts = {}) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      opts = entry;
+      entry = this.cwd;
+    }
+    const { withFileTypes = true, follow = false, filter: filter2, walkFilter } = opts;
+    const results = [];
+    if (!filter2 || filter2(entry)) {
+      results.push(withFileTypes ? entry : entry.fullpath());
+    }
+    const dirs = /* @__PURE__ */ new Set([entry]);
+    for (const dir of dirs) {
+      const entries = dir.readdirSync();
+      for (const e of entries) {
+        if (!filter2 || filter2(e)) {
+          results.push(withFileTypes ? e : e.fullpath());
+        }
+        let r = e;
+        if (e.isSymbolicLink()) {
+          if (!(follow && (r = e.realpathSync())))
+            continue;
+          if (r.isUnknown())
+            r.lstatSync();
+        }
+        if (r.shouldWalk(dirs, walkFilter)) {
+          dirs.add(r);
+        }
+      }
+    }
+    return results;
+  }
+  /**
+   * Support for `for await`
+   *
+   * Alias for {@link PathScurryBase.iterate}
+   *
+   * Note: As of Node 19, this is very slow, compared to other methods of
+   * walking.  Consider using {@link PathScurryBase.stream} if memory overhead
+   * and backpressure are concerns, or {@link PathScurryBase.walk} if not.
+   */
+  [Symbol.asyncIterator]() {
+    return this.iterate();
+  }
+  iterate(entry = this.cwd, options = {}) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      options = entry;
+      entry = this.cwd;
+    }
+    return this.stream(entry, options)[Symbol.asyncIterator]();
+  }
+  /**
+   * Iterating over a PathScurry performs a synchronous walk.
+   *
+   * Alias for {@link PathScurryBase.iterateSync}
+   */
+  [Symbol.iterator]() {
+    return this.iterateSync();
+  }
+  *iterateSync(entry = this.cwd, opts = {}) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      opts = entry;
+      entry = this.cwd;
+    }
+    const { withFileTypes = true, follow = false, filter: filter2, walkFilter } = opts;
+    if (!filter2 || filter2(entry)) {
+      yield withFileTypes ? entry : entry.fullpath();
+    }
+    const dirs = /* @__PURE__ */ new Set([entry]);
+    for (const dir of dirs) {
+      const entries = dir.readdirSync();
+      for (const e of entries) {
+        if (!filter2 || filter2(e)) {
+          yield withFileTypes ? e : e.fullpath();
+        }
+        let r = e;
+        if (e.isSymbolicLink()) {
+          if (!(follow && (r = e.realpathSync())))
+            continue;
+          if (r.isUnknown())
+            r.lstatSync();
+        }
+        if (r.shouldWalk(dirs, walkFilter)) {
+          dirs.add(r);
+        }
+      }
+    }
+  }
+  stream(entry = this.cwd, opts = {}) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      opts = entry;
+      entry = this.cwd;
+    }
+    const { withFileTypes = true, follow = false, filter: filter2, walkFilter } = opts;
+    const results = new Minipass({ objectMode: true });
+    if (!filter2 || filter2(entry)) {
+      results.write(withFileTypes ? entry : entry.fullpath());
+    }
+    const dirs = /* @__PURE__ */ new Set();
+    const queue = [entry];
+    let processing = 0;
+    const process2 = () => {
+      let paused = false;
+      while (!paused) {
+        const dir = queue.shift();
+        if (!dir) {
+          if (processing === 0)
+            results.end();
+          return;
+        }
+        processing++;
+        dirs.add(dir);
+        const onReaddir = (er, entries, didRealpaths = false) => {
+          if (er)
+            return results.emit("error", er);
+          if (follow && !didRealpaths) {
+            const promises = [];
+            for (const e of entries) {
+              if (e.isSymbolicLink()) {
+                promises.push(e.realpath().then((r) => r?.isUnknown() ? r.lstat() : r));
+              }
+            }
+            if (promises.length) {
+              Promise.all(promises).then(() => onReaddir(null, entries, true));
+              return;
+            }
+          }
+          for (const e of entries) {
+            if (e && (!filter2 || filter2(e))) {
+              if (!results.write(withFileTypes ? e : e.fullpath())) {
+                paused = true;
+              }
+            }
+          }
+          processing--;
+          for (const e of entries) {
+            const r = e.realpathCached() || e;
+            if (r.shouldWalk(dirs, walkFilter)) {
+              queue.push(r);
+            }
+          }
+          if (paused && !results.flowing) {
+            results.once("drain", process2);
+          } else if (!sync2) {
+            process2();
+          }
+        };
+        let sync2 = true;
+        dir.readdirCB(onReaddir, true);
+        sync2 = false;
+      }
+    };
+    process2();
+    return results;
+  }
+  streamSync(entry = this.cwd, opts = {}) {
+    if (typeof entry === "string") {
+      entry = this.cwd.resolve(entry);
+    } else if (!(entry instanceof PathBase)) {
+      opts = entry;
+      entry = this.cwd;
+    }
+    const { withFileTypes = true, follow = false, filter: filter2, walkFilter } = opts;
+    const results = new Minipass({ objectMode: true });
+    const dirs = /* @__PURE__ */ new Set();
+    if (!filter2 || filter2(entry)) {
+      results.write(withFileTypes ? entry : entry.fullpath());
+    }
+    const queue = [entry];
+    let processing = 0;
+    const process2 = () => {
+      let paused = false;
+      while (!paused) {
+        const dir = queue.shift();
+        if (!dir) {
+          if (processing === 0)
+            results.end();
+          return;
+        }
+        processing++;
+        dirs.add(dir);
+        const entries = dir.readdirSync();
+        for (const e of entries) {
+          if (!filter2 || filter2(e)) {
+            if (!results.write(withFileTypes ? e : e.fullpath())) {
+              paused = true;
+            }
+          }
+        }
+        processing--;
+        for (const e of entries) {
+          let r = e;
+          if (e.isSymbolicLink()) {
+            if (!(follow && (r = e.realpathSync())))
+              continue;
+            if (r.isUnknown())
+              r.lstatSync();
+          }
+          if (r.shouldWalk(dirs, walkFilter)) {
+            queue.push(r);
+          }
+        }
+      }
+      if (paused && !results.flowing)
+        results.once("drain", process2);
+    };
+    process2();
+    return results;
+  }
+  chdir(path2 = this.cwd) {
+    const oldCwd = this.cwd;
+    this.cwd = typeof path2 === "string" ? this.cwd.resolve(path2) : path2;
+    this.cwd[setAsCwd](oldCwd);
+  }
+};
+var PathScurryWin32 = class extends PathScurryBase {
+  /**
+   * separator for generating path strings
+   */
+  sep = "\\";
+  constructor(cwd = process.cwd(), opts = {}) {
+    const { nocase = true } = opts;
+    super(cwd, win32, "\\", { ...opts, nocase });
+    this.nocase = nocase;
+    for (let p = this.cwd; p; p = p.parent) {
+      p.nocase = this.nocase;
+    }
+  }
+  /**
+   * @internal
+   */
+  parseRootPath(dir) {
+    return win32.parse(dir).root.toUpperCase();
+  }
+  /**
+   * @internal
+   */
+  newRoot(fs2) {
+    return new PathWin32(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs2 });
+  }
+  /**
+   * Return true if the provided path string is an absolute path
+   */
+  isAbsolute(p) {
+    return p.startsWith("/") || p.startsWith("\\") || /^[a-z]:(\/|\\)/i.test(p);
+  }
+};
+var PathScurryPosix = class extends PathScurryBase {
+  /**
+   * separator for generating path strings
+   */
+  sep = "/";
+  constructor(cwd = process.cwd(), opts = {}) {
+    const { nocase = false } = opts;
+    super(cwd, posix, "/", { ...opts, nocase });
+    this.nocase = nocase;
+  }
+  /**
+   * @internal
+   */
+  parseRootPath(_dir) {
+    return "/";
+  }
+  /**
+   * @internal
+   */
+  newRoot(fs2) {
+    return new PathPosix(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs2 });
+  }
+  /**
+   * Return true if the provided path string is an absolute path
+   */
+  isAbsolute(p) {
+    return p.startsWith("/");
+  }
+};
+var PathScurryDarwin = class extends PathScurryPosix {
+  constructor(cwd = process.cwd(), opts = {}) {
+    const { nocase = true } = opts;
+    super(cwd, { ...opts, nocase });
+  }
+};
+var Path = process.platform === "win32" ? PathWin32 : PathPosix;
+var PathScurry = process.platform === "win32" ? PathScurryWin32 : process.platform === "darwin" ? PathScurryDarwin : PathScurryPosix;
+
+// node_modules/glob/dist/esm/pattern.js
+var isPatternList = (pl) => pl.length >= 1;
+var isGlobList = (gl) => gl.length >= 1;
+var Pattern = class _Pattern {
+  #patternList;
+  #globList;
+  #index;
+  length;
+  #platform;
+  #rest;
+  #globString;
+  #isDrive;
+  #isUNC;
+  #isAbsolute;
+  #followGlobstar = true;
+  constructor(patternList, globList, index, platform) {
+    if (!isPatternList(patternList)) {
+      throw new TypeError("empty pattern list");
+    }
+    if (!isGlobList(globList)) {
+      throw new TypeError("empty glob list");
+    }
+    if (globList.length !== patternList.length) {
+      throw new TypeError("mismatched pattern list and glob list lengths");
+    }
+    this.length = patternList.length;
+    if (index < 0 || index >= this.length) {
+      throw new TypeError("index out of range");
+    }
+    this.#patternList = patternList;
+    this.#globList = globList;
+    this.#index = index;
+    this.#platform = platform;
+    if (this.#index === 0) {
+      if (this.isUNC()) {
+        const [p0, p1, p2, p3, ...prest] = this.#patternList;
+        const [g0, g1, g2, g3, ...grest] = this.#globList;
+        if (prest[0] === "") {
+          prest.shift();
+          grest.shift();
+        }
+        const p = [p0, p1, p2, p3, ""].join("/");
+        const g = [g0, g1, g2, g3, ""].join("/");
+        this.#patternList = [p, ...prest];
+        this.#globList = [g, ...grest];
+        this.length = this.#patternList.length;
+      } else if (this.isDrive() || this.isAbsolute()) {
+        const [p1, ...prest] = this.#patternList;
+        const [g1, ...grest] = this.#globList;
+        if (prest[0] === "") {
+          prest.shift();
+          grest.shift();
+        }
+        const p = p1 + "/";
+        const g = g1 + "/";
+        this.#patternList = [p, ...prest];
+        this.#globList = [g, ...grest];
+        this.length = this.#patternList.length;
+      }
+    }
+  }
+  /**
+   * The first entry in the parsed list of patterns
+   */
+  pattern() {
+    return this.#patternList[this.#index];
+  }
+  /**
+   * true of if pattern() returns a string
+   */
+  isString() {
+    return typeof this.#patternList[this.#index] === "string";
+  }
+  /**
+   * true of if pattern() returns GLOBSTAR
+   */
+  isGlobstar() {
+    return this.#patternList[this.#index] === GLOBSTAR;
+  }
+  /**
+   * true if pattern() returns a regexp
+   */
+  isRegExp() {
+    return this.#patternList[this.#index] instanceof RegExp;
+  }
+  /**
+   * The /-joined set of glob parts that make up this pattern
+   */
+  globString() {
+    return this.#globString = this.#globString || (this.#index === 0 ? this.isAbsolute() ? this.#globList[0] + this.#globList.slice(1).join("/") : this.#globList.join("/") : this.#globList.slice(this.#index).join("/"));
+  }
+  /**
+   * true if there are more pattern parts after this one
+   */
+  hasMore() {
+    return this.length > this.#index + 1;
+  }
+  /**
+   * The rest of the pattern after this part, or null if this is the end
+   */
+  rest() {
+    if (this.#rest !== void 0)
+      return this.#rest;
+    if (!this.hasMore())
+      return this.#rest = null;
+    this.#rest = new _Pattern(this.#patternList, this.#globList, this.#index + 1, this.#platform);
+    this.#rest.#isAbsolute = this.#isAbsolute;
+    this.#rest.#isUNC = this.#isUNC;
+    this.#rest.#isDrive = this.#isDrive;
+    return this.#rest;
+  }
+  /**
+   * true if the pattern represents a //unc/path/ on windows
+   */
+  isUNC() {
+    const pl = this.#patternList;
+    return this.#isUNC !== void 0 ? this.#isUNC : this.#isUNC = this.#platform === "win32" && this.#index === 0 && pl[0] === "" && pl[1] === "" && typeof pl[2] === "string" && !!pl[2] && typeof pl[3] === "string" && !!pl[3];
+  }
+  // pattern like C:/...
+  // split = ['C:', ...]
+  // XXX: would be nice to handle patterns like `c:*` to test the cwd
+  // in c: for *, but I don't know of a way to even figure out what that
+  // cwd is without actually chdir'ing into it?
+  /**
+   * True if the pattern starts with a drive letter on Windows
+   */
+  isDrive() {
+    const pl = this.#patternList;
+    return this.#isDrive !== void 0 ? this.#isDrive : this.#isDrive = this.#platform === "win32" && this.#index === 0 && this.length > 1 && typeof pl[0] === "string" && /^[a-z]:$/i.test(pl[0]);
+  }
+  // pattern = '/' or '/...' or '/x/...'
+  // split = ['', ''] or ['', ...] or ['', 'x', ...]
+  // Drive and UNC both considered absolute on windows
+  /**
+   * True if the pattern is rooted on an absolute path
+   */
+  isAbsolute() {
+    const pl = this.#patternList;
+    return this.#isAbsolute !== void 0 ? this.#isAbsolute : this.#isAbsolute = pl[0] === "" && pl.length > 1 || this.isDrive() || this.isUNC();
+  }
+  /**
+   * consume the root of the pattern, and return it
+   */
+  root() {
+    const p = this.#patternList[0];
+    return typeof p === "string" && this.isAbsolute() && this.#index === 0 ? p : "";
+  }
+  /**
+   * Check to see if the current globstar pattern is allowed to follow
+   * a symbolic link.
+   */
+  checkFollowGlobstar() {
+    return !(this.#index === 0 || !this.isGlobstar() || !this.#followGlobstar);
+  }
+  /**
+   * Mark that the current globstar pattern is following a symbolic link
+   */
+  markFollowGlobstar() {
+    if (this.#index === 0 || !this.isGlobstar() || !this.#followGlobstar)
+      return false;
+    this.#followGlobstar = false;
+    return true;
+  }
+};
+
+// node_modules/glob/dist/esm/ignore.js
+var defaultPlatform2 = typeof process === "object" && process && typeof process.platform === "string" ? process.platform : "linux";
+var Ignore = class {
+  relative;
+  relativeChildren;
+  absolute;
+  absoluteChildren;
+  platform;
+  mmopts;
+  constructor(ignored, { nobrace, nocase, noext, noglobstar, platform = defaultPlatform2 }) {
+    this.relative = [];
+    this.absolute = [];
+    this.relativeChildren = [];
+    this.absoluteChildren = [];
+    this.platform = platform;
+    this.mmopts = {
+      dot: true,
+      nobrace,
+      nocase,
+      noext,
+      noglobstar,
+      optimizationLevel: 2,
+      platform,
+      nocomment: true,
+      nonegate: true
+    };
+    for (const ign of ignored)
+      this.add(ign);
+  }
+  add(ign) {
+    const mm = new Minimatch(ign, this.mmopts);
+    for (let i = 0; i < mm.set.length; i++) {
+      const parsed = mm.set[i];
+      const globParts = mm.globParts[i];
+      if (!parsed || !globParts) {
+        throw new Error("invalid pattern object");
+      }
+      while (parsed[0] === "." && globParts[0] === ".") {
+        parsed.shift();
+        globParts.shift();
+      }
+      const p = new Pattern(parsed, globParts, 0, this.platform);
+      const m = new Minimatch(p.globString(), this.mmopts);
+      const children = globParts[globParts.length - 1] === "**";
+      const absolute = p.isAbsolute();
+      if (absolute)
+        this.absolute.push(m);
+      else
+        this.relative.push(m);
+      if (children) {
+        if (absolute)
+          this.absoluteChildren.push(m);
+        else
+          this.relativeChildren.push(m);
+      }
+    }
+  }
+  ignored(p) {
+    const fullpath = p.fullpath();
+    const fullpaths = `${fullpath}/`;
+    const relative4 = p.relative() || ".";
+    const relatives = `${relative4}/`;
+    for (const m of this.relative) {
+      if (m.match(relative4) || m.match(relatives))
+        return true;
+    }
+    for (const m of this.absolute) {
+      if (m.match(fullpath) || m.match(fullpaths))
+        return true;
+    }
+    return false;
+  }
+  childrenIgnored(p) {
+    const fullpath = p.fullpath() + "/";
+    const relative4 = (p.relative() || ".") + "/";
+    for (const m of this.relativeChildren) {
+      if (m.match(relative4))
+        return true;
+    }
+    for (const m of this.absoluteChildren) {
+      if (m.match(fullpath))
+        return true;
+    }
+    return false;
+  }
+};
+
+// node_modules/glob/dist/esm/processor.js
+var HasWalkedCache = class _HasWalkedCache {
+  store;
+  constructor(store = /* @__PURE__ */ new Map()) {
+    this.store = store;
+  }
+  copy() {
+    return new _HasWalkedCache(new Map(this.store));
+  }
+  hasWalked(target, pattern) {
+    return this.store.get(target.fullpath())?.has(pattern.globString());
+  }
+  storeWalked(target, pattern) {
+    const fullpath = target.fullpath();
+    const cached = this.store.get(fullpath);
+    if (cached)
+      cached.add(pattern.globString());
+    else
+      this.store.set(fullpath, /* @__PURE__ */ new Set([pattern.globString()]));
+  }
+};
+var MatchRecord = class {
+  store = /* @__PURE__ */ new Map();
+  add(target, absolute, ifDir) {
+    const n = (absolute ? 2 : 0) | (ifDir ? 1 : 0);
+    const current = this.store.get(target);
+    this.store.set(target, current === void 0 ? n : n & current);
+  }
+  // match, absolute, ifdir
+  entries() {
+    return [...this.store.entries()].map(([path2, n]) => [
+      path2,
+      !!(n & 2),
+      !!(n & 1)
+    ]);
+  }
+};
+var SubWalks = class {
+  store = /* @__PURE__ */ new Map();
+  add(target, pattern) {
+    if (!target.canReaddir()) {
+      return;
+    }
+    const subs = this.store.get(target);
+    if (subs) {
+      if (!subs.find((p) => p.globString() === pattern.globString())) {
+        subs.push(pattern);
+      }
+    } else
+      this.store.set(target, [pattern]);
+  }
+  get(target) {
+    const subs = this.store.get(target);
+    if (!subs) {
+      throw new Error("attempting to walk unknown path");
+    }
+    return subs;
+  }
+  entries() {
+    return this.keys().map((k) => [k, this.store.get(k)]);
+  }
+  keys() {
+    return [...this.store.keys()].filter((t) => t.canReaddir());
+  }
+};
+var Processor = class _Processor {
+  hasWalkedCache;
+  matches = new MatchRecord();
+  subwalks = new SubWalks();
+  patterns;
+  follow;
+  dot;
+  opts;
+  constructor(opts, hasWalkedCache) {
+    this.opts = opts;
+    this.follow = !!opts.follow;
+    this.dot = !!opts.dot;
+    this.hasWalkedCache = hasWalkedCache ? hasWalkedCache.copy() : new HasWalkedCache();
+  }
+  processPatterns(target, patterns) {
+    this.patterns = patterns;
+    const processingSet = patterns.map((p) => [target, p]);
+    for (let [t, pattern] of processingSet) {
+      this.hasWalkedCache.storeWalked(t, pattern);
+      const root = pattern.root();
+      const absolute = pattern.isAbsolute() && this.opts.absolute !== false;
+      if (root) {
+        t = t.resolve(root === "/" && this.opts.root !== void 0 ? this.opts.root : root);
+        const rest2 = pattern.rest();
+        if (!rest2) {
+          this.matches.add(t, true, false);
+          continue;
+        } else {
+          pattern = rest2;
+        }
+      }
+      if (t.isENOENT())
+        continue;
+      let p;
+      let rest;
+      let changed = false;
+      while (typeof (p = pattern.pattern()) === "string" && (rest = pattern.rest())) {
+        const c = t.resolve(p);
+        t = c;
+        pattern = rest;
+        changed = true;
+      }
+      p = pattern.pattern();
+      rest = pattern.rest();
+      if (changed) {
+        if (this.hasWalkedCache.hasWalked(t, pattern))
+          continue;
+        this.hasWalkedCache.storeWalked(t, pattern);
+      }
+      if (typeof p === "string") {
+        const ifDir = p === ".." || p === "" || p === ".";
+        this.matches.add(t.resolve(p), absolute, ifDir);
+        continue;
+      } else if (p === GLOBSTAR) {
+        if (!t.isSymbolicLink() || this.follow || pattern.checkFollowGlobstar()) {
+          this.subwalks.add(t, pattern);
+        }
+        const rp = rest?.pattern();
+        const rrest = rest?.rest();
+        if (!rest || (rp === "" || rp === ".") && !rrest) {
+          this.matches.add(t, absolute, rp === "" || rp === ".");
+        } else {
+          if (rp === "..") {
+            const tp = t.parent || t;
+            if (!rrest)
+              this.matches.add(tp, absolute, true);
+            else if (!this.hasWalkedCache.hasWalked(tp, rrest)) {
+              this.subwalks.add(tp, rrest);
+            }
+          }
+        }
+      } else if (p instanceof RegExp) {
+        this.subwalks.add(t, pattern);
+      }
+    }
+    return this;
+  }
+  subwalkTargets() {
+    return this.subwalks.keys();
+  }
+  child() {
+    return new _Processor(this.opts, this.hasWalkedCache);
+  }
+  // return a new Processor containing the subwalks for each
+  // child entry, and a set of matches, and
+  // a hasWalkedCache that's a copy of this one
+  // then we're going to call
+  filterEntries(parent, entries) {
+    const patterns = this.subwalks.get(parent);
+    const results = this.child();
+    for (const e of entries) {
+      for (const pattern of patterns) {
+        const absolute = pattern.isAbsolute();
+        const p = pattern.pattern();
+        const rest = pattern.rest();
+        if (p === GLOBSTAR) {
+          results.testGlobstar(e, pattern, rest, absolute);
+        } else if (p instanceof RegExp) {
+          results.testRegExp(e, p, rest, absolute);
+        } else {
+          results.testString(e, p, rest, absolute);
+        }
+      }
+    }
+    return results;
+  }
+  testGlobstar(e, pattern, rest, absolute) {
+    if (this.dot || !e.name.startsWith(".")) {
+      if (!pattern.hasMore()) {
+        this.matches.add(e, absolute, false);
+      }
+      if (e.canReaddir()) {
+        if (this.follow || !e.isSymbolicLink()) {
+          this.subwalks.add(e, pattern);
+        } else if (e.isSymbolicLink()) {
+          if (rest && pattern.checkFollowGlobstar()) {
+            this.subwalks.add(e, rest);
+          } else if (pattern.markFollowGlobstar()) {
+            this.subwalks.add(e, pattern);
+          }
+        }
+      }
+    }
+    if (rest) {
+      const rp = rest.pattern();
+      if (typeof rp === "string" && // dots and empty were handled already
+      rp !== ".." && rp !== "" && rp !== ".") {
+        this.testString(e, rp, rest.rest(), absolute);
+      } else if (rp === "..") {
+        const ep = e.parent || e;
+        this.subwalks.add(ep, rest);
+      } else if (rp instanceof RegExp) {
+        this.testRegExp(e, rp, rest.rest(), absolute);
+      }
+    }
+  }
+  testRegExp(e, p, rest, absolute) {
+    if (!p.test(e.name))
+      return;
+    if (!rest) {
+      this.matches.add(e, absolute, false);
+    } else {
+      this.subwalks.add(e, rest);
+    }
+  }
+  testString(e, p, rest, absolute) {
+    if (!e.isNamed(p))
+      return;
+    if (!rest) {
+      this.matches.add(e, absolute, false);
+    } else {
+      this.subwalks.add(e, rest);
+    }
+  }
+};
+
+// node_modules/glob/dist/esm/walker.js
+var makeIgnore = (ignore, opts) => typeof ignore === "string" ? new Ignore([ignore], opts) : Array.isArray(ignore) ? new Ignore(ignore, opts) : ignore;
+var GlobUtil = class {
+  path;
+  patterns;
+  opts;
+  seen = /* @__PURE__ */ new Set();
+  paused = false;
+  aborted = false;
+  #onResume = [];
+  #ignore;
+  #sep;
+  signal;
+  maxDepth;
+  includeChildMatches;
+  constructor(patterns, path2, opts) {
+    this.patterns = patterns;
+    this.path = path2;
+    this.opts = opts;
+    this.#sep = !opts.posix && opts.platform === "win32" ? "\\" : "/";
+    this.includeChildMatches = opts.includeChildMatches !== false;
+    if (opts.ignore || !this.includeChildMatches) {
+      this.#ignore = makeIgnore(opts.ignore ?? [], opts);
+      if (!this.includeChildMatches && typeof this.#ignore.add !== "function") {
+        const m = "cannot ignore child matches, ignore lacks add() method.";
+        throw new Error(m);
+      }
+    }
+    this.maxDepth = opts.maxDepth || Infinity;
+    if (opts.signal) {
+      this.signal = opts.signal;
+      this.signal.addEventListener("abort", () => {
+        this.#onResume.length = 0;
+      });
+    }
+  }
+  #ignored(path2) {
+    return this.seen.has(path2) || !!this.#ignore?.ignored?.(path2);
+  }
+  #childrenIgnored(path2) {
+    return !!this.#ignore?.childrenIgnored?.(path2);
+  }
+  // backpressure mechanism
+  pause() {
+    this.paused = true;
+  }
+  resume() {
+    if (this.signal?.aborted)
+      return;
+    this.paused = false;
+    let fn = void 0;
+    while (!this.paused && (fn = this.#onResume.shift())) {
+      fn();
+    }
+  }
+  onResume(fn) {
+    if (this.signal?.aborted)
+      return;
+    if (!this.paused) {
+      fn();
+    } else {
+      this.#onResume.push(fn);
+    }
+  }
+  // do the requisite realpath/stat checking, and return the path
+  // to add or undefined to filter it out.
+  async matchCheck(e, ifDir) {
+    if (ifDir && this.opts.nodir)
+      return void 0;
+    let rpc;
+    if (this.opts.realpath) {
+      rpc = e.realpathCached() || await e.realpath();
+      if (!rpc)
+        return void 0;
+      e = rpc;
+    }
+    const needStat = e.isUnknown() || this.opts.stat;
+    const s = needStat ? await e.lstat() : e;
+    if (this.opts.follow && this.opts.nodir && s?.isSymbolicLink()) {
+      const target = await s.realpath();
+      if (target && (target.isUnknown() || this.opts.stat)) {
+        await target.lstat();
+      }
+    }
+    return this.matchCheckTest(s, ifDir);
+  }
+  matchCheckTest(e, ifDir) {
+    return e && (this.maxDepth === Infinity || e.depth() <= this.maxDepth) && (!ifDir || e.canReaddir()) && (!this.opts.nodir || !e.isDirectory()) && (!this.opts.nodir || !this.opts.follow || !e.isSymbolicLink() || !e.realpathCached()?.isDirectory()) && !this.#ignored(e) ? e : void 0;
+  }
+  matchCheckSync(e, ifDir) {
+    if (ifDir && this.opts.nodir)
+      return void 0;
+    let rpc;
+    if (this.opts.realpath) {
+      rpc = e.realpathCached() || e.realpathSync();
+      if (!rpc)
+        return void 0;
+      e = rpc;
+    }
+    const needStat = e.isUnknown() || this.opts.stat;
+    const s = needStat ? e.lstatSync() : e;
+    if (this.opts.follow && this.opts.nodir && s?.isSymbolicLink()) {
+      const target = s.realpathSync();
+      if (target && (target?.isUnknown() || this.opts.stat)) {
+        target.lstatSync();
+      }
+    }
+    return this.matchCheckTest(s, ifDir);
+  }
+  matchFinish(e, absolute) {
+    if (this.#ignored(e))
+      return;
+    if (!this.includeChildMatches && this.#ignore?.add) {
+      const ign = `${e.relativePosix()}/**`;
+      this.#ignore.add(ign);
+    }
+    const abs = this.opts.absolute === void 0 ? absolute : this.opts.absolute;
+    this.seen.add(e);
+    const mark = this.opts.mark && e.isDirectory() ? this.#sep : "";
+    if (this.opts.withFileTypes) {
+      this.matchEmit(e);
+    } else if (abs) {
+      const abs2 = this.opts.posix ? e.fullpathPosix() : e.fullpath();
+      this.matchEmit(abs2 + mark);
+    } else {
+      const rel = this.opts.posix ? e.relativePosix() : e.relative();
+      const pre = this.opts.dotRelative && !rel.startsWith(".." + this.#sep) ? "." + this.#sep : "";
+      this.matchEmit(!rel ? "." + mark : pre + rel + mark);
+    }
+  }
+  async match(e, absolute, ifDir) {
+    const p = await this.matchCheck(e, ifDir);
+    if (p)
+      this.matchFinish(p, absolute);
+  }
+  matchSync(e, absolute, ifDir) {
+    const p = this.matchCheckSync(e, ifDir);
+    if (p)
+      this.matchFinish(p, absolute);
+  }
+  walkCB(target, patterns, cb) {
+    if (this.signal?.aborted)
+      cb();
+    this.walkCB2(target, patterns, new Processor(this.opts), cb);
+  }
+  walkCB2(target, patterns, processor, cb) {
+    if (this.#childrenIgnored(target))
+      return cb();
+    if (this.signal?.aborted)
+      cb();
+    if (this.paused) {
+      this.onResume(() => this.walkCB2(target, patterns, processor, cb));
+      return;
+    }
+    processor.processPatterns(target, patterns);
+    let tasks = 1;
+    const next = () => {
+      if (--tasks === 0)
+        cb();
+    };
+    for (const [m, absolute, ifDir] of processor.matches.entries()) {
+      if (this.#ignored(m))
+        continue;
+      tasks++;
+      this.match(m, absolute, ifDir).then(() => next());
+    }
+    for (const t of processor.subwalkTargets()) {
+      if (this.maxDepth !== Infinity && t.depth() >= this.maxDepth) {
+        continue;
+      }
+      tasks++;
+      const childrenCached = t.readdirCached();
+      if (t.calledReaddir())
+        this.walkCB3(t, childrenCached, processor, next);
+      else {
+        t.readdirCB((_, entries) => this.walkCB3(t, entries, processor, next), true);
+      }
+    }
+    next();
+  }
+  walkCB3(target, entries, processor, cb) {
+    processor = processor.filterEntries(target, entries);
+    let tasks = 1;
+    const next = () => {
+      if (--tasks === 0)
+        cb();
+    };
+    for (const [m, absolute, ifDir] of processor.matches.entries()) {
+      if (this.#ignored(m))
+        continue;
+      tasks++;
+      this.match(m, absolute, ifDir).then(() => next());
+    }
+    for (const [target2, patterns] of processor.subwalks.entries()) {
+      tasks++;
+      this.walkCB2(target2, patterns, processor.child(), next);
+    }
+    next();
+  }
+  walkCBSync(target, patterns, cb) {
+    if (this.signal?.aborted)
+      cb();
+    this.walkCB2Sync(target, patterns, new Processor(this.opts), cb);
+  }
+  walkCB2Sync(target, patterns, processor, cb) {
+    if (this.#childrenIgnored(target))
+      return cb();
+    if (this.signal?.aborted)
+      cb();
+    if (this.paused) {
+      this.onResume(() => this.walkCB2Sync(target, patterns, processor, cb));
+      return;
+    }
+    processor.processPatterns(target, patterns);
+    let tasks = 1;
+    const next = () => {
+      if (--tasks === 0)
+        cb();
+    };
+    for (const [m, absolute, ifDir] of processor.matches.entries()) {
+      if (this.#ignored(m))
+        continue;
+      this.matchSync(m, absolute, ifDir);
+    }
+    for (const t of processor.subwalkTargets()) {
+      if (this.maxDepth !== Infinity && t.depth() >= this.maxDepth) {
+        continue;
+      }
+      tasks++;
+      const children = t.readdirSync();
+      this.walkCB3Sync(t, children, processor, next);
+    }
+    next();
+  }
+  walkCB3Sync(target, entries, processor, cb) {
+    processor = processor.filterEntries(target, entries);
+    let tasks = 1;
+    const next = () => {
+      if (--tasks === 0)
+        cb();
+    };
+    for (const [m, absolute, ifDir] of processor.matches.entries()) {
+      if (this.#ignored(m))
+        continue;
+      this.matchSync(m, absolute, ifDir);
+    }
+    for (const [target2, patterns] of processor.subwalks.entries()) {
+      tasks++;
+      this.walkCB2Sync(target2, patterns, processor.child(), next);
+    }
+    next();
+  }
+};
+var GlobWalker = class extends GlobUtil {
+  matches = /* @__PURE__ */ new Set();
+  constructor(patterns, path2, opts) {
+    super(patterns, path2, opts);
+  }
+  matchEmit(e) {
+    this.matches.add(e);
+  }
+  async walk() {
+    if (this.signal?.aborted)
+      throw this.signal.reason;
+    if (this.path.isUnknown()) {
+      await this.path.lstat();
+    }
+    await new Promise((res, rej) => {
+      this.walkCB(this.path, this.patterns, () => {
+        if (this.signal?.aborted) {
+          rej(this.signal.reason);
+        } else {
+          res(this.matches);
+        }
+      });
+    });
+    return this.matches;
+  }
+  walkSync() {
+    if (this.signal?.aborted)
+      throw this.signal.reason;
+    if (this.path.isUnknown()) {
+      this.path.lstatSync();
+    }
+    this.walkCBSync(this.path, this.patterns, () => {
+      if (this.signal?.aborted)
+        throw this.signal.reason;
+    });
+    return this.matches;
+  }
+};
+var GlobStream = class extends GlobUtil {
+  results;
+  constructor(patterns, path2, opts) {
+    super(patterns, path2, opts);
+    this.results = new Minipass({
+      signal: this.signal,
+      objectMode: true
+    });
+    this.results.on("drain", () => this.resume());
+    this.results.on("resume", () => this.resume());
+  }
+  matchEmit(e) {
+    this.results.write(e);
+    if (!this.results.flowing)
+      this.pause();
+  }
+  stream() {
+    const target = this.path;
+    if (target.isUnknown()) {
+      target.lstat().then(() => {
+        this.walkCB(target, this.patterns, () => this.results.end());
+      });
+    } else {
+      this.walkCB(target, this.patterns, () => this.results.end());
+    }
+    return this.results;
+  }
+  streamSync() {
+    if (this.path.isUnknown()) {
+      this.path.lstatSync();
+    }
+    this.walkCBSync(this.path, this.patterns, () => this.results.end());
+    return this.results;
+  }
+};
+
+// node_modules/glob/dist/esm/glob.js
+var defaultPlatform3 = typeof process === "object" && process && typeof process.platform === "string" ? process.platform : "linux";
+var Glob = class {
+  absolute;
+  cwd;
+  root;
+  dot;
+  dotRelative;
+  follow;
+  ignore;
+  magicalBraces;
+  mark;
+  matchBase;
+  maxDepth;
+  nobrace;
+  nocase;
+  nodir;
+  noext;
+  noglobstar;
+  pattern;
+  platform;
+  realpath;
+  scurry;
+  stat;
+  signal;
+  windowsPathsNoEscape;
+  withFileTypes;
+  includeChildMatches;
+  /**
+   * The options provided to the constructor.
+   */
+  opts;
+  /**
+   * An array of parsed immutable {@link Pattern} objects.
+   */
+  patterns;
+  /**
+   * All options are stored as properties on the `Glob` object.
+   *
+   * See {@link GlobOptions} for full options descriptions.
+   *
+   * Note that a previous `Glob` object can be passed as the
+   * `GlobOptions` to another `Glob` instantiation to re-use settings
+   * and caches with a new pattern.
+   *
+   * Traversal functions can be called multiple times to run the walk
+   * again.
+   */
+  constructor(pattern, opts) {
+    if (!opts)
+      throw new TypeError("glob options required");
+    this.withFileTypes = !!opts.withFileTypes;
+    this.signal = opts.signal;
+    this.follow = !!opts.follow;
+    this.dot = !!opts.dot;
+    this.dotRelative = !!opts.dotRelative;
+    this.nodir = !!opts.nodir;
+    this.mark = !!opts.mark;
+    if (!opts.cwd) {
+      this.cwd = "";
+    } else if (opts.cwd instanceof URL || opts.cwd.startsWith("file://")) {
+      opts.cwd = fileURLToPath2(opts.cwd);
+    }
+    this.cwd = opts.cwd || "";
+    this.root = opts.root;
+    this.magicalBraces = !!opts.magicalBraces;
+    this.nobrace = !!opts.nobrace;
+    this.noext = !!opts.noext;
+    this.realpath = !!opts.realpath;
+    this.absolute = opts.absolute;
+    this.includeChildMatches = opts.includeChildMatches !== false;
+    this.noglobstar = !!opts.noglobstar;
+    this.matchBase = !!opts.matchBase;
+    this.maxDepth = typeof opts.maxDepth === "number" ? opts.maxDepth : Infinity;
+    this.stat = !!opts.stat;
+    this.ignore = opts.ignore;
+    if (this.withFileTypes && this.absolute !== void 0) {
+      throw new Error("cannot set absolute and withFileTypes:true");
+    }
+    if (typeof pattern === "string") {
+      pattern = [pattern];
+    }
+    this.windowsPathsNoEscape = !!opts.windowsPathsNoEscape || opts.allowWindowsEscape === false;
+    if (this.windowsPathsNoEscape) {
+      pattern = pattern.map((p) => p.replace(/\\/g, "/"));
+    }
+    if (this.matchBase) {
+      if (opts.noglobstar) {
+        throw new TypeError("base matching requires globstar");
+      }
+      pattern = pattern.map((p) => p.includes("/") ? p : `./**/${p}`);
+    }
+    this.pattern = pattern;
+    this.platform = opts.platform || defaultPlatform3;
+    this.opts = { ...opts, platform: this.platform };
+    if (opts.scurry) {
+      this.scurry = opts.scurry;
+      if (opts.nocase !== void 0 && opts.nocase !== opts.scurry.nocase) {
+        throw new Error("nocase option contradicts provided scurry option");
+      }
+    } else {
+      const Scurry = opts.platform === "win32" ? PathScurryWin32 : opts.platform === "darwin" ? PathScurryDarwin : opts.platform ? PathScurryPosix : PathScurry;
+      this.scurry = new Scurry(this.cwd, {
+        nocase: opts.nocase,
+        fs: opts.fs
+      });
+    }
+    this.nocase = this.scurry.nocase;
+    const nocaseMagicOnly = this.platform === "darwin" || this.platform === "win32";
+    const mmo = {
+      // default nocase based on platform
+      ...opts,
+      dot: this.dot,
+      matchBase: this.matchBase,
+      nobrace: this.nobrace,
+      nocase: this.nocase,
+      nocaseMagicOnly,
+      nocomment: true,
+      noext: this.noext,
+      nonegate: true,
+      optimizationLevel: 2,
+      platform: this.platform,
+      windowsPathsNoEscape: this.windowsPathsNoEscape,
+      debug: !!this.opts.debug
+    };
+    const mms = this.pattern.map((p) => new Minimatch(p, mmo));
+    const [matchSet, globParts] = mms.reduce((set, m) => {
+      set[0].push(...m.set);
+      set[1].push(...m.globParts);
+      return set;
+    }, [[], []]);
+    this.patterns = matchSet.map((set, i) => {
+      const g = globParts[i];
+      if (!g)
+        throw new Error("invalid pattern object");
+      return new Pattern(set, g, 0, this.platform);
+    });
+  }
+  async walk() {
+    return [
+      ...await new GlobWalker(this.patterns, this.scurry.cwd, {
+        ...this.opts,
+        maxDepth: this.maxDepth !== Infinity ? this.maxDepth + this.scurry.cwd.depth() : Infinity,
+        platform: this.platform,
+        nocase: this.nocase,
+        includeChildMatches: this.includeChildMatches
+      }).walk()
+    ];
+  }
+  walkSync() {
+    return [
+      ...new GlobWalker(this.patterns, this.scurry.cwd, {
+        ...this.opts,
+        maxDepth: this.maxDepth !== Infinity ? this.maxDepth + this.scurry.cwd.depth() : Infinity,
+        platform: this.platform,
+        nocase: this.nocase,
+        includeChildMatches: this.includeChildMatches
+      }).walkSync()
+    ];
+  }
+  stream() {
+    return new GlobStream(this.patterns, this.scurry.cwd, {
+      ...this.opts,
+      maxDepth: this.maxDepth !== Infinity ? this.maxDepth + this.scurry.cwd.depth() : Infinity,
+      platform: this.platform,
+      nocase: this.nocase,
+      includeChildMatches: this.includeChildMatches
+    }).stream();
+  }
+  streamSync() {
+    return new GlobStream(this.patterns, this.scurry.cwd, {
+      ...this.opts,
+      maxDepth: this.maxDepth !== Infinity ? this.maxDepth + this.scurry.cwd.depth() : Infinity,
+      platform: this.platform,
+      nocase: this.nocase,
+      includeChildMatches: this.includeChildMatches
+    }).streamSync();
+  }
+  /**
+   * Default sync iteration function. Returns a Generator that
+   * iterates over the results.
+   */
+  iterateSync() {
+    return this.streamSync()[Symbol.iterator]();
+  }
+  [Symbol.iterator]() {
+    return this.iterateSync();
+  }
+  /**
+   * Default async iteration function. Returns an AsyncGenerator that
+   * iterates over the results.
+   */
+  iterate() {
+    return this.stream()[Symbol.asyncIterator]();
+  }
+  [Symbol.asyncIterator]() {
+    return this.iterate();
+  }
+};
+
+// node_modules/glob/dist/esm/has-magic.js
+var hasMagic = (pattern, options = {}) => {
+  if (!Array.isArray(pattern)) {
+    pattern = [pattern];
+  }
+  for (const p of pattern) {
+    if (new Minimatch(p, options).hasMagic())
+      return true;
+  }
+  return false;
+};
+
+// node_modules/glob/dist/esm/index.js
+function globStreamSync(pattern, options = {}) {
+  return new Glob(pattern, options).streamSync();
+}
+function globStream(pattern, options = {}) {
+  return new Glob(pattern, options).stream();
+}
+function globSync(pattern, options = {}) {
+  return new Glob(pattern, options).walkSync();
+}
+async function glob_(pattern, options = {}) {
+  return new Glob(pattern, options).walk();
+}
+function globIterateSync(pattern, options = {}) {
+  return new Glob(pattern, options).iterateSync();
+}
+function globIterate(pattern, options = {}) {
+  return new Glob(pattern, options).iterate();
+}
+var streamSync = globStreamSync;
+var stream = Object.assign(globStream, { sync: globStreamSync });
+var iterateSync = globIterateSync;
+var iterate = Object.assign(globIterate, {
+  sync: globIterateSync
+});
+var sync = Object.assign(globSync, {
+  stream: globStreamSync,
+  iterate: globIterateSync
+});
+var glob = Object.assign(glob_, {
+  glob: glob_,
+  globSync,
+  sync,
+  globStream,
+  stream,
+  globStreamSync,
+  streamSync,
+  globIterate,
+  iterate,
+  globIterateSync,
+  iterateSync,
+  Glob,
+  hasMagic,
+  escape,
+  unescape
+});
+glob.glob = glob;
+
+// src/tools/doc-sources.ts
+import { createHash } from "crypto";
+import { join, relative, resolve, sep as sep2 } from "path";
+import { spawnSync } from "child_process";
+function hashContent(content) {
+  return createHash("sha256").update(content).digest("hex");
+}
+var JUNK_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".venv", "venv", ".next", "dist", "build"]);
+function assertSafeSlug(slug) {
+  if (!slug || /[\\/]|\.\./.test(slug)) {
+    throw new Error(`unsafe slug: ${JSON.stringify(slug)}`);
+  }
+}
+function filterIgnored(projectRoot, absPaths) {
+  const nonJunk = absPaths.filter((p) => !relative(projectRoot, p).split("/").some((seg) => JUNK_DIRS.has(seg)));
+  if (nonJunk.length === 0) return [];
+  const rels = nonJunk.map((p) => relative(projectRoot, p));
+  const res = spawnSync("git", ["-C", projectRoot, "check-ignore", "--stdin"], { input: rels.join("\n"), encoding: "utf-8" });
+  if (res.status === 0 || res.status === 1) {
+    const ignored = new Set((res.stdout || "").split("\n").filter(Boolean).map((r) => join(projectRoot, r)));
+    return nonJunk.filter((p) => !ignored.has(p));
+  }
+  return nonJunk;
+}
+
+// src/tools/raw-inbox.ts
+import { promises as fs } from "fs";
+import { join as join2, basename, extname } from "path";
+function rawDir(brainDir, slug) {
+  return join2(brainDir, "projects", slug, "raw");
+}
+function slugify(text) {
+  const s = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
+  return s || "item";
+}
+function compactStamp(iso) {
+  return iso.replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
+}
+function isBinary(buf) {
+  const n = Math.min(buf.length, 8192);
+  for (let i = 0; i < n; i++) if (buf[i] === 0) return true;
+  return false;
+}
+function contentTypeForFile(path2, binary) {
+  const ext2 = extname(path2).toLowerCase();
+  if (binary) return ext2 === ".pdf" ? "application/pdf" : "application/octet-stream";
+  return ext2 === ".md" || ext2 === ".markdown" ? "text/markdown" : "text/plain";
+}
+function fmValue(s) {
+  return s.replace(/[\r\n]+/g, " ");
+}
+function serialize(item) {
+  const fm = ["---"];
+  fm.push(`id: ${fmValue(item.id)}`);
+  fm.push(`source: ${fmValue(item.source)}`);
+  fm.push(`captured_at: ${fmValue(item.captured_at)}`);
+  fm.push(`captured_by: ${fmValue(item.captured_by)}`);
+  fm.push(`content_type: ${fmValue(item.content_type)}`);
+  fm.push(`status: ${fmValue(item.status)}`);
+  if (item.target_node) fm.push(`target_node: ${fmValue(item.target_node)}`);
+  if (item.blob) fm.push(`blob: ${fmValue(item.blob)}`);
+  fm.push(`hash: ${fmValue(item.hash)}`);
+  fm.push(`gist: ${fmValue(item.gist)}`);
+  fm.push("---", "", item.body, "");
+  return fm.join("\n");
+}
+function parse(content, id) {
+  const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
+  const base = {
+    id,
+    source: "",
+    captured_at: "",
+    captured_by: "user",
+    content_type: "",
+    status: "unprocessed",
+    hash: "",
+    gist: "",
+    body: ""
+  };
+  if (!m) {
+    return { ...base, body: content, malformed: true };
+  }
+  const [, fmText, body] = m;
+  const get = (k) => {
+    const mm = fmText.match(new RegExp(`^${k}:[ \\t]*(.*)$`, "m"));
+    return mm ? mm[1].trim() : void 0;
+  };
+  const status = get("status") ?? "";
+  const validStatus = status === "unprocessed" || status === "processed" || status === "discarded";
+  const item = {
+    id,
+    source: get("source") ?? "",
+    captured_at: get("captured_at") ?? "",
+    captured_by: get("captured_by") ?? "user",
+    content_type: get("content_type") ?? "",
+    status: validStatus ? status : "unprocessed",
+    target_node: get("target_node") || void 0,
+    blob: get("blob") || void 0,
+    hash: get("hash") ?? "",
+    gist: get("gist") ?? "",
+    body: body.trim()
+  };
+  if (!item.source || !item.captured_at || !item.content_type || !validStatus) item.malformed = true;
+  return item;
+}
+async function readItems(brainDir, slug) {
+  const dir = rawDir(brainDir, slug);
+  let names = [];
+  try {
+    names = (await fs.readdir(dir)).filter((n) => n.endsWith(".md"));
+  } catch {
+    return [];
+  }
+  const items = [];
+  for (const name of names.sort()) {
+    try {
+      const content = await fs.readFile(join2(dir, name), "utf-8");
+      items.push(parse(content, name.replace(/\.md$/, "")));
+    } catch {
+    }
+  }
+  return items;
+}
+async function unprocessedCount(brainDir, slug) {
+  assertSafeSlug(slug);
+  const items = await readItems(brainDir, slug);
+  return items.filter((i) => i.status === "unprocessed" || i.malformed).length;
+}
+async function captureItem(input) {
+  assertSafeSlug(input.slug);
+  const dir = rawDir(input.brainDir, input.slug);
+  const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
+  const capturedBy = input.capturedBy ?? "user";
+  let body = "";
+  let blobBuf;
+  let blobExt = "";
+  let contentType = "";
+  let gistSeed = "";
+  let hashInput = "";
+  if (input.kind === "paste") {
+    body = input.content ?? "";
+    contentType = "text/markdown";
+    gistSeed = body;
+    hashInput = `paste:${body}`;
+  } else if (input.kind === "url") {
+    const url = input.content ?? input.source;
+    body = url;
+    contentType = "text/uri-list";
+    gistSeed = url;
+    hashInput = `url:${url}`;
+  } else {
+    const buf = await fs.readFile(input.source);
+    const binary = isBinary(buf);
+    contentType = contentTypeForFile(input.source, binary);
+    hashInput = `file:${hashContent(buf.toString("binary"))}`;
+    if (binary) {
+      blobBuf = buf;
+      blobExt = extname(input.source) || ".bin";
+      gistSeed = basename(input.source);
+      body = `(binary ${contentType}; original captured as the sibling blob) \u2014 ${basename(input.source)}`;
+    } else {
+      body = buf.toString("utf-8");
+      gistSeed = body;
+    }
+  }
+  const hash = hashContent(hashInput);
+  const existing = (await readItems(input.brainDir, input.slug)).find((i) => i.hash === hash && i.status === "unprocessed");
+  if (existing) {
+    return { id: existing.id, duplicate: true, unprocessed: await unprocessedCount(input.brainDir, input.slug) };
+  }
+  await fs.mkdir(dir, { recursive: true });
+  const sourceSlug = input.kind === "url" ? slugify(input.content ?? input.source) : input.kind === "file" ? slugify(basename(input.source)) : slugify(body);
+  const baseId = `${compactStamp(now)}-${sourceSlug}`;
+  let id = baseId;
+  for (let n = 2; ; n++) {
+    try {
+      await fs.access(join2(dir, `${id}.md`));
+      id = `${baseId}-${n}`;
+    } catch {
+      break;
+    }
+  }
+  const blob = blobBuf ? `${id}${blobExt}` : void 0;
+  if (blobBuf && blob) {
+    const btmp = join2(dir, `${blob}.tmp`);
+    await fs.writeFile(btmp, blobBuf);
+    await fs.rename(btmp, join2(dir, blob));
+  }
+  const gist = gistSeed.replace(/^#\s*/, "").split("\n").map((l) => l.trim()).find(Boolean)?.slice(0, 120) ?? "";
+  const item = {
+    id,
+    source: input.source,
+    captured_at: now,
+    captured_by: capturedBy,
+    content_type: contentType,
+    status: "unprocessed",
+    target_node: input.targetNode,
+    blob,
+    hash,
+    gist,
+    body
+  };
+  const file = join2(dir, `${id}.md`);
+  const tmp = `${file}.tmp`;
+  await fs.writeFile(tmp, serialize(item));
+  await fs.rename(tmp, file);
+  return { id, duplicate: false, unprocessed: await unprocessedCount(input.brainDir, input.slug) };
+}
+
+// src/tools/raw-scan.ts
+var DOC_DIRS = /* @__PURE__ */ new Set(["docs", "doc", "adr", "adrs", "rfc", "rfcs", "spec", "specs", "decisions", ".ai-docs", "notes"]);
+var NAME_INCLUDE = /^(readme|architecture|design|contributing|roadmap)/i;
+var LOW_SIGNAL = /^(changelog|license|licence|code_of_conduct)/i;
+var TEMPLATE_RE = /template/i;
+var SECRET_RE = /(^|\/)\.env|\.pem$|\.key$|id_rsa|secret|credential/i;
+function isHighSignal(relRaw) {
+  const rel = relRaw.replace(/\\/g, "/");
+  const segs = rel.split("/");
+  const file = segs[segs.length - 1];
+  if (!/\.(md|markdown)$/i.test(file)) return false;
+  const baseName = file.replace(/\.(md|markdown)$/i, "");
+  const dirs = segs.slice(0, -1).map((s) => s.toLowerCase());
+  const include = segs.length === 1 || dirs.some((d) => DOC_DIRS.has(d)) || NAME_INCLUDE.test(baseName);
+  if (!include) return false;
+  if (LOW_SIGNAL.test(baseName) || TEMPLATE_RE.test(file)) return false;
+  if (SECRET_RE.test(rel)) return false;
+  return true;
+}
+function scanCap() {
+  const n = parseInt(process.env.SB_SCAN_MAX ?? "", 10);
+  return Number.isFinite(n) && n >= 0 ? n : 50;
+}
+async function scanCandidates(projectRoot) {
+  const root = resolve2(projectRoot);
+  const matches = await glob("**/*.{md,markdown}", { cwd: root, absolute: true, nodir: true, follow: false }).catch(() => []);
+  const within = matches.filter((p) => {
+    const r = resolve2(p);
+    return r === root || r.startsWith(root + sep3);
+  });
+  const highSignal = within.filter((p) => isHighSignal(relative2(root, p)));
+  const kept = filterIgnored(root, highSignal);
+  kept.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
+  return kept;
+}
+async function runScan(projectRoot, brainDir, slug, opts) {
+  assertSafeSlug(slug);
+  const all = await scanCandidates(projectRoot);
+  const cap = scanCap();
+  const candidates = all.slice(0, cap);
+  const overflow = all.slice(cap);
+  const truncated = overflow.length;
+  if (opts.dryRun) return { candidates, overflow, captured: 0, skipped: 0, errored: 0, truncated };
+  let captured = 0, skipped = 0, errored = 0;
+  for (const src of candidates) {
+    try {
+      const r = await captureItem({ brainDir, slug, kind: "file", source: src, capturedBy: "setup-scan" });
+      if (r.duplicate) skipped++;
+      else captured++;
+    } catch {
+      skipped++;
+      errored++;
+    }
+  }
+  return { candidates, overflow, captured, skipped, errored, truncated };
+}
+
+// src/tools/raw-scan-cli.ts
+function resolveSlug(brainDir) {
+  if (process.env.SB_ACTIVE_SLUG) return process.env.SB_ACTIVE_SLUG;
+  try {
+    const pin = readFileSync(join3(brainDir, ".active-session-slug"), "utf-8").trim();
+    if (pin && existsSync(join3(brainDir, "projects", pin, "PROJECT.md"))) return pin;
+  } catch {
+  }
+  const base = basename2(process.cwd());
+  return base && base !== "/" && base !== "." && base !== ".." ? base : void 0;
+}
+async function main() {
+  const brainDir = process.env.BRAIN_DIR || join3(homedir(), ".second-brain");
+  const projectRoot = process.env.SCAN_ROOT || process.cwd();
+  const slug = resolveSlug(brainDir);
+  if (!slug) {
+    console.log("scan: could not resolve the active project. cd into a project.");
+    return;
+  }
+  const dryRun = process.argv.includes("--dry-run");
+  try {
+    const r = await runScan(projectRoot, brainDir, slug, { dryRun });
+    if (dryRun) {
+      console.log(`${r.candidates.length} high-signal doc(s) to capture into ${slug}'s raw inbox:`);
+      for (const p of r.candidates) console.log(`  - ${relative3(projectRoot, p)}`);
+      if (r.candidates.length === 0) console.log("  (no high-signal docs found)");
+      if (r.overflow.length) {
+        console.log(`  \u2026and ${r.overflow.length} more over the SB_SCAN_MAX cap (NOT captured \u2014 raise SB_SCAN_MAX or /second-brain:track them):`);
+        for (const p of r.overflow) console.log(`    \xB7 ${relative3(projectRoot, p)}`);
+      }
+    } else {
+      const more = r.truncated ? `, ${r.truncated} over the cap (raise SB_SCAN_MAX or /second-brain:track them)` : "";
+      const errNote = r.errored ? ` (${r.errored} unreadable)` : "";
+      console.log(`Captured ${r.captured}, skipped ${r.skipped} already-in-inbox${errNote}${more}. Review: /second-brain:capture --list`);
+    }
+  } catch (e) {
+    console.log(`scan error: ${e instanceof Error ? e.message : String(e)}`);
+  }
+}
+main();
diff --git a/mcp/dist/tools/raw-scan-cli.d.ts b/mcp/dist/tools/raw-scan-cli.d.ts
new file mode 100644
index 0000000..bbdcef0
--- /dev/null
+++ b/mcp/dist/tools/raw-scan-cli.d.ts
@@ -0,0 +1,2 @@
+export {};
+//# sourceMappingURL=raw-scan-cli.d.ts.map
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan-cli.d.ts.map b/mcp/dist/tools/raw-scan-cli.d.ts.map
new file mode 100644
index 0000000..f828cf2
--- /dev/null
+++ b/mcp/dist/tools/raw-scan-cli.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"raw-scan-cli.d.ts","sourceRoot":"","sources":["../../src/tools/raw-scan-cli.ts"],"names":[],"mappings":""}
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan-cli.js b/mcp/dist/tools/raw-scan-cli.js
new file mode 100644
index 0000000..75c4ae4
--- /dev/null
+++ b/mcp/dist/tools/raw-scan-cli.js
@@ -0,0 +1,51 @@
+import { homedir } from 'os';
+import { join, basename, relative } from 'path';
+import { existsSync, readFileSync } from 'fs';
+import { runScan } from './raw-scan.js';
+function resolveSlug(brainDir) {
+    if (process.env.SB_ACTIVE_SLUG)
+        return process.env.SB_ACTIVE_SLUG;
+    try {
+        const pin = readFileSync(join(brainDir, '.active-session-slug'), 'utf-8').trim();
+        if (pin && existsSync(join(brainDir, 'projects', pin, 'PROJECT.md')))
+            return pin;
+    }
+    catch { /* no pin */ }
+    const base = basename(process.cwd());
+    return base && base !== '/' && base !== '.' && base !== '..' ? base : undefined;
+}
+async function main() {
+    const brainDir = process.env.BRAIN_DIR || join(homedir(), '.second-brain');
+    const projectRoot = process.env.SCAN_ROOT || process.cwd();
+    const slug = resolveSlug(brainDir);
+    if (!slug) {
+        console.log('scan: could not resolve the active project. cd into a project.');
+        return;
+    }
+    const dryRun = process.argv.includes('--dry-run');
+    try {
+        const r = await runScan(projectRoot, brainDir, slug, { dryRun });
+        if (dryRun) {
+            console.log(`${r.candidates.length} high-signal doc(s) to capture into ${slug}'s raw inbox:`);
+            for (const p of r.candidates)
+                console.log(`  - ${relative(projectRoot, p)}`);
+            if (r.candidates.length === 0)
+                console.log('  (no high-signal docs found)');
+            if (r.overflow.length) {
+                console.log(`  …and ${r.overflow.length} more over the SB_SCAN_MAX cap (NOT captured — raise SB_SCAN_MAX or /second-brain:track them):`);
+                for (const p of r.overflow)
+                    console.log(`    · ${relative(projectRoot, p)}`);
+            }
+        }
+        else {
+            const more = r.truncated ? `, ${r.truncated} over the cap (raise SB_SCAN_MAX or /second-brain:track them)` : '';
+            const errNote = r.errored ? ` (${r.errored} unreadable)` : '';
+            console.log(`Captured ${r.captured}, skipped ${r.skipped} already-in-inbox${errNote}${more}. Review: /second-brain:capture --list`);
+        }
+    }
+    catch (e) {
+        console.log(`scan error: ${e instanceof Error ? e.message : String(e)}`);
+    }
+}
+main();
+//# sourceMappingURL=raw-scan-cli.js.map
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan-cli.js.map b/mcp/dist/tools/raw-scan-cli.js.map
new file mode 100644
index 0000000..bab3239
--- /dev/null
+++ b/mcp/dist/tools/raw-scan-cli.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"raw-scan-cli.js","sourceRoot":"","sources":["../../src/tools/raw-scan-cli.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan.d.ts b/mcp/dist/tools/raw-scan.d.ts
new file mode 100644
index 0000000..ed8180c
--- /dev/null
+++ b/mcp/dist/tools/raw-scan.d.ts
@@ -0,0 +1,24 @@
+/** A repo-relative markdown path is high-signal iff it matches an include rule and no denylist.
+ *  Normalizes separators first: `path.relative` emits OS-native separators, so a Windows path
+ *  `docs\adr\x.md` must be split on `\` too or rule 2 / the secret anchor silently misfire. */
+export declare function isHighSignal(relRaw: string): boolean;
+/** Max items captured per scan (SB_SCAN_MAX, default 50). */
+export declare function scanCap(): number;
+/** Walk the repo for high-signal markdown docs (junk + git-ignored dropped). Sorted, uncapped. */
+export declare function scanCandidates(projectRoot: string): Promise;
+export interface ScanResult {
+    candidates: string[];
+    overflow: string[];
+    captured: number;
+    skipped: number;
+    errored: number;
+    truncated: number;
+}
+/** Scan + (unless dryRun) capture each candidate into the raw inbox as `setup-scan` material.
+ *  Dedup is unprocessed-scoped (captureItem): re-running re-captures only new/changed docs. Once
+ *  SP-4 marks an item `processed`, re-capture policy for that doc is SP-4's concern (it owns the
+ *  processed lifecycle), so this scan intentionally does not dedup against processed items. */
+export declare function runScan(projectRoot: string, brainDir: string, slug: string, opts: {
+    dryRun?: boolean;
+}): Promise;
+//# sourceMappingURL=raw-scan.d.ts.map
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan.d.ts.map b/mcp/dist/tools/raw-scan.d.ts.map
new file mode 100644
index 0000000..a8dbae7
--- /dev/null
+++ b/mcp/dist/tools/raw-scan.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"raw-scan.d.ts","sourceRoot":"","sources":["../../src/tools/raw-scan.ts"],"names":[],"mappings":"AAWA;;+FAE+F;AAC/F,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAcpD;AAED,6DAA6D;AAC7D,wBAAgB,OAAO,IAAI,MAAM,CAGhC;AAED,kGAAkG;AAClG,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAS3E;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;+FAG+F;AAC/F,wBAAsB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EACnD,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAgB7E"}
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan.js b/mcp/dist/tools/raw-scan.js
new file mode 100644
index 0000000..e23719b
--- /dev/null
+++ b/mcp/dist/tools/raw-scan.js
@@ -0,0 +1,77 @@
+import { resolve, relative, sep } from 'path';
+import { glob } from 'glob';
+import { filterIgnored, assertSafeSlug } from './doc-sources.js';
+import { captureItem } from './raw-inbox.js';
+const DOC_DIRS = new Set(['docs', 'doc', 'adr', 'adrs', 'rfc', 'rfcs', 'spec', 'specs', 'decisions', '.ai-docs', 'notes']);
+const NAME_INCLUDE = /^(readme|architecture|design|contributing|roadmap)/i; // basename (sans ext)
+const LOW_SIGNAL = /^(changelog|license|licence|code_of_conduct)/i; // basename (sans ext)
+const TEMPLATE_RE = /template/i; // basename
+const SECRET_RE = /(^|\/)\.env|\.pem$|\.key$|id_rsa|secret|credential/i; // full rel path
+/** A repo-relative markdown path is high-signal iff it matches an include rule and no denylist.
+ *  Normalizes separators first: `path.relative` emits OS-native separators, so a Windows path
+ *  `docs\adr\x.md` must be split on `\` too or rule 2 / the secret anchor silently misfire. */
+export function isHighSignal(relRaw) {
+    const rel = relRaw.replace(/\\/g, '/');
+    const segs = rel.split('/');
+    const file = segs[segs.length - 1];
+    if (!/\.(md|markdown)$/i.test(file))
+        return false;
+    const baseName = file.replace(/\.(md|markdown)$/i, '');
+    const dirs = segs.slice(0, -1).map(s => s.toLowerCase());
+    const include = segs.length === 1 // rule 1: root-level *.md
+        || dirs.some(d => DOC_DIRS.has(d)) // rule 2: a directory segment is a doc dir
+        || NAME_INCLUDE.test(baseName); // rule 3: high-signal basename anywhere
+    if (!include)
+        return false;
+    if (LOW_SIGNAL.test(baseName) || TEMPLATE_RE.test(file))
+        return false; // low-signal
+    if (SECRET_RE.test(rel))
+        return false; // secret defense-in-depth
+    return true;
+}
+/** Max items captured per scan (SB_SCAN_MAX, default 50). */
+export function scanCap() {
+    const n = parseInt(process.env.SB_SCAN_MAX ?? '', 10);
+    return Number.isFinite(n) && n >= 0 ? n : 50;
+}
+/** Walk the repo for high-signal markdown docs (junk + git-ignored dropped). Sorted, uncapped. */
+export async function scanCandidates(projectRoot) {
+    const root = resolve(projectRoot);
+    // follow:false → never traverse symlinked directories (a symlink loop would otherwise hang the scan).
+    const matches = await glob('**/*.{md,markdown}', { cwd: root, absolute: true, nodir: true, follow: false }).catch(() => []);
+    const within = matches.filter(p => { const r = resolve(p); return r === root || r.startsWith(root + sep); });
+    const highSignal = within.filter(p => isHighSignal(relative(root, p)));
+    const kept = filterIgnored(root, highSignal); // drops JUNK_DIRS + `git check-ignore` paths
+    kept.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); // byte-stable, locale-independent
+    return kept;
+}
+/** Scan + (unless dryRun) capture each candidate into the raw inbox as `setup-scan` material.
+ *  Dedup is unprocessed-scoped (captureItem): re-running re-captures only new/changed docs. Once
+ *  SP-4 marks an item `processed`, re-capture policy for that doc is SP-4's concern (it owns the
+ *  processed lifecycle), so this scan intentionally does not dedup against processed items. */
+export async function runScan(projectRoot, brainDir, slug, opts) {
+    assertSafeSlug(slug);
+    const all = await scanCandidates(projectRoot);
+    const cap = scanCap();
+    const candidates = all.slice(0, cap);
+    const overflow = all.slice(cap);
+    const truncated = overflow.length;
+    if (opts.dryRun)
+        return { candidates, overflow, captured: 0, skipped: 0, errored: 0, truncated };
+    let captured = 0, skipped = 0, errored = 0;
+    for (const src of candidates) {
+        try {
+            const r = await captureItem({ brainDir, slug, kind: 'file', source: src, capturedBy: 'setup-scan' });
+            if (r.duplicate)
+                skipped++;
+            else
+                captured++;
+        }
+        catch {
+            skipped++;
+            errored++;
+        } // unreadable → skip, never abort the scan
+    }
+    return { candidates, overflow, captured, skipped, errored, truncated };
+}
+//# sourceMappingURL=raw-scan.js.map
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan.js.map b/mcp/dist/tools/raw-scan.js.map
new file mode 100644
index 0000000..44576c7
--- /dev/null
+++ b/mcp/dist/tools/raw-scan.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"raw-scan.js","sourceRoot":"","sources":["../../src/tools/raw-scan.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan.test.d.ts b/mcp/dist/tools/raw-scan.test.d.ts
new file mode 100644
index 0000000..93f2147
--- /dev/null
+++ b/mcp/dist/tools/raw-scan.test.d.ts
@@ -0,0 +1,2 @@
+export {};
+//# sourceMappingURL=raw-scan.test.d.ts.map
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan.test.d.ts.map b/mcp/dist/tools/raw-scan.test.d.ts.map
new file mode 100644
index 0000000..b521bdb
--- /dev/null
+++ b/mcp/dist/tools/raw-scan.test.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"raw-scan.test.d.ts","sourceRoot":"","sources":["../../src/tools/raw-scan.test.ts"],"names":[],"mappings":""}
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan.test.js b/mcp/dist/tools/raw-scan.test.js
new file mode 100644
index 0000000..1eb148f
--- /dev/null
+++ b/mcp/dist/tools/raw-scan.test.js
@@ -0,0 +1,80 @@
+import { describe, it, expect } from 'vitest';
+import { promises as fs } from 'fs';
+import { join } from 'path';
+import { tmpdir } from 'os';
+import { scanCandidates, runScan, scanCap, isHighSignal } from './raw-scan.js';
+import { listItems } from './raw-inbox.js';
+/** Build a temp repo with a known file set; returns its root. NOT a git repo (junk-filter only). */
+async function repo() {
+    const root = await fs.mkdtemp(join(tmpdir(), 'scan-'));
+    // Distinct body per file — captureItem dedups by content hash, so identical bodies
+    // would collapse to one captured item and break the capture-count assertion.
+    const w = async (rel, body = `# ${rel}\nunique content for ${rel}`) => {
+        await fs.mkdir(join(root, rel, '..'), { recursive: true });
+        await fs.writeFile(join(root, rel), body);
+    };
+    await w('README.md'); // rule 1 (root)
+    await w('docs/guide.md'); // rule 2 (docs dir)
+    await w('docs/adr/ADR-001.md'); // rule 2 (adr dir)
+    await w('src/DESIGN.md'); // rule 3 (basename)
+    await w('src/components/notes.md'); // EXCLUDED: file named notes, not a notes/ dir
+    await w('CHANGELOG.md'); // EXCLUDED: low-signal denylist
+    await w('docs/credentials.md'); // EXCLUDED: secret denylist
+    await w('node_modules/pkg/README.md'); // EXCLUDED: junk dir
+    return root;
+}
+async function brain() {
+    const brainDir = await fs.mkdtemp(join(tmpdir(), 'scan-brain-'));
+    const slug = 'demo';
+    await fs.mkdir(join(brainDir, 'projects', slug), { recursive: true });
+    return { brainDir, slug };
+}
+function rels(root, paths) {
+    return paths.map(p => p.slice(root.length + 1).split('\\').join('/'));
+}
+describe('raw-scan', () => {
+    it('curates high-signal docs and excludes notes/changelog/secret/junk', async () => {
+        const root = await repo();
+        const got = rels(root, await scanCandidates(root)).sort();
+        expect(got).toEqual(['README.md', 'docs/adr/ADR-001.md', 'docs/guide.md', 'src/DESIGN.md']);
+    });
+    it('isHighSignal handles Windows backslash paths (cross-OS) — path.relative emits native sep', () => {
+        expect(isHighSignal('docs\\adr\\ADR-001.md')).toBe(true); // rule 2 (adr dir), backslashes
+        expect(isHighSignal('src\\components\\notes.md')).toBe(false); // NOT root, NOT a notes/ dir
+        expect(isHighSignal('config\\.env.md')).toBe(false); // secret denylist (.env) must still fire
+        expect(isHighSignal('README.md')).toBe(true); // root-level
+    });
+    it('scanCap reads SB_SCAN_MAX (default 50)', () => {
+        delete process.env.SB_SCAN_MAX;
+        expect(scanCap()).toBe(50);
+        process.env.SB_SCAN_MAX = '2';
+        expect(scanCap()).toBe(2);
+        delete process.env.SB_SCAN_MAX;
+    });
+    it('dryRun previews (capped) and writes nothing', async () => {
+        const root = await repo();
+        const { brainDir, slug } = await brain();
+        process.env.SB_SCAN_MAX = '2';
+        const r = await runScan(root, brainDir, slug, { dryRun: true });
+        delete process.env.SB_SCAN_MAX;
+        expect(r.candidates).toHaveLength(2);
+        expect(r.overflow).toHaveLength(2); // the over-cap paths are surfaced (not hidden) for preview
+        expect(r.truncated).toBe(2); // 4 high-signal, cap 2
+        expect(r.captured).toBe(0);
+        expect(await listItems(brainDir, slug)).toHaveLength(0); // nothing written
+    });
+    it('captures survivors stamped captured_by: setup-scan, and dedups on re-run', async () => {
+        const root = await repo();
+        const { brainDir, slug } = await brain();
+        const r1 = await runScan(root, brainDir, slug, {});
+        expect(r1.captured).toBe(4);
+        const items = await listItems(brainDir, slug);
+        expect(items).toHaveLength(4);
+        expect(items.every(i => i.captured_by === 'setup-scan')).toBe(true);
+        const r2 = await runScan(root, brainDir, slug, {}); // re-run: unchanged → all skipped
+        expect(r2.captured).toBe(0);
+        expect(r2.skipped).toBe(4);
+        expect(await listItems(brainDir, slug)).toHaveLength(4);
+    });
+});
+//# sourceMappingURL=raw-scan.test.js.map
\ No newline at end of file
diff --git a/mcp/dist/tools/raw-scan.test.js.map b/mcp/dist/tools/raw-scan.test.js.map
new file mode 100644
index 0000000..46c6354
--- /dev/null
+++ b/mcp/dist/tools/raw-scan.test.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"raw-scan.test.js","sourceRoot":"","sources":["../../src/tools/raw-scan.test.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/mcp/package.json b/mcp/package.json
index 0e181ff..383ec21 100644
--- a/mcp/package.json
+++ b/mcp/package.json
@@ -6,7 +6,7 @@
   "main": "dist/server.js",
   "scripts": {
     "build": "tsc && npm run bundle",
-    "bundle": "esbuild src/server.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/server.bundle.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\" && esbuild src/tools/knowledge-reindex.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/knowledge-reindex.bundle.js && esbuild src/tools/knowledge-validate.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/knowledge-validate.bundle.js && esbuild src/tools/knowledge-search-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/knowledge-search-cli.bundle.js && esbuild src/tools/episodic-index-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/episodic-index-cli.bundle.js && esbuild src/tools/episodic-search-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/episodic-search-cli.bundle.js && esbuild src/cli/sb-entry.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/cli/sb-entry.bundle.js && esbuild src/cli/persona-think-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/cli/persona-think-cli.bundle.js && esbuild src/tools/doc-sources-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/doc-sources-cli.bundle.js && esbuild src/tools/doc-sources-config-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/doc-sources-config-cli.bundle.js && esbuild src/tools/graph-neighbors-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/graph-neighbors-cli.bundle.js && esbuild src/tools/graph-cluster-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/graph-cluster-cli.bundle.js && esbuild src/tools/ai-block-render-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/ai-block-render-cli.bundle.js && esbuild src/tools/raw-capture-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/raw-capture-cli.bundle.js",
+    "bundle": "esbuild src/server.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/server.bundle.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\" && esbuild src/tools/knowledge-reindex.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/knowledge-reindex.bundle.js && esbuild src/tools/knowledge-validate.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/knowledge-validate.bundle.js && esbuild src/tools/knowledge-search-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/knowledge-search-cli.bundle.js && esbuild src/tools/episodic-index-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/episodic-index-cli.bundle.js && esbuild src/tools/episodic-search-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/episodic-search-cli.bundle.js && esbuild src/cli/sb-entry.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/cli/sb-entry.bundle.js && esbuild src/cli/persona-think-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/cli/persona-think-cli.bundle.js && esbuild src/tools/doc-sources-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/doc-sources-cli.bundle.js && esbuild src/tools/doc-sources-config-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/doc-sources-config-cli.bundle.js && esbuild src/tools/graph-neighbors-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/graph-neighbors-cli.bundle.js && esbuild src/tools/graph-cluster-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/graph-cluster-cli.bundle.js && esbuild src/tools/ai-block-render-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/ai-block-render-cli.bundle.js && esbuild src/tools/raw-capture-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/raw-capture-cli.bundle.js && esbuild src/tools/raw-scan-cli.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/tools/raw-scan-cli.bundle.js",
     "start": "node dist/server.bundle.js",
     "dev": "tsc --watch",
     "test": "vitest run"
diff --git a/mcp/src/tools/doc-sources.ts b/mcp/src/tools/doc-sources.ts
index 466173a..1b47251 100644
--- a/mcp/src/tools/doc-sources.ts
+++ b/mcp/src/tools/doc-sources.ts
@@ -49,7 +49,7 @@ export async function readConfig(brainDir: string, slug: string): Promise !relative(projectRoot, p).split('/').some((seg) => JUNK_DIRS.has(seg)));
   if (nonJunk.length === 0) return [];
   const rels = nonJunk.map((p) => relative(projectRoot, p));
diff --git a/mcp/src/tools/raw-scan-cli.ts b/mcp/src/tools/raw-scan-cli.ts
new file mode 100644
index 0000000..660e039
--- /dev/null
+++ b/mcp/src/tools/raw-scan-cli.ts
@@ -0,0 +1,42 @@
+import { homedir } from 'os';
+import { join, basename, relative } from 'path';
+import { existsSync, readFileSync } from 'fs';
+import { runScan } from './raw-scan.js';
+
+function resolveSlug(brainDir: string): string | undefined {
+  if (process.env.SB_ACTIVE_SLUG) return process.env.SB_ACTIVE_SLUG;
+  try {
+    const pin = readFileSync(join(brainDir, '.active-session-slug'), 'utf-8').trim();
+    if (pin && existsSync(join(brainDir, 'projects', pin, 'PROJECT.md'))) return pin;
+  } catch { /* no pin */ }
+  const base = basename(process.cwd());
+  return base && base !== '/' && base !== '.' && base !== '..' ? base : undefined;
+}
+
+async function main(): Promise {
+  const brainDir = process.env.BRAIN_DIR || join(homedir(), '.second-brain');
+  const projectRoot = process.env.SCAN_ROOT || process.cwd();
+  const slug = resolveSlug(brainDir);
+  if (!slug) { console.log('scan: could not resolve the active project. cd into a project.'); return; }
+  const dryRun = process.argv.includes('--dry-run');
+  try {
+    const r = await runScan(projectRoot, brainDir, slug, { dryRun });
+    if (dryRun) {
+      console.log(`${r.candidates.length} high-signal doc(s) to capture into ${slug}'s raw inbox:`);
+      for (const p of r.candidates) console.log(`  - ${relative(projectRoot, p)}`);
+      if (r.candidates.length === 0) console.log('  (no high-signal docs found)');
+      if (r.overflow.length) {
+        console.log(`  …and ${r.overflow.length} more over the SB_SCAN_MAX cap (NOT captured — raise SB_SCAN_MAX or /second-brain:track them):`);
+        for (const p of r.overflow) console.log(`    · ${relative(projectRoot, p)}`);
+      }
+    } else {
+      const more = r.truncated ? `, ${r.truncated} over the cap (raise SB_SCAN_MAX or /second-brain:track them)` : '';
+      const errNote = r.errored ? ` (${r.errored} unreadable)` : '';
+      console.log(`Captured ${r.captured}, skipped ${r.skipped} already-in-inbox${errNote}${more}. Review: /second-brain:capture --list`);
+    }
+  } catch (e) {
+    console.log(`scan error: ${e instanceof Error ? e.message : String(e)}`);
+  }
+}
+
+main();
diff --git a/mcp/src/tools/raw-scan.test.ts b/mcp/src/tools/raw-scan.test.ts
new file mode 100644
index 0000000..4464f97
--- /dev/null
+++ b/mcp/src/tools/raw-scan.test.ts
@@ -0,0 +1,87 @@
+import { describe, it, expect } from 'vitest';
+import { promises as fs } from 'fs';
+import { join } from 'path';
+import { tmpdir } from 'os';
+import { scanCandidates, runScan, scanCap, isHighSignal } from './raw-scan.js';
+import { listItems } from './raw-inbox.js';
+
+/** Build a temp repo with a known file set; returns its root. NOT a git repo (junk-filter only). */
+async function repo(): Promise {
+  const root = await fs.mkdtemp(join(tmpdir(), 'scan-'));
+  // Distinct body per file — captureItem dedups by content hash, so identical bodies
+  // would collapse to one captured item and break the capture-count assertion.
+  const w = async (rel: string, body = `# ${rel}\nunique content for ${rel}`) => {
+    await fs.mkdir(join(root, rel, '..'), { recursive: true });
+    await fs.writeFile(join(root, rel), body);
+  };
+  await w('README.md');                       // rule 1 (root)
+  await w('docs/guide.md');                    // rule 2 (docs dir)
+  await w('docs/adr/ADR-001.md');              // rule 2 (adr dir)
+  await w('src/DESIGN.md');                    // rule 3 (basename)
+  await w('src/components/notes.md');          // EXCLUDED: file named notes, not a notes/ dir
+  await w('CHANGELOG.md');                      // EXCLUDED: low-signal denylist
+  await w('docs/credentials.md');              // EXCLUDED: secret denylist
+  await w('node_modules/pkg/README.md');       // EXCLUDED: junk dir
+  return root;
+}
+
+async function brain(): Promise<{ brainDir: string; slug: string }> {
+  const brainDir = await fs.mkdtemp(join(tmpdir(), 'scan-brain-'));
+  const slug = 'demo';
+  await fs.mkdir(join(brainDir, 'projects', slug), { recursive: true });
+  return { brainDir, slug };
+}
+
+function rels(root: string, paths: string[]): string[] {
+  return paths.map(p => p.slice(root.length + 1).split('\\').join('/'));
+}
+
+describe('raw-scan', () => {
+  it('curates high-signal docs and excludes notes/changelog/secret/junk', async () => {
+    const root = await repo();
+    const got = rels(root, await scanCandidates(root)).sort();
+    expect(got).toEqual(['README.md', 'docs/adr/ADR-001.md', 'docs/guide.md', 'src/DESIGN.md']);
+  });
+
+  it('isHighSignal handles Windows backslash paths (cross-OS) — path.relative emits native sep', () => {
+    expect(isHighSignal('docs\\adr\\ADR-001.md')).toBe(true);    // rule 2 (adr dir), backslashes
+    expect(isHighSignal('src\\components\\notes.md')).toBe(false); // NOT root, NOT a notes/ dir
+    expect(isHighSignal('config\\.env.md')).toBe(false);          // secret denylist (.env) must still fire
+    expect(isHighSignal('README.md')).toBe(true);                 // root-level
+  });
+
+  it('scanCap reads SB_SCAN_MAX (default 50)', () => {
+    delete process.env.SB_SCAN_MAX;
+    expect(scanCap()).toBe(50);
+    process.env.SB_SCAN_MAX = '2';
+    expect(scanCap()).toBe(2);
+    delete process.env.SB_SCAN_MAX;
+  });
+
+  it('dryRun previews (capped) and writes nothing', async () => {
+    const root = await repo();
+    const { brainDir, slug } = await brain();
+    process.env.SB_SCAN_MAX = '2';
+    const r = await runScan(root, brainDir, slug, { dryRun: true });
+    delete process.env.SB_SCAN_MAX;
+    expect(r.candidates).toHaveLength(2);
+    expect(r.overflow).toHaveLength(2);   // the over-cap paths are surfaced (not hidden) for preview
+    expect(r.truncated).toBe(2);          // 4 high-signal, cap 2
+    expect(r.captured).toBe(0);
+    expect(await listItems(brainDir, slug)).toHaveLength(0); // nothing written
+  });
+
+  it('captures survivors stamped captured_by: setup-scan, and dedups on re-run', async () => {
+    const root = await repo();
+    const { brainDir, slug } = await brain();
+    const r1 = await runScan(root, brainDir, slug, {});
+    expect(r1.captured).toBe(4);
+    const items = await listItems(brainDir, slug);
+    expect(items).toHaveLength(4);
+    expect(items.every(i => i.captured_by === 'setup-scan')).toBe(true);
+    const r2 = await runScan(root, brainDir, slug, {});   // re-run: unchanged → all skipped
+    expect(r2.captured).toBe(0);
+    expect(r2.skipped).toBe(4);
+    expect(await listItems(brainDir, slug)).toHaveLength(4);
+  });
+});
diff --git a/mcp/src/tools/raw-scan.ts b/mcp/src/tools/raw-scan.ts
new file mode 100644
index 0000000..d7f6f96
--- /dev/null
+++ b/mcp/src/tools/raw-scan.ts
@@ -0,0 +1,79 @@
+import { resolve, relative, sep } from 'path';
+import { glob } from 'glob';
+import { filterIgnored, assertSafeSlug } from './doc-sources.js';
+import { captureItem } from './raw-inbox.js';
+
+const DOC_DIRS = new Set(['docs', 'doc', 'adr', 'adrs', 'rfc', 'rfcs', 'spec', 'specs', 'decisions', '.ai-docs', 'notes']);
+const NAME_INCLUDE = /^(readme|architecture|design|contributing|roadmap)/i;  // basename (sans ext)
+const LOW_SIGNAL = /^(changelog|license|licence|code_of_conduct)/i;          // basename (sans ext)
+const TEMPLATE_RE = /template/i;                                              // basename
+const SECRET_RE = /(^|\/)\.env|\.pem$|\.key$|id_rsa|secret|credential/i;      // full rel path
+
+/** A repo-relative markdown path is high-signal iff it matches an include rule and no denylist.
+ *  Normalizes separators first: `path.relative` emits OS-native separators, so a Windows path
+ *  `docs\adr\x.md` must be split on `\` too or rule 2 / the secret anchor silently misfire. */
+export function isHighSignal(relRaw: string): boolean {
+  const rel = relRaw.replace(/\\/g, '/');
+  const segs = rel.split('/');
+  const file = segs[segs.length - 1];
+  if (!/\.(md|markdown)$/i.test(file)) return false;
+  const baseName = file.replace(/\.(md|markdown)$/i, '');
+  const dirs = segs.slice(0, -1).map(s => s.toLowerCase());
+  const include = segs.length === 1                       // rule 1: root-level *.md
+    || dirs.some(d => DOC_DIRS.has(d))                    // rule 2: a directory segment is a doc dir
+    || NAME_INCLUDE.test(baseName);                       // rule 3: high-signal basename anywhere
+  if (!include) return false;
+  if (LOW_SIGNAL.test(baseName) || TEMPLATE_RE.test(file)) return false;  // low-signal
+  if (SECRET_RE.test(rel)) return false;                                  // secret defense-in-depth
+  return true;
+}
+
+/** Max items captured per scan (SB_SCAN_MAX, default 50). */
+export function scanCap(): number {
+  const n = parseInt(process.env.SB_SCAN_MAX ?? '', 10);
+  return Number.isFinite(n) && n >= 0 ? n : 50;
+}
+
+/** Walk the repo for high-signal markdown docs (junk + git-ignored dropped). Sorted, uncapped. */
+export async function scanCandidates(projectRoot: string): Promise {
+  const root = resolve(projectRoot);
+  // follow:false → never traverse symlinked directories (a symlink loop would otherwise hang the scan).
+  const matches = await glob('**/*.{md,markdown}', { cwd: root, absolute: true, nodir: true, follow: false }).catch(() => [] as string[]);
+  const within = matches.filter(p => { const r = resolve(p); return r === root || r.startsWith(root + sep); });
+  const highSignal = within.filter(p => isHighSignal(relative(root, p)));
+  const kept = filterIgnored(root, highSignal);  // drops JUNK_DIRS + `git check-ignore` paths
+  kept.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));  // byte-stable, locale-independent
+  return kept;
+}
+
+export interface ScanResult {
+  candidates: string[];   // the (capped) set that will be / was captured
+  overflow: string[];     // candidates beyond the cap (shown in the dry-run preview so nothing is hidden)
+  captured: number;
+  skipped: number;        // already-in-inbox (dedup) OR unreadable at capture time
+  errored: number;        // subset of skipped that failed to read (kept separate so the CLI can be honest)
+  truncated: number;      // === overflow.length
+}
+
+/** Scan + (unless dryRun) capture each candidate into the raw inbox as `setup-scan` material.
+ *  Dedup is unprocessed-scoped (captureItem): re-running re-captures only new/changed docs. Once
+ *  SP-4 marks an item `processed`, re-capture policy for that doc is SP-4's concern (it owns the
+ *  processed lifecycle), so this scan intentionally does not dedup against processed items. */
+export async function runScan(projectRoot: string, brainDir: string, slug: string,
+                              opts: { dryRun?: boolean }): Promise {
+  assertSafeSlug(slug);
+  const all = await scanCandidates(projectRoot);
+  const cap = scanCap();
+  const candidates = all.slice(0, cap);
+  const overflow = all.slice(cap);
+  const truncated = overflow.length;
+  if (opts.dryRun) return { candidates, overflow, captured: 0, skipped: 0, errored: 0, truncated };
+  let captured = 0, skipped = 0, errored = 0;
+  for (const src of candidates) {
+    try {
+      const r = await captureItem({ brainDir, slug, kind: 'file', source: src, capturedBy: 'setup-scan' });
+      if (r.duplicate) skipped++; else captured++;
+    } catch { skipped++; errored++; }  // unreadable → skip, never abort the scan
+  }
+  return { candidates, overflow, captured, skipped, errored, truncated };
+}
diff --git a/skills/setup/SKILL.md b/skills/setup/SKILL.md
index 9c2d58c..6502368 100644
--- a/skills/setup/SKILL.md
+++ b/skills/setup/SKILL.md
@@ -3,7 +3,7 @@ name: setup
 description: Scaffold the v1.0 hot tier — USER.md, projects//PROJECT.md, projects.jsonl — for the active repo. Idempotent.
 user-invocable: true
 disable-model-invocation: true
-allowed-tools: Read Write Edit Bash(git rev-parse:*) Bash(basename *) Bash(date *) Bash(test *) Bash(jq *) Bash(mkdir *) Bash(grep *) Bash(sed *) Bash(awk *) Bash(head *) Bash(cat *) Bash(wc *)
+allowed-tools: Read Write Edit Bash(git rev-parse:*) Bash(basename *) Bash(date *) Bash(test *) Bash(jq *) Bash(mkdir *) Bash(grep *) Bash(sed *) Bash(awk *) Bash(head *) Bash(cat *) Bash(wc *) Bash(node *)
 ---
 
 # Setup
@@ -137,7 +137,36 @@ fi
 
 The persona-card is user-owned content. The persona reads it; nothing in the plugin should ever rewrite it automatically. The user edits it directly when their role, style, or preferences change.
 
-### 6. Confirm
+### 6. Deep-scan the repo into the raw inbox (preview, then confirm)
+
+Seed this project's KB by capturing its **high-signal docs** (README, `docs/`, ADRs,
+`DESIGN.md`, …) into the raw inbox, where the maintainer later refines them into wiki
+notes. Skipped entirely if `SB_SCAN_SKIP=1`. Curation reuses the doc-sources junk +
+git-ignore filtering and a low-signal/secret denylist; the inbox dedups by content hash,
+so re-running setup only captures new or changed docs.
+
+```bash
+if [ "${SB_SCAN_SKIP:-0}" != "1" ]; then
+  SCAN_CLI="${CLAUDE_PLUGIN_ROOT}/mcp/dist/tools/raw-scan-cli.bundle.js"
+  SCAN_ROOT_DIR=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
+  SCAN_ROOT="$SCAN_ROOT_DIR" node "$SCAN_CLI" --dry-run
+fi
+```
+
+Show the preview list. This writes nothing yet. **Ask the user to confirm** capturing
+these into the raw inbox (an impactful action). On a yes (recompute the paths — each
+bash block runs as a separate shell, so vars from the preview block do not persist):
+
+```bash
+SCAN_CLI="${CLAUDE_PLUGIN_ROOT}/mcp/dist/tools/raw-scan-cli.bundle.js"
+SCAN_ROOT_DIR=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
+SCAN_ROOT="$SCAN_ROOT_DIR" node "$SCAN_CLI"
+```
+
+Report the captured/skipped counts and point the user to `/second-brain:capture --list`.
+If the preview was empty, say there were no high-signal docs to seed and move on.
+
+### 7. Confirm
 
 Print byte counts of `USER.md` and `PROJECT.md` and the combined total. Verify combined < ~3200 bytes (≈ 800-token hot-tier cap):
 
diff --git a/skills/upgrade/SKILL.md b/skills/upgrade/SKILL.md
index 214c06e..d250c8e 100644
--- a/skills/upgrade/SKILL.md
+++ b/skills/upgrade/SKILL.md
@@ -35,6 +35,7 @@ Each migration is identified by its target version. Run only migrations whose ta
 | To version | Migration | Idempotent check |
 |---|---|---|
 | **vector-deps health** (re-runs every upgrade) | Smoke-import `@huggingface/transformers` from `$CLAUDE_PLUGIN_ROOT/mcp`. On failure, run `bash $CLAUDE_PLUGIN_ROOT/bin/install-vector-deps.sh`. **Why**: mcp bundles mark `@huggingface/transformers` as esbuild `--external` because its native binaries (`onnxruntime-node`, `sharp`) can't be statically packed. A plugin cache refresh ships `dist/` but does not touch `node_modules/`, so vector search silently degrades to text-only on every fresh install or cache wipe. Without this gate the user sees no error — just empty embeddings and degraded recall. Idempotent: the script is a no-op when the package and import smoke-check both succeed. | `cd "$CLAUDE_PLUGIN_ROOT/mcp" && node --input-type=module -e 'await import("@huggingface/transformers"); console.log("ok")' >/dev/null 2>&1` — if exit 0, skip. Otherwise run `bash "$CLAUDE_PLUGIN_ROOT/bin/install-vector-deps.sh"`; report the ~70MB network requirement before installing. |
+| **0.24.12** | KB **setup deep-scan** (SP-3 of the consolidation vision; design `docs/specs/2026-06-03-setup-deep-scan-design`, plan `docs/plans/2026-06-03-setup-deep-scan`). `/second-brain:setup` gains a step that **seeds the raw inbox** from the repo's existing high-signal docs, so a fresh project starts with material for the maintainer (SP-4) to refine into wiki nodes. New pure `mcp/src/tools/raw-scan.ts`: `scanCandidates` globs `**/*.{md,markdown}` → a curation heuristic (include iff root-level `*.md`, OR a *directory* segment is `docs/doc/adr(s)/rfc(s)/spec(s)/decisions/.ai-docs/notes`, OR basename matches `README|ARCHITECTURE|DESIGN|CONTRIBUTING|ROADMAP`) → low-signal (`CHANGELOG|LICENSE|CODE_OF_CONDUCT|*template*`) + **secret** (`.env|*.pem|*.key|id_rsa|*secret*|*credential*`) denylists → reused `doc-sources.filterIgnored` (junk dirs + `git check-ignore`) → byte-stable sort; `runScan` captures survivors into the per-project raw inbox via SP-2 `captureItem({capturedBy:'setup-scan'})`, capped at `SB_SCAN_MAX` (default 50), content-hash **dedup** on re-run. The setup step **previews** (`raw-scan-cli --dry-run`, listing both the to-capture set and the over-cap remainder) then captures only on explicit confirm; kill switch `SB_SCAN_SKIP=1`. Cross-OS hardened (deep-review): separator-normalized curation (`path.relative` emits native sep — Windows `docs\\adr\\x.md` no longer collapses to one segment), `glob follow:false` (no symlink-loop hang), and the capture fence recomputes its shell vars (skill fences are separate shells). **No new MCP server tool, no SP-2 schema change** (`setup-scan` was already a valid `captured_by`; server stays 2.6.4). New `raw-scan.ts` (5 vitest incl. a Windows-path case) + `test-setup-scan.sh` (git-ignore e2e + self-contained-capture guard). **Additive + opt-in:** the scan only writes on the user's confirm; declining (or `SB_SCAN_SKIP=1`) leaves setup unchanged. SP-4 maintainer drain (raw→nodes) deferred. | No precondition — bumping the marker is sufficient. Re-run `/second-brain:setup` in a project to seed its inbox from existing docs; `SB_SCAN_SKIP=1` to skip the scan, `SB_SCAN_MAX=N` to change the cap. |
 | **0.24.11** | KB **raw inbox** (SP-2 of the consolidation vision; design `docs/specs/2026-06-03-raw-inbox-design`, plan `docs/plans/2026-06-03-raw-inbox`). A per-project staging area `~/.second-brain/projects//raw/` for **unprocessed** material — dropped with provenance, held **out** of `knowledge_search`, surfaced as a backlog, and later refined into wiki nodes by the maintainer (SP-4, deferred). New user-invocable **`/second-brain:capture`**: `` copies a file (binary → blob sidecar); `` → an **offline pointer** (no fetch); inline text / `paste` → markdown; `--node ` pre-records provenance; `--list` / `--discard `; idempotent via content-hash. Each item is `raw/.md` with flat-YAML provenance frontmatter (`status: unprocessed|processed|discarded`); the work-list is **derived** by scanning status (no index file — drift-free). New `raw` group in `kb-schema.json` (dual-reader, `searchable:false`). `session-load.sh` surfaces a backlog banner (open = unprocessed + malformed; mawk-free `total − closed`), kill switch `SB_RAW_INBOX=off`. Security-hardened (deep-review): `setStatus` rejects path-traversal ids (a `--discard ../../wiki/page` can't rewrite an arbitrary `.md`), and every frontmatter value is newline-sanitized (no `status:` injection). **No MCP server tool change** — capture rides a standalone `raw-capture-cli` bundle (server stays 2.6.4). New `raw-inbox.ts` (9 vitest, incl. traversal + injection regressions) + `test-raw-capture.sh` + `test-raw-inbox-banner.sh` + raw-group schema guards. **Additive + back-compat:** `raw/` appears lazily on first capture; with no captures and `SB_RAW_INBOX` unset, behaviour is unchanged. SP-3 setup deep-scan + SP-4 maintainer drain are deferred. | No precondition — bumping the marker is sufficient. Capture material with `/second-brain:capture`; set `SB_RAW_INBOX=off` to suppress the backlog banner. |
 | **0.24.10** | Release-gate **typecheck guard**. `tests/run-all.sh` runs `vitest`, which transpiles per-file and does **not** typecheck the project — so a real `tsc` error (and the stale committed `dist/` left behind when `npm run build` = `tsc && bundle` fails) slipped through the "ALL GREEN" gate invisibly. That is exactly how the 0.24.7 `EDGE_TYPES` zod-enum cast broke the build yet shipped in **both** 0.24.7 and 0.24.8. New `tests/test-mcp-typecheck.sh` runs `tsc --noEmit` (no side effects; skips cleanly when `node` or the local `typescript` is absent) and is auto-included via the `test-*.sh` glob, so the gate now **fails on a type error** — closing the "validate the real capability" gap (the gate was testing a cheap proxy, not the real `npm run build`). Test-only — no state migration, no MCP change. | No precondition — bumping the marker is sufficient. |
 | **0.24.9** | KB **project-scoped serving** (SP-1 of the consolidation vision; design `docs/specs/2026-06-03-project-scoped-serving-design`, plan `docs/plans/2026-06-03-project-scoped-serving`). When a project is active, `knowledge_search` now serves *that project's* knowledge first instead of the whole wiki — killing the cross-project noise the per-prompt persona injection used to feed Claude. Each candidate is tiered: **T1** same `project:` facet **plus** the active project's own local-docs (registry pages are tier-1 by construction); **T2** the graph-neighbourhood of the project's anchor pages (`SB_SCOPE_HOPS`, default 2, 0–4); **T3** shared / no-facet; **T4** other project. Sorted **scoped-first** (tier asc, then score desc) and **auto-broadens** back to the full global pool when fewer than `SB_SCOPE_MIN_HITS` (default 3, 0–100) in-scope hits clear the score floor — so a legitimately cross-project answer is never starved. Wired through the single `knowledge_search` chokepoint so **both** injection surfaces inherit it: `knowledge-search-cli` forwards `SB_ACTIVE_SLUG`/`BRAIN_DIR`; `persona-context.sh` (per-prompt) and `session-load.sh` (session-start) export the active slug. The slug→project map + anchors are built from **wiki docs only** so a local-doc never overwrites a same-basename wiki page's project (which would leak that other-project page into scope). Kill switches: `SB_PROJECT_SCOPE=off`, `scope:"all"`, `SB_SCOPE_MIN_HITS`, `SB_SCOPE_HOPS`. Also unblocks the build: the 0.24.7 `EDGE_TYPES` source-of-truth migration left `server.ts` casting the zod edge enum to `[string, …]`, widening the `knowledge_relate`/`knowledge_neighbors` arg types back to `string` and breaking `tsc` (and thus `tsc && bundle`) since 0.24.7 — fixed to the `[EdgeType, …]` tuple. MCP server → 2.6.4. **Additive + back-compat:** with no active project (or `SB_PROJECT_SCOPE=off` / `scope:"all"`) the ranking is byte-for-byte the prior global search. New tests: 6 SP-1 vitest cases (incl. local-doc tier-1 + the same-basename leak guard) + `test-search-cli-scope.sh`. | No precondition — bumping the marker is sufficient. Scoping activates automatically when a project is active; set `SB_PROJECT_SCOPE=off` to restore the old global behaviour. |
diff --git a/tests/test-setup-scan.sh b/tests/test-setup-scan.sh
new file mode 100755
index 0000000..248a3f4
--- /dev/null
+++ b/tests/test-setup-scan.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+# End-to-end: raw-scan-cli previews + captures high-signal docs into a project's raw inbox,
+# honors git-ignore, dedups on re-run; the setup skill wires the CLI.
+set -u
+ROOT="$(cd "$(dirname "$0")"/.. && pwd)"
+CLI="$ROOT/mcp/dist/tools/raw-scan-cli.bundle.js"
+SKILL="$ROOT/skills/setup/SKILL.md"
+fail(){ echo "FAIL: $1"; exit 1; }; pass(){ echo "PASS: $1"; }
+
+grep -q 'raw-scan-cli.bundle.js' "$SKILL" || fail "setup skill does not invoke raw-scan-cli"
+grep -qE 'allowed-tools:.*Bash\(node \*\)' "$SKILL" || fail "setup skill missing Bash(node *) allowed-tool"
+# The preview and capture are separate bash fences (separate shells), so each must recompute
+# SCAN_ROOT_DIR itself — the `SCAN_ROOT_DIR=$(git rev-parse ...)` assignment must appear twice
+# (once per fence). A single occurrence means the capture fence references an unset var and the
+# scan silently does nothing. (Step 1's slug uses a different assignment, so it isn't counted.)
+N_ROOT=$(grep -cE 'SCAN_ROOT_DIR=\$\(git rev-parse' "$SKILL")
+[ "${N_ROOT:-0}" -ge 2 ] || fail "setup capture fence does not recompute SCAN_ROOT_DIR (found $N_ROOT, need >=2)"
+pass "setup skill wires the scan CLI (preview + capture both self-contained)"
+
+command -v node >/dev/null 2>&1 || { echo "SKIP: node"; echo; echo "ALL PASS"; exit 0; }
+[ -f "$CLI" ] || { echo "SKIP: CLI bundle not built"; echo; echo "ALL PASS"; exit 0; }
+command -v git >/dev/null 2>&1 || { echo "SKIP: git"; echo; echo "ALL PASS"; exit 0; }
+
+T=$(mktemp -d); export BRAIN_DIR="$T" SB_ACTIVE_SLUG=demo
+mkdir -p "$T/projects/demo"; : > "$T/projects/demo/PROJECT.md"
+R=$(mktemp -d)
+( cd "$R" && git init -q && git config user.email t@t && git config user.name t )
+printf '# Readme\nx\n' > "$R/README.md"
+mkdir -p "$R/docs"; printf '# G\nx\n' > "$R/docs/guide.md"; printf '# sec\nx\n' > "$R/docs/secret-ignored.md"
+printf 'docs/secret-ignored.md\n' > "$R/.gitignore"   # git-ignored → must be excluded
+
+OUT=$(SCAN_ROOT="$R" node "$CLI" --dry-run)
+echo "$OUT" | grep -q 'README.md' || fail "dry-run missing README.md ($OUT)"
+echo "$OUT" | grep -q 'docs/guide.md' || fail "dry-run missing docs/guide.md"
+echo "$OUT" | grep -q 'secret-ignored' && fail "dry-run included a git-ignored file"
+[ -z "$(ls -A "$T/projects/demo/raw" 2>/dev/null)" ] || fail "dry-run wrote items"
+pass "dry-run previews high-signal docs, excludes git-ignored, writes nothing"
+
+OUT=$(SCAN_ROOT="$R" node "$CLI")
+echo "$OUT" | grep -q 'Captured 2, skipped 0' || fail "capture count wrong ($OUT)"
+grep -lq '^captured_by: setup-scan$' "$T/projects/demo/raw"/*.md || fail "items not stamped setup-scan"
+pass "capture writes 2 setup-scan items"
+
+OUT=$(SCAN_ROOT="$R" node "$CLI")
+echo "$OUT" | grep -q 'Captured 0, skipped 2' || fail "re-run not idempotent ($OUT)"
+pass "re-run dedups (idempotent)"
+
+rm -rf "$T" "$R"
+echo; echo "ALL PASS"