From d3ec4bb32ad27ee427e52853a9d31364d815af06 Mon Sep 17 00:00:00 2001 From: Christopher Tso Date: Mon, 13 Jul 2026 04:34:28 +0200 Subject: [PATCH] fix(test): isolate home state under CI quality gates --- .github/workflows/ci.yml | 64 ++++++++++++++++++++ .pre-commit-config.yaml | 21 ------- AGENTS.md | 4 +- CONTRIBUTING.md | 19 +++++- bun.lock | 59 ------------------ package.json | 5 +- src/constants.ts | 5 +- tests/helpers/env.ts | 22 +++---- tests/unit/constants.test.ts | 16 +++++ tests/unit/core/status-both-scopes.test.ts | 14 ++--- tests/unit/core/workspace-cache-seed.test.ts | 22 +++---- 11 files changed, 132 insertions(+), 119 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a432453d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build: + name: Build + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v6 + - uses: oven-sh/setup-bun@v2 + with: + bun-version-file: package.json + - run: bun install --frozen-lockfile --ignore-scripts + - run: bun run build + + typecheck: + name: Typecheck + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v6 + - uses: oven-sh/setup-bun@v2 + with: + bun-version-file: package.json + - run: bun install --frozen-lockfile --ignore-scripts + - run: bun run typecheck + + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + - uses: oven-sh/setup-bun@v2 + with: + bun-version-file: package.json + - run: bun install --frozen-lockfile --ignore-scripts + - run: bun run lint + + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v6 + - uses: oven-sh/setup-bun@v2 + with: + bun-version-file: package.json + - run: bun install --frozen-lockfile --ignore-scripts + - run: bun run test diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 750859b1..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,21 +0,0 @@ -repos: - - repo: local - hooks: - - id: lint - name: Lint - entry: bun run lint - language: system - pass_filenames: false - stages: [pre-push] - - id: typecheck - name: Typecheck - entry: bun run typecheck - language: system - pass_filenames: false - stages: [pre-push] - - id: test - name: Test - entry: bun run test - language: system - pass_filenames: false - stages: [pre-push] diff --git a/AGENTS.md b/AGENTS.md index fae9b13c..6e47df4d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -106,7 +106,7 @@ git worktree remove ../allagents.worktrees/ ## Tech Stack & Tools - Runtime and package manager: Bun - Language: TypeScript -- Tests: `bun:test` plus shell integration tests +- Tests: `bun:test` unit and E2E tests - Lint/format: Biome ## Testing & Verification @@ -114,7 +114,7 @@ git worktree remove ../allagents.worktrees/ ### Core Commands - Build: `bun run build` - Unit tests: `bun test` -- Integration tests: `bun run test:integration` +- E2E tests: `bun run test:e2e` - Typecheck: `bun run typecheck` - Lint: `bun run lint` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a200ad1..2768a086 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,9 +32,26 @@ bun run dev update - PR explains what changed and why - Tests are updated when relevant - No unrelated refactors in the same PR -- Pre-push hooks pass (`lint`, `typecheck`, `test` run automatically on push) +- Run focused local checks while developing and the relevant broad checks before pushing - Manual E2E test: build the CLI (`bun run build`) and run against a temp workspace +GitHub Actions is the authoritative broad quality gate. It runs build, typecheck, +lint, and the full test suite as separate checks on pull requests and `main`. +Repository maintainers should configure the `Build`, `Typecheck`, `Lint`, and +`Test` checks as required in the `main` branch protection rules after this +workflow lands. You can run the same checks locally when needed: + +```bash +bun run build +bun run typecheck +bun run lint +bun run test +``` + +Older clones may still have the previously generated prek pre-push hook. If a +push still invokes prek, inspect `.git/hooks/pre-push` and remove it only when +it is the generated prek hook; preserve any custom hook content. + ## Workflow - Branch from `main` diff --git a/bun.lock b/bun.lock index 7d16014c..b5d365d2 100644 --- a/bun.lock +++ b/bun.lock @@ -20,7 +20,6 @@ }, "devDependencies": { "@biomejs/biome": "^1.9.0", - "@j178/prek": "^0.3.0", "@types/bun": "latest", "@types/js-yaml": "^4.0.9", "@types/micromatch": "^4.0.10", @@ -55,12 +54,6 @@ "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], - "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], - - "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], - - "@j178/prek": ["@j178/prek@0.3.0", "", { "dependencies": { "axios": "^1.13.2", "axios-proxy-builder": "^0.1.2", "console.table": "^0.10.0", "detect-libc": "^2.1.2", "rimraf": "^6.1.2" }, "bin": { "prek": "run-prek.js" } }, "sha512-dvPPNGHf0P61rV29husfPKuEIjIHbkmuWKoq6RxquSAnQwmy22/bu5XFH3GzRgvwIxs4OWxmglwiAI6fwEy8Lg=="], - "@kwsites/file-exists": ["@kwsites/file-exists@1.1.1", "", { "dependencies": { "debug": "^4.1.1" } }, "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw=="], "@kwsites/promise-deferred": ["@kwsites/promise-deferred@1.1.1", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="], @@ -93,12 +86,6 @@ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="], - - "axios-proxy-builder": ["axios-proxy-builder@0.1.2", "", { "dependencies": { "tunnel": "^0.0.6" } }, "sha512-6uBVsBZzkB3tCC8iyx59mCjQckhB8+GQrI9Cop8eC7ybIsvs/KtnNgEBfRMSEa7GqK2VBGUzgjNYMdPIfotyPA=="], - "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -113,14 +100,8 @@ "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], - "cmd-ts": ["cmd-ts@0.14.3", "", { "dependencies": { "chalk": "^5.4.1", "debug": "^4.4.1", "didyoumean": "^1.2.2", "strip-ansi": "^7.1.0" } }, "sha512-i9miaLBHGPn4T4vUFUdAdWoQ9HI8ato6usFrhubO21o/MQGqI6Nsqyd4ncZusT2chvPetNMmTt0JWkbaAOdWPg=="], - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "console.table": ["console.table@0.10.0", "", { "dependencies": { "easy-table": "1.1.0" } }, "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g=="], - "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -135,20 +116,12 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], - - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - "easy-table": ["easy-table@1.1.0", "", { "optionalDependencies": { "wcwidth": ">=1.0.1" } }, "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA=="], - "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -161,8 +134,6 @@ "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], @@ -193,10 +164,6 @@ "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], - "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], - - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], - "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], @@ -209,8 +176,6 @@ "get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], - "glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="], - "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], @@ -219,8 +184,6 @@ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "hono": ["hono@4.12.14", "", {}, "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w=="], @@ -267,8 +230,6 @@ "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], - "lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="], - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], @@ -287,12 +248,8 @@ "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], - "minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], @@ -313,16 +270,12 @@ "p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="], - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], - "path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -333,8 +286,6 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], @@ -353,8 +304,6 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - "rimraf": ["rimraf@6.1.2", "", { "dependencies": { "glob": "^13.0.0", "package-json-from-dist": "^1.0.1" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g=="], - "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -411,8 +360,6 @@ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], - "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -423,8 +370,6 @@ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], @@ -433,16 +378,12 @@ "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], - "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], "shelljs/execa": ["execa@1.0.0", "", { "dependencies": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" } }, "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA=="], - "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "shelljs/execa/cross-spawn": ["cross-spawn@6.0.6", "", { "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } }, "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw=="], diff --git a/package.json b/package.json index 3ef12cb7..8522080b 100644 --- a/package.json +++ b/package.json @@ -18,14 +18,14 @@ "test": "bun test", "test:watch": "bun test --watch", "test:coverage": "bun test --coverage", - "test:integration": "bats tests/integration/*.bats", + "test:e2e": "bun test tests/e2e", "typecheck": "tsc --noEmit", "lint": "biome lint src", "lint:fix": "biome lint --write src", "format": "biome format --write src", "check": "biome check src", "check:fix": "biome check --write src", - "prepare": "bun run build && (test -d .git && bunx prek install -t pre-push || true)", + "prepare": "bun run build", "release": "bun run scripts/release.ts", "release:next": "bun run scripts/release.ts next", "release:finalize": "bun run scripts/release.ts finalize", @@ -68,7 +68,6 @@ }, "devDependencies": { "@biomejs/biome": "^1.9.0", - "@j178/prek": "^0.3.0", "@types/bun": "latest", "@types/js-yaml": "^4.0.9", "@types/micromatch": "^4.0.10", diff --git a/src/constants.ts b/src/constants.ts index 9fa38b85..6e99ae26 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -10,7 +10,10 @@ import { homedir } from 'node:os'; * (see EntityProcess/allagents#433). */ export function getHomeDir(): string { - return homedir(); + // Bun caches os.homedir() for the process, so tests cannot isolate user state + // by mutating HOME/USERPROFILE. This deliberately named override belongs to + // AllAgents tests; production continues to use the Windows-safe os.homedir(). + return process.env.ALLAGENTS_TEST_HOME || homedir(); } /** diff --git a/tests/helpers/env.ts b/tests/helpers/env.ts index 4a8b6b5f..28ec29db 100644 --- a/tests/helpers/env.ts +++ b/tests/helpers/env.ts @@ -1,22 +1,18 @@ /** * Temporarily override the resolved home directory for a test. * - * os.homedir() (what src/constants.ts#getHomeDir now delegates to) reads - * $HOME on POSIX and %USERPROFILE% on Windows — never both — so tests that - * only stubbed HOME silently stopped taking effect on Windows. Stubbing both - * keeps tests platform-independent. Returns a restore function that deletes - * (rather than stringifies `undefined` into) any var that wasn't originally set. + * Bun caches os.homedir() for the process, so changing HOME or USERPROFILE + * cannot isolate tests reliably. getHomeDir() owns this explicitly test-only + * override while continuing to use os.homedir() in production. The restore + * function supports nested stubs and deletes an override that was originally + * absent. */ export function stubHomeDir(path: string): () => void { - const originalHome = process.env.HOME; - const originalUserProfile = process.env.USERPROFILE; - process.env.HOME = path; - process.env.USERPROFILE = path; + const originalTestHome = process.env.ALLAGENTS_TEST_HOME; + process.env.ALLAGENTS_TEST_HOME = path; return () => { - if (originalHome === undefined) delete process.env.HOME; - else process.env.HOME = originalHome; - if (originalUserProfile === undefined) delete process.env.USERPROFILE; - else process.env.USERPROFILE = originalUserProfile; + if (originalTestHome === undefined) delete process.env.ALLAGENTS_TEST_HOME; + else process.env.ALLAGENTS_TEST_HOME = originalTestHome; }; } diff --git a/tests/unit/constants.test.ts b/tests/unit/constants.test.ts index 0f3845ba..6b1df2f5 100644 --- a/tests/unit/constants.test.ts +++ b/tests/unit/constants.test.ts @@ -1,5 +1,7 @@ import { describe, expect, test, afterEach } from 'bun:test'; +import { homedir } from 'node:os'; import { getHomeDir } from '../../src/constants.js'; +import { stubHomeDir } from '../helpers/env.js'; describe('getHomeDir', () => { const originalHome = process.env.HOME; @@ -12,6 +14,20 @@ describe('getHomeDir', () => { else process.env.USERPROFILE = originalUserProfile; }); + test('uses and restores the explicit AllAgents test home override', () => { + // Resolve os.homedir() first to reproduce Bun's process-level cache. + const realHome = homedir(); + const restoreHomeDir = stubHomeDir('/tmp/allagents-isolated-home'); + + try { + expect(getHomeDir()).toBe('/tmp/allagents-isolated-home'); + } finally { + restoreHomeDir(); + } + + expect(getHomeDir()).toBe(realHome); + }); + // Windows-only: os.homedir() ignores HOME entirely on win32 (uses USERPROFILE), // so this needs HOME and USERPROFILE set to *different* values to prove the // point — stubHomeDir (which sets both to the same path) doesn't apply here. diff --git a/tests/unit/core/status-both-scopes.test.ts b/tests/unit/core/status-both-scopes.test.ts index 3936efe8..4e4a81ec 100644 --- a/tests/unit/core/status-both-scopes.test.ts +++ b/tests/unit/core/status-both-scopes.test.ts @@ -11,13 +11,16 @@ import { stubHomeDir } from '../../helpers/env.js'; describe('workspace status - both scopes', () => { let testDir: string; let restoreHomeDir: () => void; + let restoreScopedHomeDir: (() => void) | undefined; beforeEach(async () => { testDir = await mkdtemp(join(tmpdir(), 'allagents-status-test-')); restoreHomeDir = stubHomeDir(testDir); + restoreScopedHomeDir = undefined; }); afterEach(async () => { + restoreScopedHomeDir?.(); restoreHomeDir(); await rm(testDir, { recursive: true, force: true }); }); @@ -50,11 +53,8 @@ describe('workspace status - both scopes', () => { it('should include userPlugins in status result', async () => { // Use a separate HOME so user config doesn't overlap with project dir. - // Discarding the returned restore fn is intentional: the outer afterEach's - // restoreHomeDir() already unwinds straight back to the pre-suite original - // regardless of how many times HOME/USERPROFILE were reassigned in between. const homeDir = await mkdtemp(join(tmpdir(), 'allagents-status-home-')); - stubHomeDir(homeDir); + restoreScopedHomeDir = stubHomeDir(homeDir); const projectPlugin = await createLocalPlugin('project-plugin'); const userPlugin = await createLocalPlugin('user-plugin'); @@ -85,7 +85,7 @@ describe('workspace status - both scopes', () => { it('should fall back to user plugins when no project workspace exists', async () => { // Use a separate HOME so user config doesn't overlap with project dir const homeDir = await mkdtemp(join(tmpdir(), 'allagents-status-home-')); - stubHomeDir(homeDir); + restoreScopedHomeDir = stubHomeDir(homeDir); const userPlugin = await createLocalPlugin('user-plugin'); const allagentsDir = join(homeDir, '.allagents'); @@ -129,7 +129,7 @@ describe('workspace status - both scopes', () => { it('should show empty userPlugins when no user config exists', async () => { // Use a separate HOME dir so there's no user config const separateHome = await mkdtemp(join(tmpdir(), 'allagents-status-home-')); - stubHomeDir(separateHome); + restoreScopedHomeDir = stubHomeDir(separateHome); const projectPlugin = await createLocalPlugin('project-plugin'); @@ -150,7 +150,7 @@ describe('workspace status - both scopes', () => { // Regression: `skills add ` clones into `-@`, // but `workspace status` looked up `-` and reported "not cached". const homeDir = await mkdtemp(join(tmpdir(), 'allagents-status-home-')); - process.env.HOME = homeDir; + restoreScopedHomeDir = stubHomeDir(homeDir); const branchedCache = join( homeDir, diff --git a/tests/unit/core/workspace-cache-seed.test.ts b/tests/unit/core/workspace-cache-seed.test.ts index 1521b9b3..e863c8bf 100644 --- a/tests/unit/core/workspace-cache-seed.test.ts +++ b/tests/unit/core/workspace-cache-seed.test.ts @@ -6,29 +6,27 @@ import { tmpdir } from 'node:os'; import { seedCacheFromClone } from '../../../src/core/workspace.js'; import { getPluginCachePath } from '../../../src/utils/plugin-path.js'; import { getMarketplacesDir } from '../../../src/core/marketplace.js'; +import { stubHomeDir } from '../../helpers/env.js'; describe('seedCacheFromClone', () => { let tempCloneDir: string; + let tempHomeDir: string; + let restoreHomeDir: () => void; beforeEach(async () => { tempCloneDir = await mkdtemp(join(tmpdir(), 'allagents-clone-')); + tempHomeDir = await mkdtemp(join(tmpdir(), 'allagents-home-')); + restoreHomeDir = stubHomeDir(tempHomeDir); // Add a marker file to verify the clone was copied await writeFile(join(tempCloneDir, 'marker.txt'), 'cloned-content'); }); afterEach(async () => { - if (existsSync(tempCloneDir)) { - await rm(tempCloneDir, { recursive: true, force: true }); - } - // Clean up any cache directories that were seeded - const pluginCachePath = getPluginCachePath('test-owner', 'test-repo', 'main'); - if (existsSync(pluginCachePath)) { - await rm(pluginCachePath, { recursive: true, force: true }); - } - const marketplaceCachePath = join(getMarketplacesDir(), 'test-repo'); - if (existsSync(marketplaceCachePath)) { - await rm(marketplaceCachePath, { recursive: true, force: true }); - } + restoreHomeDir(); + await Promise.all([ + rm(tempCloneDir, { recursive: true, force: true }), + rm(tempHomeDir, { recursive: true, force: true }), + ]); }); it('should seed both plugin and marketplace caches', async () => {