From 4ac34d27ae76bd69c773026a4977ff18d65b5996 Mon Sep 17 00:00:00 2001 From: Antonio Mancini Date: Fri, 9 Oct 2026 18:10:32 +0200 Subject: [PATCH 1/2] feat!: install the full stable Matt Pocock skill set BREAKING CHANGE: the wizard's "Select AI engineering workflow" prompt and the --spec-workflow flag are gone. Every standard skill installs by default, so there was nothing left to select. Skills: - Install all 27 stable Matt Pocock skills (engineering + productivity) instead of the tdd / code-review / diagnosing-bugs trio. The misc and in-progress buckets stay out. - Upgrade saved configs that still hold an old default skill selection, and drop the retired skills.workflow field. Hand-narrowed lists are preserved. - AGENTS.md lists the full standard set and the spec workflow, understands wildcard sources, and separates its sections with blank lines again. Wording: - Replace T3 Code references with generic AI coding agent wording. Toolchain: - TypeScript 7, Vitest 5, Commander 15, tsx 4.23, pnpm 12.10. @types/node stays on the 22.x line so the types match engines >= 22.12.0. - pnpm typecheck now type-checks tests/ via tsconfig.test.json. - Version is read from package.json in the CLI and the smoke test; bump to 0.2.0 with a changelog. Fixes: - aginit doctor no longer warns when skills are disabled in config, and reports on-disk skills even when the lockfile records none. - aginit init in the current directory no longer prints "cd .". - Ignore graft's .ignore ripgrep hint alongside /graft/. Additions: - pnpm check:skills and a weekly Skill drift workflow that fails when a standard skill disappears upstream or a new stable skill is published. --- .github/workflows/skill-drift.yml | 34 ++ .gitignore | 2 + AGENTS.md | 4 +- CHANGELOG.md | 43 +++ README.md | 17 +- aginit.config.json | 31 +- package.json | 21 +- pnpm-lock.yaml | 523 +++++++++++++++++++----------- scripts/check-skill-drift.mjs | 81 +++++ scripts/smoke-test.mjs | 3 +- src/adapters/agents-md.ts | 76 +++-- src/adapters/git.ts | 2 + src/adapters/skills.ts | 17 +- src/cli/doctor.ts | 14 +- src/cli/main.ts | 42 +-- src/cli/new.ts | 28 +- src/cli/wizard.ts | 60 +--- src/config/defaults.ts | 97 +++++- src/config/schema.ts | 5 + tests/agents-md.test.ts | 21 ++ tests/config.test.ts | 43 ++- tests/git.test.ts | 4 +- tests/interactive.test.ts | 1 - tests/release.test.ts | 56 +++- tests/wizard.test.ts | 11 +- tsconfig.json | 3 +- tsconfig.test.json | 12 + 27 files changed, 850 insertions(+), 401 deletions(-) create mode 100644 .github/workflows/skill-drift.yml create mode 100644 CHANGELOG.md create mode 100644 scripts/check-skill-drift.mjs create mode 100644 tsconfig.test.json diff --git a/.github/workflows/skill-drift.yml b/.github/workflows/skill-drift.yml new file mode 100644 index 0000000..8587bc6 --- /dev/null +++ b/.github/workflows/skill-drift.yml @@ -0,0 +1,34 @@ +name: Skill drift + +on: + schedule: + - cron: '0 6 * * 1' + workflow_dispatch: + +jobs: + drift: + name: Standard skill set vs mattpocock/skills + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24.x + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build distribution + run: pnpm build + + - name: Compare standard skills with upstream + run: pnpm check:skills + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index d014beb..d9557a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ node_modules/ dist/ /graft/ +# ripgrep hint graft writes next to the git-ignored graph; regenerated by `graft build` +.ignore .DS_Store *.log *.tgz diff --git a/AGENTS.md b/AGENTS.md index 556f430..059d299 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # aginit -Minimal, modular AI-first project bootstrapper for T3 Code, Antigravity, and Codex. +Minimal, modular AI-first project bootstrapper for AI coding agents. ## Core Invariants - Orchestrate upstream tools; do not duplicate, vendor, or fork them. @@ -15,7 +15,7 @@ Minimal, modular AI-first project bootstrapper for T3 Code, Antigravity, and Cod ## AI Capabilities & Skills Installed skills live in `.agents/skills/` (shared by Antigravity and Codex): -- **Engineering**: `tdd`, `code-review`, `diagnosing-bugs` +- **Engineering**: the full stable Matt Pocock skill set — 27 skills covering `tdd`, `code-review`, `diagnosing-bugs`, `to-spec`, `to-tickets`, `implement-spec`, `pr`, `research`, `retro`, `triage`, and more (see `.agents/skills/`) - **Prose & Copy**: Use `humanizer` skill to eliminate AI writing tells and refine documentation. - **Codebase Graph**: Use `graft ask "" --source` or `graft map` for orientation before raw grep. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d639da8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +# Changelog + +## 0.2.0 — 2026-10-09 + +### Breaking + +- Removed `--spec-workflow` from `aginit new` and `aginit init`, and removed the + wizard's "Select AI engineering workflow" prompt. The spec and tickets skills + are part of the standard set now, so the selector no longer had anything to + select. +- `skills.workflow` is no longer written to `aginit.config.json`. Existing + configs that still carry it keep loading unchanged. + +### Changed + +- All 27 stable Matt Pocock skills install by default instead of the + `tdd` / `code-review` / `diagnosing-bugs` trio: the `engineering` and + `productivity` categories. The `misc` and `in-progress` categories stay out. +- Saved configs holding an old default skill selection are upgraded on + `aginit init`. A skill list narrowed by hand is preserved as written. +- AGENTS.md now lists the full standard skill set and the spec workflow. +- Dependency updates: TypeScript 7, Vitest 5, Commander 15, tsx 4.23, + pnpm 12.10. `@types/node` stays on the 22.x line so the types match the + `engines.node >= 22.12.0` support floor. +- Descriptions no longer mention T3 Code. + +### Fixed + +- Generated AGENTS.md separates its sections with blank lines again; only + inapplicable lines are dropped. +- AGENTS.md no longer claims skills are disabled when a source installs every + skill it ships. +- `aginit init` in the current directory no longer prints `cd .`. +- `aginit doctor` no longer warns about missing skills when installation is + disabled in the configuration, and reports skills installed by hand even when + the lockfile records none. +- `aginit --version` and the smoke test read the version from `package.json`. + +### Added + +- `pnpm typecheck` now type-checks `tests/` through `tsconfig.test.json`. +- `pnpm check:skills` and a weekly `Skill drift` CI job that fails when a + standard skill disappears upstream or a new stable skill is published. diff --git a/README.md b/README.md index 64c24a8..3c9b7a9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Aginit -Project bootstrapper for T3 Code, Antigravity, and Codex. Configures agent instructions, skills, a codebase graph, and test tooling using upstream tools. +Project bootstrapper for AI coding agents. Configures agent instructions, skills, a codebase graph, and test tooling using upstream tools. -Beta · v0.1.0 +Beta [![CI](https://github.com/antomanc/aginit/actions/workflows/ci.yml/badge.svg)](https://github.com/antomanc/aginit/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) @@ -27,7 +27,7 @@ npm install -g @antomanc/aginit aginit ``` -The wizard lets you create a project or initialize an existing directory, then choose a preset, framework, workflow, and package manager. Existing projects retain their detected package manager. +The wizard lets you create a project or initialize an existing directory, then choose a preset, framework, and package manager. Existing projects retain their detected package manager. Graft requires a separate installation. Use `--no-graft` to skip it. @@ -66,11 +66,11 @@ The web preset supports `--framework none` (default), `vite`, `next`, or `existi ### Agent configuration - `AGENTS.md`: project instructions shared by agents. -- `.agents/skills/`: `tdd`, `code-review`, and `diagnosing-bugs` from Matt Pocock, and `humanizer` from Blake Robbins. Web projects also include Impeccable and agent-browser. +- `.agents/skills/`: the 27 stable skills from Matt Pocock (engineering + productivity), and `humanizer` from Blake Robbins. Web projects also include Impeccable and agent-browser. - Graft: a codebase graph for `graft map` and `graft ask`. - `aginit.config.json`: saved preset, framework, package manager, agents, and skill sources. -Use `--spec-workflow` to add `to-spec`, `to-tickets`, and `implement-spec`. Use `--no-skills` to skip skill installation. +The standard skill set includes the spec and tickets workflow (`to-spec`, `to-tickets`, `implement-spec`); there is no workflow selector to run. Skills from Matt Pocock's `misc` and `in-progress` buckets are left out. Use `--no-skills` to skip skill installation. ## Existing projects @@ -78,6 +78,8 @@ Use `--spec-workflow` to add `to-spec`, `to-tickets`, and `implement-spec`. Use Repeated initialization preserves saved settings and extension fields. Explicit options override the corresponding settings. Invalid configuration is reported before files are changed. `--no-skills` skips installation while retaining saved skill sources. +Configs written by older versions that still hold the retired default skill selection are upgraded to the current standard set, along with dropping the retired `skills.workflow` selector. A skill list narrowed by hand is preserved as written. + ## Maintenance ```bash @@ -101,7 +103,8 @@ aginit update ```bash pnpm install --frozen-lockfile -pnpm check # typecheck, tests, build, dependency audit +pnpm check # typecheck (src + tests), tests, build, dependency audit +pnpm check:skills # compare the standard skill set with upstream (after pnpm build) pnpm smoke # verify projects generated from the packed tarball pnpm smoke --frameworks # also scaffold, test, build, and serve Vite and Next.js pnpm smoke --package-managers # verify pnpm, Yarn, and Bun consumer commands @@ -110,7 +113,7 @@ npm pack --dry-run Framework smoke tests download upstream tools and dependencies. `npm publish` runs `pnpm check` and the base consumer smoke test before publication. -See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines and [SECURITY.md](SECURITY.md) for vulnerability reporting. +See [CHANGELOG.md](CHANGELOG.md) for release notes, [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines, and [SECURITY.md](SECURITY.md) for vulnerability reporting. ## License diff --git a/aginit.config.json b/aginit.config.json index a34a550..3093629 100644 --- a/aginit.config.json +++ b/aginit.config.json @@ -8,11 +8,38 @@ "secondary": "codex" }, "skills": { - "workflow": "minimal", "sources": [ { "package": "mattpocock/skills", - "skills": ["tdd", "code-review", "diagnosing-bugs"] + "skills": [ + "ask-matt", + "code-review", + "codebase-design", + "diagnosing-bugs", + "domain-modeling", + "grill-with-docs", + "implement", + "implement-spec", + "improve-codebase-architecture", + "pr", + "prototype", + "research", + "retro", + "setup-matt-pocock-skills", + "tdd", + "to-spec", + "to-tickets", + "triage", + "wayfinder", + "wizard", + "grill-me", + "grilling", + "handoff", + "teach", + "to-questionnaire", + "wait-what", + "writing-for-agents" + ] }, { "package": "blader/humanizer", diff --git a/package.json b/package.json index fbd620d..0fc4d09 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@antomanc/aginit", - "version": "0.1.0", - "description": "Minimal, modular AI-first project bootstrapper for T3 Code, Antigravity, and Codex", + "version": "0.2.0", + "description": "Minimal, modular AI-first project bootstrapper for AI coding agents", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -19,6 +19,7 @@ "src", "bin", "README.md", + "CHANGELOG.md", "LICENSE", "THIRD_PARTY_NOTICES.md" ], @@ -39,7 +40,6 @@ "agents", "antigravity", "codex", - "t3-code", "skills", "graft", "bootstrap", @@ -49,26 +49,27 @@ "build": "tsc", "watch": "tsc --watch", "test": "vitest run", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", "prepack": "tsc", "check": "pnpm typecheck && pnpm test && pnpm build && pnpm audit", + "check:skills": "node scripts/check-skill-drift.mjs", "smoke": "node scripts/smoke-test.mjs", "prepublishOnly": "pnpm check && pnpm smoke" }, "dependencies": { "@clack/prompts": "^1.8.1", - "commander": "^12.1.0", + "commander": "^15.0.0", "picocolors": "^1.1.1" }, "devDependencies": { - "@types/node": "^22.8.0", - "tsx": "^4.19.2", - "typescript": "^5.6.3", - "vitest": "^4.1.11" + "@types/node": "^22.20.5", + "tsx": "^4.23.15", + "typescript": "^7.0.2", + "vitest": "^5.0.3" }, "license": "MIT", "engines": { "node": ">=22.12.0" }, - "packageManager": "pnpm@12.8.1" + "packageManager": "pnpm@12.10.1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 189dbd8..8c6c8e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,153 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.8.1 - version: 12.8.1 + specifier: 12.10.1 + version: 12.10.1 packages: - '@pnpm/exe.android-arm64@12.8.1': - resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==} + '@pnpm/exe.android-arm64@12.10.1': + resolution: {integrity: sha512-CVTlHPwNvadQCJD9bGSMzWIhFX8QpjtqC2c27dhdnn/9iKzEjZ3qL62o3gHpKbIuo6qn4WxTKRoaokgNdReBig==} cpu: [arm64] os: [android] - '@pnpm/exe.android-x64@12.8.1': - resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==} + '@pnpm/exe.android-x64@12.10.1': + resolution: {integrity: sha512-NFiYLIaodY7da0XIebwcU3JoP7IK/lzF7zgKSSP+9H+QcFnQ1GDfBkuBKCYJzZ+Xu0WH54neIPLBBCaOjaaauA==} cpu: [x64] os: [android] - '@pnpm/exe.darwin-arm64@12.8.1': - resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==} + '@pnpm/exe.darwin-arm64@12.10.1': + resolution: {integrity: sha512-0O8tdQ8ijlPJudXspLMTcitMkdBEz6uUflazL5uQE0WMs85oQIp0U+5hfEktMFB64QqEhjqlvvujTwbzdr9oMg==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.8.1': - resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==} + '@pnpm/exe.darwin-x64@12.10.1': + resolution: {integrity: sha512-ud+1/uZOKEo8P/AsPynOJc/LNwfI0j6WwCElibj71rU9l4nIBnsSn+wWTIPZPkZ4BBSH5+82azSnAARMyJ1lNA==} cpu: [x64] os: [darwin] - '@pnpm/exe.freebsd-x64@12.8.1': - resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==} + '@pnpm/exe.freebsd-x64@12.10.1': + resolution: {integrity: sha512-mj9PE5rf8uAfQQ2K66HSATMSut+lNiB4eSqFd5+azFVCMnxNSy/32xpZQJT21PhnN3Y4qbK+iKHe5nWAxSuRhw==} cpu: [x64] os: [freebsd] - '@pnpm/exe.linux-arm64-musl@12.8.1': - resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==} + '@pnpm/exe.linux-arm64-musl@12.10.1': + resolution: {integrity: sha512-xba+00wZJ8Um6rBJJjWl88uJj0tydB8dtomvjlCLvi0uL32vpa9CRP8Vv26FIXvbiZ1bo2RCzYt/eChxJVV52w==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.8.1': - resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==} + '@pnpm/exe.linux-arm64@12.10.1': + resolution: {integrity: sha512-oBdMp69ScC9UnniG4qZAQVr2fBDSXjm4yk/72vkEVyrlwS23yMxcRQl7Qdo3sgXPAygY6TWnPRKm4rXzjoyW7w==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-ppc64@12.8.1': - resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==} + '@pnpm/exe.linux-ppc64@12.10.1': + resolution: {integrity: sha512-Yyb3wu09h3c7mLme270uHFiVaAIUHC411Ru9LIU2BKUIstrGlXa9OqqVWoXaRgYRdFdOFknkwG7GNn6ygwLbnw==} cpu: [ppc64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-riscv64@12.8.1': - resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==} + '@pnpm/exe.linux-riscv64@12.10.1': + resolution: {integrity: sha512-PdkeUFFkybknnGkWP0H22eMpIiS4OZXL5BcQzF4lh+vmrW3IaC2HOkbANKgot/PKO0Kl77pzA/6PKFxgbgfXIQ==} cpu: [riscv64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-s390x@12.8.1': - resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==} + '@pnpm/exe.linux-s390x@12.10.1': + resolution: {integrity: sha512-1okKuHKJwJPAbofDxfhZWAUxEWtee4ZSf7Pah8kcvocaJkf2WxFJ5KVTiNIvDml5GLidDQsgf7Pcf/WHbCak7Q==} cpu: [s390x] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.8.1': - resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==} + '@pnpm/exe.linux-x64-musl@12.10.1': + resolution: {integrity: sha512-e6IqzVNkPa7srFKJKnY23uWJ+qz0LaRWiTUo/sHfFXRRMRzB5uNOr6qWWip/jqhdGwU91n9J7LrEYZ6e0tRczw==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.8.1': - resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==} + '@pnpm/exe.linux-x64@12.10.1': + resolution: {integrity: sha512-ZByV0DRgWzGaDSjT5ddRBASdwz+r/qPMYh7o6RGmPQjtRGNFbSl94xY2DcAM8mqCOZJUUDtAXQ+wpBXcdpKlkg==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.8.1': - resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==} + '@pnpm/exe.win32-arm64@12.10.1': + resolution: {integrity: sha512-I7QZHcjioG7orFeCRUFUHByJ1G0zFPnAlaGGyrUvb6MGkv5oeNwNPl7t+Gp+7zHfnMcgLGJ1a+Wfim0IDw3lOw==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.8.1': - resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==} + '@pnpm/exe.win32-x64@12.10.1': + resolution: {integrity: sha512-4WJFdp58EKlCj0zLobrXU0vpszSZIr4X/3y/uNbAQqGW0zE+VdruZB0gFc3L2kzsUzVwTYbaCn+XEhnCYSAlTg==} cpu: [x64] os: [win32] - pnpm@12.8.1: - resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==} + pnpm@12.10.1: + resolution: {integrity: sha512-ukCjfrGjcNYP6oxM8dNkwTvMzvUY6YrSvO6VT1OdONo3RY5YJho/pV4GUjRQ6g//+fSXsTW+QfLBXBZCusrObA==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.android-arm64@12.8.1': + '@pnpm/exe.android-arm64@12.10.1': optional: true - '@pnpm/exe.android-x64@12.8.1': + '@pnpm/exe.android-x64@12.10.1': optional: true - '@pnpm/exe.darwin-arm64@12.8.1': + '@pnpm/exe.darwin-arm64@12.10.1': optional: true - '@pnpm/exe.darwin-x64@12.8.1': + '@pnpm/exe.darwin-x64@12.10.1': optional: true - '@pnpm/exe.freebsd-x64@12.8.1': + '@pnpm/exe.freebsd-x64@12.10.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.8.1': + '@pnpm/exe.linux-arm64-musl@12.10.1': optional: true - '@pnpm/exe.linux-arm64@12.8.1': + '@pnpm/exe.linux-arm64@12.10.1': optional: true - '@pnpm/exe.linux-ppc64@12.8.1': + '@pnpm/exe.linux-ppc64@12.10.1': optional: true - '@pnpm/exe.linux-riscv64@12.8.1': + '@pnpm/exe.linux-riscv64@12.10.1': optional: true - '@pnpm/exe.linux-s390x@12.8.1': + '@pnpm/exe.linux-s390x@12.10.1': optional: true - '@pnpm/exe.linux-x64-musl@12.8.1': + '@pnpm/exe.linux-x64-musl@12.10.1': optional: true - '@pnpm/exe.linux-x64@12.8.1': + '@pnpm/exe.linux-x64@12.10.1': optional: true - '@pnpm/exe.win32-arm64@12.8.1': + '@pnpm/exe.win32-arm64@12.10.1': optional: true - '@pnpm/exe.win32-x64@12.8.1': + '@pnpm/exe.win32-x64@12.10.1': optional: true - pnpm@12.8.1: + pnpm@12.10.1: optionalDependencies: - '@pnpm/exe.android-arm64': 12.8.1 - '@pnpm/exe.android-x64': 12.8.1 - '@pnpm/exe.darwin-arm64': 12.8.1 - '@pnpm/exe.darwin-x64': 12.8.1 - '@pnpm/exe.freebsd-x64': 12.8.1 - '@pnpm/exe.linux-arm64': 12.8.1 - '@pnpm/exe.linux-arm64-musl': 12.8.1 - '@pnpm/exe.linux-ppc64': 12.8.1 - '@pnpm/exe.linux-riscv64': 12.8.1 - '@pnpm/exe.linux-s390x': 12.8.1 - '@pnpm/exe.linux-x64': 12.8.1 - '@pnpm/exe.linux-x64-musl': 12.8.1 - '@pnpm/exe.win32-arm64': 12.8.1 - '@pnpm/exe.win32-x64': 12.8.1 + '@pnpm/exe.android-arm64': 12.10.1 + '@pnpm/exe.android-x64': 12.10.1 + '@pnpm/exe.darwin-arm64': 12.10.1 + '@pnpm/exe.darwin-x64': 12.10.1 + '@pnpm/exe.freebsd-x64': 12.10.1 + '@pnpm/exe.linux-arm64': 12.10.1 + '@pnpm/exe.linux-arm64-musl': 12.10.1 + '@pnpm/exe.linux-ppc64': 12.10.1 + '@pnpm/exe.linux-riscv64': 12.10.1 + '@pnpm/exe.linux-s390x': 12.10.1 + '@pnpm/exe.linux-x64': 12.10.1 + '@pnpm/exe.linux-x64-musl': 12.10.1 + '@pnpm/exe.win32-arm64': 12.10.1 + '@pnpm/exe.win32-x64': 12.10.1 --- lockfileVersion: '9.0' @@ -170,24 +170,24 @@ importers: specifier: ^1.8.1 version: 1.8.1 commander: - specifier: ^12.1.0 - version: 12.1.0 + specifier: ^15.0.0 + version: 15.0.0 picocolors: specifier: ^1.1.1 version: 1.1.1 devDependencies: '@types/node': - specifier: ^22.8.0 + specifier: ^22.20.5 version: 22.20.5 tsx: - specifier: ^4.19.2 + specifier: ^4.23.15 version: 4.23.15 typescript: - specifier: ^5.6.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vitest: - specifier: ^4.1.11 - version: 4.1.11(@types/node@22.20.5)(vite@8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)) + specifier: ^5.0.3 + version: 5.0.3(@types/node@22.20.5)(vite@8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)) packages: @@ -355,9 +355,16 @@ packages: cpu: [x64] os: [win32] + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.6.0': resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@oxc-project/types@0.152.0': resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} @@ -460,9 +467,6 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -475,11 +479,128 @@ packages: '@types/node@22.20.5': resolution: {integrity: sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==} - '@vitest/expect@4.1.11': - resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] - '@vitest/mocker@4.1.11': - resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -489,20 +610,8 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.11': - resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} - - '@vitest/runner@4.1.11': - resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} - - '@vitest/snapshot@4.1.11': - resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} - - '@vitest/spy@4.1.11': - resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} - - '@vitest/utils@4.1.11': - resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} @@ -512,12 +621,9 @@ packages: resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} engines: {node: '>=18'} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} @@ -635,8 +741,8 @@ packages: resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} engines: {node: '>= 12.0.0'} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} nanoid@3.3.19: resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} @@ -647,9 +753,6 @@ packages: resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} engines: {node: '>=12.20.0'} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -666,9 +769,6 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -676,14 +776,12 @@ packages: resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@4.3.0: resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinybench@6.2.1: + resolution: {integrity: sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==} + engines: {node: '>=20.0.0'} tinyexec@1.3.1: resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} @@ -693,18 +791,14 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinyrainbow@3.2.0: - resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} - engines: {node: '>=14.0.0'} - tsx@4.23.15: resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} engines: {node: '>=18.0.0'} hasBin: true - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true undici-types@6.21.0: @@ -753,23 +847,23 @@ packages: yaml: optional: true - vitest@4.1.11: - resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.11 - '@vitest/browser-preview': 4.1.11 - '@vitest/browser-webdriverio': 4.1.11 - '@vitest/coverage-istanbul': 4.1.11 - '@vitest/coverage-v8': 4.1.11 - '@vitest/ui': 4.1.11 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -794,9 +888,9 @@ packages: jsdom: optional: true - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} hasBin: true snapshots: @@ -891,8 +985,15 @@ snapshots: '@esbuild/win32-x64@0.28.2': optional: true + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + '@oxc-project/types@0.152.0': {} '@rolldown/binding-android-arm-eabi@1.2.12': @@ -942,8 +1043,6 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@standard-schema/spec@1.1.0': {} - '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -957,54 +1056,82 @@ snapshots: dependencies: undici-types: 6.21.0 - '@vitest/expect@4.1.11': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - chai: 6.3.0 - tinyrainbow: 3.2.0 + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true - '@vitest/mocker@4.1.11(vite@8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15))': - dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15) + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true - '@vitest/pretty-format@4.1.11': - dependencies: - tinyrainbow: 3.2.0 + '@typescript/typescript-darwin-x64@7.0.2': + optional: true - '@vitest/runner@4.1.11': - dependencies: - '@vitest/utils': 4.1.11 - pathe: 2.0.3 + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true - '@vitest/snapshot@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - '@vitest/utils': 4.1.11 - magic-string: 0.30.21 - pathe: 2.0.3 + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true - '@vitest/spy@4.1.11': {} + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true - '@vitest/utils@4.1.11': + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@vitest/mocker@5.0.3(vite@8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15))': dependencies: - '@vitest/pretty-format': 4.1.11 - convert-source-map: 2.0.0 - tinyrainbow: 3.2.0 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15) + + '@vitest/spy@5.0.3': {} assertion-error@2.0.1: {} chai@6.3.0: {} - commander@12.1.0: {} - - convert-source-map@2.0.0: {} + commander@15.0.0: {} detect-libc@2.1.2: {} @@ -1111,7 +1238,7 @@ snapshots: lightningcss-win32-arm64-msvc: 1.33.0 lightningcss-win32-x64-msvc: 1.33.0 - magic-string@0.30.21: + magic-string@1.4.3: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -1119,8 +1246,6 @@ snapshots: obug@2.2.1: {} - pathe@2.0.3: {} - picocolors@1.1.1: {} picomatch@4.0.7: {} @@ -1152,17 +1277,13 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.12 '@rolldown/binding-win32-x64-msvc': 1.2.12 - siginfo@2.0.0: {} - sisteransi@1.0.5: {} source-map-js@1.2.2: {} - stackback@0.0.2: {} - std-env@4.3.0: {} - tinybench@2.9.0: {} + tinybench@6.2.1: {} tinyexec@1.3.1: {} @@ -1171,15 +1292,34 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 - tinyrainbow@3.2.0: {} - tsx@4.23.15: dependencies: esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 - typescript@5.9.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 undici-types@6.21.0: {} @@ -1196,34 +1336,25 @@ snapshots: fsevents: 2.3.3 tsx: 4.23.15 - vitest@4.1.11(@types/node@22.20.5)(vite@8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)): + vitest@5.0.3(@types/node@22.20.5)(vite@8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(vite@8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)) + chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.4.3 obug: 2.2.1 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.3.0 - tinybench: 2.9.0 + tinybench: 6.2.1 tinyexec: 1.3.1 tinyglobby: 0.2.17 - tinyrainbow: 3.2.0 vite: 8.3.2(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15) - why-is-node-running: 2.3.0 + why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 22.20.5 transitivePeerDependencies: - msw - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 + why-is-node-running@3.2.1: {} diff --git a/scripts/check-skill-drift.mjs b/scripts/check-skill-drift.mjs new file mode 100644 index 0000000..fd6f161 --- /dev/null +++ b/scripts/check-skill-drift.mjs @@ -0,0 +1,81 @@ +#!/usr/bin/env node +/** + * Guards MATT_POCOCK_SKILLS against drift in mattpocock/skills. + * + * It fails when: + * - a skill in the standard set was renamed, deleted, or moved out of the + * stable categories (engineering, productivity) — `aginit init` would fail + * for those names; + * - a skill published in a stable category is absent from the standard set. + * + * Usage: pnpm check:skills (requires `pnpm build` first) + * Set GITHUB_TOKEN to raise the GitHub API rate limit. + */ +import { existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +const REPO = 'mattpocock/skills'; +const STABLE_CATEGORIES = new Set(['engineering', 'productivity']); + +const defaultsPath = fileURLToPath(new URL('../dist/config/defaults.js', import.meta.url)); +if (!existsSync(defaultsPath)) { + console.error('dist/config/defaults.js is missing — run `pnpm build` first.'); + process.exit(1); +} +const { MATT_POCOCK_SKILLS } = await import(defaultsPath); + +const headers = { 'User-Agent': 'aginit-skill-drift', Accept: 'application/vnd.github+json' }; +if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`; + +const response = await fetch( + `https://api.github.com/repos/${REPO}/git/trees/main?recursive=1`, + { headers } +); +if (!response.ok) { + console.error(`GitHub API responded ${response.status} while reading ${REPO}.`); + process.exit(1); +} +const tree = await response.json(); +if (tree.truncated) { + console.error('Upstream tree is truncated; the comparison would be incomplete.'); + process.exit(1); +} + +const stable = new Map(); // skill -> category +const published = new Map(); // skill -> category +for (const node of tree.tree ?? []) { + const match = /^skills\/([^/]+)\/([^/]+)\/SKILL\.md$/.exec(node.path ?? ''); + if (!match) continue; + const [, category, skill] = match; + published.set(skill, category); + if (STABLE_CATEGORIES.has(category)) stable.set(skill, category); +} + +const standard = [...MATT_POCOCK_SKILLS]; +const missing = standard.filter((skill) => !stable.has(skill)); +const unpicked = [...stable.keys()].filter((skill) => !standard.includes(skill)); + +if (missing.length || unpicked.length) { + if (missing.length) { + console.error( + `\n${missing.length} standard skill(s) are no longer in ${[...STABLE_CATEGORIES].join('/')}:` + ); + for (const skill of missing) { + const where = published.has(skill) ? `moved to ${published.get(skill)}` : 'renamed or deleted'; + console.error(` - ${skill} (${where})`); + } + console.error(' `aginit init` would fail for these names. Update MATT_POCOCK_SKILLS.'); + } + if (unpicked.length) { + console.error(`\n${unpicked.length} stable skill(s) upstream are missing from MATT_POCOCK_SKILLS:`); + for (const skill of unpicked) console.error(` - ${skill} (${stable.get(skill)})`); + console.error(' Add them to MATT_POCOCK_SKILLS, or move them out of the stable categories.'); + } + console.error(''); + process.exit(1); +} + +console.log( + `All ${standard.length} standard skills verified against ${REPO} ` + + `(${published.size} published, ${stable.size} in stable categories).` +); diff --git a/scripts/smoke-test.mjs b/scripts/smoke-test.mjs index 8d850fd..263c170 100644 --- a/scripts/smoke-test.mjs +++ b/scripts/smoke-test.mjs @@ -94,7 +94,8 @@ try { ); const entry = path.join(workspace, 'node_modules', '@antomanc', 'aginit', 'bin', 'aginit.js'); const cli = (args, cwd = projectsDir) => run(process.execPath, [entry, ...args], cwd); - assert.equal(cli(['--version']).trim(), '0.1.0'); + const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); + assert.equal(cli(['--version']).trim(), pkg.version); cli(['new', 'dry-run', '--preset', 'web', '--framework', 'vite', '--dry-run', ...flags], projectsDir); assert(!fs.existsSync(path.join(projectsDir, 'dry-run'))); for (const [name, preset, framework] of [ diff --git a/src/adapters/agents-md.ts b/src/adapters/agents-md.ts index e6c14ea..5f23337 100644 --- a/src/adapters/agents-md.ts +++ b/src/adapters/agents-md.ts @@ -31,24 +31,45 @@ export function generateAgentsMarkdown( testE2E = `- Test (E2E / Browser): \`${pmAdapter.runCmd('test:e2e')}\``; } - const selectedSkills = config.skills.sources.flatMap((source) => source.skills); - const engineering = selectedSkills.filter((skill) => - ['tdd', 'code-review', 'diagnosing-bugs'].includes(skill) - ); + // A source with an empty skill list (or `*`) installs every skill it ships. + const skillInstalled = (skill: string) => + config.skills.sources.some( + (source) => + source.skills.length === 0 || source.skills.includes('*') || source.skills.includes(skill) + ); + const mattSkills = (() => { + const source = config.skills.sources.find((s) => s.package === 'mattpocock/skills'); + if (!source) return []; + return source.skills.length ? source.skills : ['*']; + })(); + const hasSkills = config.skills.sources.length > 0; + + const specSkillNames = ['to-spec', 'to-tickets', 'implement-spec']; + let engineeringCapability = ''; + if (mattSkills.includes('*')) { + engineeringCapability = + '- **Engineering**: the full Matt Pocock skill set installed in `.agents/skills/` — start with `ask-matt` to route to the right one.'; + } else if (mattSkills.length) { + const engineering = mattSkills.filter((skill) => !specSkillNames.includes(skill)); + if (engineering.length) { + engineeringCapability = `- **Engineering**: ${engineering.map((skill) => '`' + skill + '`').join(', ')}`; + } + } + let proseCapability = ''; - if (selectedSkills.includes('humanizer')) { + if (skillInstalled('humanizer')) { proseCapability = '- **Prose & Copy**: Use `humanizer` skill to eliminate AI writing tells and refine documentation.'; } let uiCapability = ''; - if (isWeb && selectedSkills.includes('impeccable')) { + if (isWeb && skillInstalled('impeccable')) { uiCapability = '- **UI & Design**: Use `impeccable` skill for frontend polish, design critique, and token craft.'; } let browserCapability = ''; - if (hasVisualAgent && selectedSkills.includes('agent-browser')) { + if (hasVisualAgent && skillInstalled('agent-browser')) { browserCapability = '- **Browser Verification**: Use `agent-browser` skill for interactive inspection and visual QA.'; } @@ -59,12 +80,11 @@ export function generateAgentsMarkdown( '- **Codebase Graph**: Use `graft ask "" --source` or `graft map` for orientation before raw grep.'; } - let specWorkflow = ''; - if (config.skills.workflow === 'spec' && selectedSkills.includes('to-spec')) { - specWorkflow = '- **Spec & Tickets Workflow**: `to-spec`, `to-tickets`, `implement-spec`'; - } + const specCapability = skillInstalled('to-spec') + ? '- **Spec & Tickets Workflow**: `to-spec`, `to-tickets`, `implement-spec`' + : ''; - const sections = [ + const sections: Array = [ `# ${config.name}`, '', `AI-first ${config.preset} project configured with \`aginit\`.`, @@ -76,25 +96,23 @@ export function generateAgentsMarkdown( '', '## Essential Commands', `- Package Manager: \`${pm}\``, - devCommand, - hasCommand('build') ? `- Build: \`${pmAdapter.runCmd('build')}\`` : '', - hasCommand('typecheck') ? `- Typecheck: \`${pmAdapter.runCmd('typecheck')}\`` : '', - hasCommand('test') ? `- Test: \`${pmAdapter.runCmd('test')}\`` : '', - hasCommand('test:unit') ? `- Unit Test: \`${pmAdapter.runCmd('test:unit')}\`` : '', - testE2E, + devCommand || undefined, + hasCommand('build') ? `- Build: \`${pmAdapter.runCmd('build')}\`` : undefined, + hasCommand('typecheck') ? `- Typecheck: \`${pmAdapter.runCmd('typecheck')}\`` : undefined, + hasCommand('test') ? `- Test: \`${pmAdapter.runCmd('test')}\`` : undefined, + hasCommand('test:unit') ? `- Unit Test: \`${pmAdapter.runCmd('test:unit')}\`` : undefined, + testE2E || undefined, '', '## AI Capabilities & Skills', - selectedSkills.length + hasSkills ? 'Configured skills install into `.agents/skills/` (shared by Antigravity and Codex):' : 'Skills installation is disabled in this configuration.', - engineering.length - ? `- **Engineering**: ${engineering.map((skill) => '\`' + skill + '\`').join(', ')}` - : '', - proseCapability, - specWorkflow, - uiCapability, - browserCapability, - graftCapability, + engineeringCapability || undefined, + proseCapability || undefined, + specCapability || undefined, + uiCapability || undefined, + browserCapability || undefined, + graftCapability || undefined, '', '## Bootstrap & First Session', 'When the user asks to "bootstrap this project" or kicks off a new initiative:', @@ -104,9 +122,11 @@ export function generateAgentsMarkdown( '' ]; + // Conditional lines are `undefined` and dropped; `''` entries are the blank + // separators between sections and must survive. return ( sections - .filter((s) => s !== '') + .filter((s): s is string => s !== undefined) .join('\n') .trim() + '\n' ); diff --git a/src/adapters/git.ts b/src/adapters/git.ts index e6d9b69..21b93af 100644 --- a/src/adapters/git.ts +++ b/src/adapters/git.ts @@ -33,6 +33,8 @@ blob-report/ # Codebase intelligence cache (graft builds locally, git-ignored) /graft/ +# ripgrep hint graft writes next to that cache; regenerated by \`graft build\` +.ignore `; export async function setupGit( diff --git a/src/adapters/skills.ts b/src/adapters/skills.ts index 4ed50d6..2fe8be3 100644 --- a/src/adapters/skills.ts +++ b/src/adapters/skills.ts @@ -81,25 +81,28 @@ export async function installAllSkills( } export function getInstalledSkills(targetDir: string): string[] { + // The lockfile only records what the skills CLI installed through aginit; + // skills added by hand live on disk only, so report the union of both. + const installed = new Set(); + const lockPath = path.join(targetDir, 'skills-lock.json'); if (fileExists(lockPath)) { const lock = readJsonFile(lockPath); - if (lock && lock.skills) { - return Object.keys(lock.skills); - } + for (const name of Object.keys(lock?.skills ?? {})) installed.add(name); } - // Check .agents/skills directory directly const skillsDir = path.join(targetDir, '.agents', 'skills'); if (fileExists(skillsDir)) { try { - return fs.readdirSync(skillsDir).filter((f) => !f.startsWith('.')); + for (const entry of fs.readdirSync(skillsDir)) { + if (!entry.startsWith('.')) installed.add(entry); + } } catch { - return []; + // Unreadable directory: keep whatever the lockfile reported. } } - return []; + return [...installed]; } export async function updateSkills( diff --git a/src/cli/doctor.ts b/src/cli/doctor.ts index 17e0ca1..e56c590 100644 --- a/src/cli/doctor.ts +++ b/src/cli/doctor.ts @@ -198,18 +198,20 @@ export async function runDoctor(targetDir: string = process.cwd()): Promise 0, - detail: - installedSkills.length > 0 + ok: skillsDisabled || installedSkills.length > 0, + detail: skillsDisabled + ? 'Disabled in configuration (skills.sources is empty)' + : installedSkills.length > 0 ? `${installedSkills.length} skills installed: ${installedSkills.join(', ')}` : 'No skills found in .agents/skills/', suggestion: - installedSkills.length === 0 - ? 'Run `aginit init` or `npx skills add ...` to install skills' - : undefined + skillsDisabled || installedSkills.length > 0 + ? undefined + : 'Run `aginit init` or `npx skills add ...` to install skills' }); // Print grouped results diff --git a/src/cli/main.ts b/src/cli/main.ts index ccefe27..3ca1515 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -1,4 +1,5 @@ import { Command } from 'commander'; +import { readFileSync } from 'node:fs'; import { createProject } from './new.js'; import { initCurrentDirectory } from './init.js'; import { runDoctor } from './doctor.js'; @@ -14,15 +15,26 @@ import { runInteractiveWizard } from './wizard.js'; +/** + * Read the version from package.json so releases only bump one place. + * Resolves from `src/cli/main.ts` and from `dist/cli/main.js` alike. + */ +function readVersion(): string { + try { + const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')); + return typeof pkg.version === 'string' ? pkg.version : '0.0.0'; + } catch { + return '0.0.0'; + } +} + export function createCliProgram(): Command { const program = new Command(); program .name('aginit') - .description( - 'Minimal, modular AI-first project bootstrapper for T3 Code, Antigravity, and Codex' - ) - .version('0.1.0'); + .description('Minimal, modular AI-first project bootstrapper for AI coding agents') + .version(readVersion()); // Root action: interactive wizard when in TTY, else display help program.action(async () => { @@ -40,10 +52,6 @@ export function createCliProgram(): Command { .option('-p, --preset ', 'Project preset: web | cli | generic', 'generic') .option('-f, --framework ', 'Web framework: none | vite | next | existing', 'none') .option('-m, --package-manager ', 'Package manager: pnpm | npm | yarn | bun', 'pnpm') - .option( - '--spec-workflow', - 'Include full spec and tickets skills (to-spec, to-tickets, implement-spec)' - ) .option('--no-skills', 'Skip installing AI skills') .option('--no-graft', 'Skip codebase graph (Graft) setup') .option('--no-git', 'Skip git repository initialization') @@ -53,7 +61,6 @@ export function createCliProgram(): Command { let finalPreset = options.preset; let finalFramework = options.framework; let finalPackageManager = options.packageManager; - let finalSpecWorkflow = options.specWorkflow; if (!finalName) { if (!isInteractive()) { @@ -71,24 +78,18 @@ export function createCliProgram(): Command { packageManager: command.getOptionValueSource('packageManager') === 'cli' ? (options.packageManager as PackageManager) - : undefined, - specWorkflow: - command.getOptionValueSource('specWorkflow') === 'cli' - ? options.specWorkflow : undefined }); finalName = answers.projectName; finalPreset = answers.preset; finalFramework = answers.framework; finalPackageManager = answers.packageManager; - finalSpecWorkflow = answers.specWorkflow; } const complete = await createProject(finalName, { preset: finalPreset as PresetType, framework: finalFramework as WebFramework, packageManager: finalPackageManager as PackageManager, - specWorkflow: finalSpecWorkflow, skills: options.skills, graft: options.graft, git: options.git, @@ -104,10 +105,6 @@ export function createCliProgram(): Command { .option('-p, --preset ', 'Project preset: web | cli | generic') .option('-f, --framework ', 'Web framework: none | vite | next | existing') .option('-m, --package-manager ', 'Package manager: pnpm | npm | yarn | bun') - .option( - '--spec-workflow', - 'Include full spec and tickets skills (to-spec, to-tickets, implement-spec)' - ) .option('--no-skills', 'Skip installing AI skills') .option('--no-graft', 'Skip codebase graph (Graft) setup') .option('--no-git', 'Skip git repository initialization') @@ -116,32 +113,27 @@ export function createCliProgram(): Command { let finalPreset = options.preset; let finalFramework = options.framework; let finalPackageManager = options.packageManager; - let finalSpecWorkflow = options.specWorkflow; const isPresetExplicit = command.getOptionValueSource('preset') === 'cli'; const isFrameworkExplicit = command.getOptionValueSource('framework') === 'cli'; const isPmExplicit = command.getOptionValueSource('packageManager') === 'cli'; - const isSpecExplicit = command.getOptionValueSource('specWorkflow') === 'cli'; const savedConfig = readProjectConfig(process.cwd()); if (isInteractive() && !isPresetExplicit && !options.dryRun && !savedConfig) { const answers = await promptInitProject({ preset: isPresetExplicit ? (options.preset as PresetType) : undefined, framework: isFrameworkExplicit ? (options.framework as WebFramework) : undefined, - packageManager: isPmExplicit ? (options.packageManager as PackageManager) : undefined, - specWorkflow: isSpecExplicit ? options.specWorkflow : undefined + packageManager: isPmExplicit ? (options.packageManager as PackageManager) : undefined }); finalPreset = answers.preset; finalFramework = answers.framework; finalPackageManager = answers.packageManager; - finalSpecWorkflow = answers.specWorkflow; } const complete = await initCurrentDirectory({ preset: finalPreset as PresetType, framework: finalFramework as WebFramework, packageManager: finalPackageManager as PackageManager, - specWorkflow: finalSpecWorkflow, skills: options.skills, graft: options.graft, git: options.git, diff --git a/src/cli/new.ts b/src/cli/new.ts index 6d4c580..62e2b7e 100644 --- a/src/cli/new.ts +++ b/src/cli/new.ts @@ -1,6 +1,6 @@ import path from 'node:path'; import pc from 'picocolors'; -import { getDefaultConfig } from '../config/defaults.js'; +import { getDefaultConfig, upgradeLegacySkillConfig } from '../config/defaults.js'; import { AGINIT_CONFIG_FILENAME, PresetType, @@ -28,7 +28,6 @@ export interface NewProjectOptions { preset?: PresetType; framework?: WebFramework; packageManager?: PackageManager; - specWorkflow?: boolean; skills?: boolean; graft?: boolean; git?: boolean; @@ -73,8 +72,7 @@ export async function createProject( const presetHandler = getPreset(presetName); const defaults = getDefaultConfig(projectName, presetName, { framework, - packageManager, - specWorkflow: options.specWorkflow + packageManager }); const config = validateConfig( existingConfig @@ -88,22 +86,7 @@ export async function createProject( options.framework !== undefined || options.preset !== undefined ? { ...existingConfig.browser, ...defaults.browser } : existingConfig.browser, - skills: - options.specWorkflow !== undefined - ? { - ...existingConfig.skills, - workflow: defaults.skills.workflow, - sources: existingConfig.skills.sources.map((source) => - source.package === 'mattpocock/skills' - ? { - ...source, - skills: defaults.skills.sources.find((s) => s.package === source.package)! - .skills - } - : source - ) - } - : existingConfig.skills + skills: upgradeLegacySkillConfig(existingConfig.skills) } : defaults ); @@ -189,9 +172,10 @@ export async function createProject( ); console.log(); console.log('Next steps:'); - console.log(pc.cyan(` cd ${path.relative(process.cwd(), targetDir) || '.'}`)); + const relativeTarget = path.relative(process.cwd(), targetDir); + if (relativeTarget) console.log(pc.cyan(` cd ${relativeTarget}`)); console.log(pc.cyan(` ${packageManager} install`)); - console.log(pc.cyan(' Open the repository in T3 Code')); + console.log(pc.cyan(' Open the repository in your AI coding agent')); console.log(pc.dim(' Prompt the agent: ') + pc.bold(pc.green('"bootstrap this project"'))); console.log(); } diff --git a/src/cli/wizard.ts b/src/cli/wizard.ts index 6087d47..f6affa9 100644 --- a/src/cli/wizard.ts +++ b/src/cli/wizard.ts @@ -23,14 +23,12 @@ export interface WizardNewAnswers { preset: PresetType; framework: WebFramework; packageManager: PackageManager; - specWorkflow: boolean; } export interface WizardInitAnswers { preset: PresetType; framework: WebFramework; packageManager: PackageManager; - specWorkflow: boolean; } export async function promptNewProject(defaults?: { @@ -38,7 +36,6 @@ export async function promptNewProject(defaults?: { preset?: PresetType; framework?: WebFramework; packageManager?: PackageManager; - specWorkflow?: boolean; }): Promise { let projectName: string = defaults?.projectName || ''; if (!projectName) { @@ -101,28 +98,6 @@ export async function promptNewProject(defaults?: { framework = assertNotCancelled(res) as WebFramework; } - let specWorkflow = defaults?.specWorkflow; - if (specWorkflow === undefined) { - const res = await p.select({ - message: 'Select AI engineering workflow:', - initialValue: 'minimal', - options: [ - { - value: 'minimal', - label: 'Standard Workflow (Recommended)', - hint: 'TDD, Code Review, Diagnosing Bugs' - }, - { - value: 'spec', - label: 'Spec & Tickets Workflow', - hint: 'Adds to-spec, to-tickets, implement-spec' - } - ] - }); - const selected = assertNotCancelled(res); - specWorkflow = selected === 'spec'; - } - let packageManager = defaults?.packageManager; if (!packageManager) { const res = await p.select({ @@ -146,8 +121,7 @@ export async function promptNewProject(defaults?: { projectName, preset, framework, - packageManager, - specWorkflow + packageManager }; } @@ -156,7 +130,6 @@ export async function promptInitProject(defaults?: { preset?: PresetType; framework?: WebFramework; packageManager?: PackageManager; - specWorkflow?: boolean; }): Promise { const targetDir = defaults?.targetDir || process.cwd(); @@ -197,28 +170,6 @@ export async function promptInitProject(defaults?: { framework = assertNotCancelled(res) as WebFramework; } - let specWorkflow = defaults?.specWorkflow; - if (specWorkflow === undefined) { - const res = await p.select({ - message: 'Select AI engineering workflow:', - initialValue: 'minimal', - options: [ - { - value: 'minimal', - label: 'Standard Workflow (Recommended)', - hint: 'TDD, Code Review, Diagnosing Bugs' - }, - { - value: 'spec', - label: 'Spec & Tickets Workflow', - hint: 'Adds to-spec, to-tickets, implement-spec' - } - ] - }); - const selected = assertNotCancelled(res); - specWorkflow = selected === 'spec'; - } - // Auto-detect and preserve existing package manager without prompting let packageManager = defaults?.packageManager; if (!packageManager) { @@ -243,8 +194,7 @@ export async function promptInitProject(defaults?: { return { preset, framework, - packageManager, - specWorkflow + packageManager }; } @@ -277,8 +227,7 @@ export async function runInteractiveWizard(): Promise { complete = await createProject(answers.projectName, { preset: answers.preset, framework: answers.framework, - packageManager: answers.packageManager, - specWorkflow: answers.specWorkflow + packageManager: answers.packageManager }); } else { const saved = readProjectConfig(process.cwd()); @@ -291,8 +240,7 @@ export async function runInteractiveWizard(): Promise { complete = await initCurrentDirectory({ preset: answers.preset, framework: answers.framework, - packageManager: answers.packageManager, - specWorkflow: answers.specWorkflow + packageManager: answers.packageManager }); } } diff --git a/src/config/defaults.ts b/src/config/defaults.ts index e39530c..440da7f 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -1,21 +1,61 @@ -import { AginitConfig, PresetType, WebFramework, PackageManager } from './schema.js'; +import { + AginitConfig, + PresetType, + WebFramework, + PackageManager, + SkillSourceConfig +} from './schema.js'; export interface DefaultConfigOptions { framework?: WebFramework; - specWorkflow?: boolean; packageManager?: PackageManager; } +/** + * The stable skill set from mattpocock/skills installed by default: + * every skill in the `engineering` and `productivity` categories. + * + * The `misc` (rarely used) and `in-progress` categories are deliberately + * excluded. Re-evaluate when the upstream repository reshuffles categories. + */ +export const MATT_POCOCK_SKILLS = [ + // engineering + 'ask-matt', + 'code-review', + 'codebase-design', + 'diagnosing-bugs', + 'domain-modeling', + 'grill-with-docs', + 'implement', + 'implement-spec', + 'improve-codebase-architecture', + 'pr', + 'prototype', + 'research', + 'retro', + 'setup-matt-pocock-skills', + 'tdd', + 'to-spec', + 'to-tickets', + 'triage', + 'wayfinder', + 'wizard', + // productivity + 'grill-me', + 'grilling', + 'handoff', + 'teach', + 'to-questionnaire', + 'wait-what', + 'writing-for-agents' +] as const; + export function getDefaultConfig( name: string, preset: PresetType, options: DefaultConfigOptions = {} ): AginitConfig { - const specWorkflow = !!options.specWorkflow; const packageManager = options.packageManager || 'pnpm'; - const mattSkills = specWorkflow - ? ['tdd', 'code-review', 'diagnosing-bugs', 'to-spec', 'to-tickets', 'implement-spec'] - : ['tdd', 'code-review', 'diagnosing-bugs']; switch (preset) { case 'web': { @@ -33,11 +73,10 @@ export function getDefaultConfig( secondary: 'codex' }, skills: { - workflow: specWorkflow ? 'spec' : 'minimal', sources: [ { package: 'mattpocock/skills', - skills: mattSkills + skills: [...MATT_POCOCK_SKILLS] }, { package: 'blader/humanizer', @@ -80,11 +119,10 @@ export function getDefaultConfig( secondary: 'codex' }, skills: { - workflow: specWorkflow ? 'spec' : 'minimal', sources: [ { package: 'mattpocock/skills', - skills: mattSkills + skills: [...MATT_POCOCK_SKILLS] }, { package: 'blader/humanizer', @@ -119,11 +157,10 @@ export function getDefaultConfig( secondary: 'codex' }, skills: { - workflow: specWorkflow ? 'spec' : 'minimal', sources: [ { package: 'mattpocock/skills', - skills: mattSkills + skills: [...MATT_POCOCK_SKILLS] }, { package: 'blader/humanizer', @@ -147,3 +184,39 @@ export function getDefaultConfig( }; } } + +/** The skill selections that older versions of aginit wrote as defaults. */ +const LEGACY_MATT_SKILL_SELECTIONS: string[][] = [ + ['tdd', 'code-review', 'diagnosing-bugs'], + ['tdd', 'code-review', 'diagnosing-bugs', 'to-spec', 'to-tickets', 'implement-spec'] +]; + +function matchesLegacySelection(skills: string[]): boolean { + return LEGACY_MATT_SKILL_SELECTIONS.some( + (selection) => + selection.length === skills.length && selection.every((skill) => skills.includes(skill)) + ); +} + +/** + * Bring a saved `skills` section forward to the current standard: install the + * full stable Matt Pocock set and drop the retired workflow selector. + * + * Only the exact selections aginit used to write are migrated, so a project + * whose skill list was narrowed by hand keeps the list its author chose. + */ +export function upgradeLegacySkillConfig( + skills: AginitConfig['skills'] +): AginitConfig['skills'] { + const sources: SkillSourceConfig[] = skills.sources.map((source) => + source.package === 'mattpocock/skills' && matchesLegacySelection(source.skills) + ? { ...source, skills: [...MATT_POCOCK_SKILLS] } + : source + ); + const migrated = sources.some((source, index) => source !== skills.sources[index]); + if (!migrated) return skills; + + const upgraded: AginitConfig['skills'] = { ...skills, sources }; + delete upgraded.workflow; + return upgraded; +} diff --git a/src/config/schema.ts b/src/config/schema.ts index 51ccc08..24fd686 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -18,6 +18,11 @@ export interface AginitConfig { secondary: string; }; skills: { + /** + * @deprecated Legacy selector from when only a subset of skills was + * installed. All standard skills are installed now, so this field is no + * longer written. It is still accepted so existing configs keep loading. + */ workflow?: 'minimal' | 'spec'; sources: SkillSourceConfig[]; }; diff --git a/tests/agents-md.test.ts b/tests/agents-md.test.ts index ca57a0c..51785e7 100644 --- a/tests/agents-md.test.ts +++ b/tests/agents-md.test.ts @@ -72,4 +72,25 @@ describe('AGENTS.md generation & safety', () => { fs.rmSync(tempDir, { recursive: true, force: true }); } }); + + it('separates sections with blank lines while dropping inapplicable lines', () => { + const config = getDefaultConfig('demo-cli', 'cli'); + const md = generateAgentsMarkdown(config); + + expect(md).toContain('# demo-cli\n\nAI-first cli project configured'); + expect(md).toContain('## Core Invariants\n- Keep changes small'); + expect(md).toContain('\n\n## Essential Commands'); + expect(md).toContain('\n\n## AI Capabilities & Skills'); + expect(md).toContain('\n\n## Bootstrap & First Session'); + expect(md).not.toContain('\n\n\n'); + expect(md.endsWith('\n')).toBe(true); + }); + + it('reports skills as disabled only when no source is configured', () => { + const config = getDefaultConfig('demo-off', 'generic'); + config.skills.sources = []; + const md = generateAgentsMarkdown(config); + expect(md).toContain('Skills installation is disabled'); + expect(md).not.toContain('**Engineering**'); + }); }); diff --git a/tests/config.test.ts b/tests/config.test.ts index b6fc46d..90a698b 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -1,8 +1,12 @@ import { describe, it, expect } from 'vitest'; -import { getDefaultConfig } from '../src/config/defaults.js'; +import { + getDefaultConfig, + upgradeLegacySkillConfig, + MATT_POCOCK_SKILLS +} from '../src/config/defaults.js'; describe('Configuration & Defaults', () => { - it('generates correct config for web preset with simplified Matt skills and schemaVersion', () => { + it('generates correct config for web preset with the full stable Matt skill set and schemaVersion', () => { const config = getDefaultConfig('my-web-app', 'web'); expect(config.schemaVersion).toBe('1.0.0'); expect(config.name).toBe('my-web-app'); @@ -18,8 +22,9 @@ describe('Configuration & Defaults', () => { const mattSource = config.skills.sources.find((s) => s.package === 'mattpocock/skills'); expect(mattSource).toBeDefined(); - // Default Matt skills must only contain the universally useful trio - expect(mattSource?.skills).toEqual(['tdd', 'code-review', 'diagnosing-bugs']); + // The whole stable Matt Pocock set is installed as standard + expect(mattSource?.skills).toEqual([...MATT_POCOCK_SKILLS]); + expect(mattSource?.skills).toHaveLength(27); const packages = config.skills.sources.map((s) => s.package); expect(packages).toContain('blader/humanizer'); @@ -38,13 +43,35 @@ describe('Configuration & Defaults', () => { expect(configBun.packageManager).toBe('bun'); }); - it('supports optional specWorkflow in config', () => { - const config = getDefaultConfig('my-spec-app', 'web', { specWorkflow: true }); - expect(config.skills.workflow).toBe('spec'); + it('installs every standard skill without a workflow selector', () => { + const config = getDefaultConfig('my-spec-app', 'web'); const mattSource = config.skills.sources.find((s) => s.package === 'mattpocock/skills'); expect(mattSource?.skills).toContain('to-spec'); expect(mattSource?.skills).toContain('to-tickets'); expect(mattSource?.skills).toContain('implement-spec'); + // Upstream skills outside the stable categories stay out of the standard set + expect(mattSource?.skills).not.toContain('chief-of-staff'); // in-progress + expect(mattSource?.skills).not.toContain('setup-pre-commit'); // misc + expect(config.skills.workflow).toBeUndefined(); + }); + + it('upgrades saved legacy skill selections to the current standard', () => { + const legacy = { + workflow: 'minimal' as const, + sources: [ + { package: 'mattpocock/skills', skills: ['tdd', 'code-review', 'diagnosing-bugs'] }, + { package: 'blader/humanizer', skills: ['humanizer'] } + ] + }; + const upgraded = upgradeLegacySkillConfig(legacy); + expect(upgraded.workflow).toBeUndefined(); + expect(upgraded.sources[0].skills).toEqual([...MATT_POCOCK_SKILLS]); + expect(upgraded.sources[1]).toBe(legacy.sources[1]); + }); + + it('preserves skill selections that were narrowed by hand', () => { + const custom = { sources: [{ package: 'mattpocock/skills', skills: ['tdd'] }] }; + expect(upgradeLegacySkillConfig(custom)).toBe(custom); }); it('supports custom web framework choice and enables playwright when web target exists', () => { @@ -67,7 +94,7 @@ describe('Configuration & Defaults', () => { expect(config.codebase.graft).toBe(true); const mattSource = config.skills.sources.find((s) => s.package === 'mattpocock/skills'); - expect(mattSource?.skills).toEqual(['tdd', 'code-review', 'diagnosing-bugs']); + expect(mattSource?.skills).toEqual([...MATT_POCOCK_SKILLS]); expect(config.skills.sources.map((s) => s.package)).toContain('blader/humanizer'); expect(config.skills.sources.map((s) => s.package)).not.toContain('pbakaus/impeccable'); }); diff --git a/tests/git.test.ts b/tests/git.test.ts index 19e881a..28c9cfc 100644 --- a/tests/git.test.ts +++ b/tests/git.test.ts @@ -40,7 +40,7 @@ describe('Git Adapter', () => { it('still generates .gitignore even if git init fails', async () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'git-test-fail-')); - const spy = vi.spyOn(shell, 'runExecutable').mockImplementation(async (_cmd, args) => { + const spy = vi.spyOn(shell, 'runExecutable').mockImplementation(async (_cmd, args = []) => { if (args[0] === 'init') { return { ok: false, exitCode: 1, stdout: '', stderr: 'git init failed' }; } @@ -61,7 +61,7 @@ describe('Git Adapter', () => { it('detects when directory is already inside a git work tree and avoids nested git init', async () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'git-test-worktree-')); - const spy = vi.spyOn(shell, 'runExecutable').mockImplementation(async (_cmd, args) => { + const spy = vi.spyOn(shell, 'runExecutable').mockImplementation(async (_cmd, args = []) => { if (args.includes('--is-inside-work-tree')) { return { ok: true, exitCode: 0, stdout: 'true', stderr: '' }; } diff --git a/tests/interactive.test.ts b/tests/interactive.test.ts index 99d95b2..ff0d628 100644 --- a/tests/interactive.test.ts +++ b/tests/interactive.test.ts @@ -29,7 +29,6 @@ it('reports incomplete new setup from the root wizard', async () => { mocks.select .mockResolvedValueOnce('new') .mockResolvedValueOnce('generic') - .mockResolvedValueOnce('minimal') .mockResolvedValueOnce('npm'); mocks.text.mockResolvedValue('app'); mocks.create.mockResolvedValue(false); diff --git a/tests/release.test.ts b/tests/release.test.ts index e5aa724..3f27c56 100644 --- a/tests/release.test.ts +++ b/tests/release.test.ts @@ -6,7 +6,7 @@ import { createProject } from '../src/cli/new.js'; import { initCurrentDirectory } from '../src/cli/init.js'; import { createCliProgram } from '../src/cli/main.js'; import { runDoctor } from '../src/cli/doctor.js'; -import { installSkillSource } from '../src/adapters/skills.js'; +import { installSkillSource, getInstalledSkills } from '../src/adapters/skills.js'; import { setupGit } from '../src/adapters/git.js'; import { getDefaultConfig } from '../src/config/defaults.js'; @@ -82,8 +82,7 @@ it('preserves saved config and extension fields through the actual init CLI', as const config = { ...getDefaultConfig('existing', 'web', { framework: 'vite', - packageManager: 'npm', - specWorkflow: true + packageManager: 'npm' }), extension: { custom: true } }; @@ -111,13 +110,14 @@ it('applies explicit init overrides without dropping extensions', async () => { JSON.stringify({ name: 'existing', type: 'module' }) ); process.chdir(dir); - await initCurrentDirectory({ ...options, packageManager: 'bun', specWorkflow: true }); - expect(read('aginit.config.json')).toMatchObject({ + await initCurrentDirectory({ ...options, packageManager: 'bun' }); + const applied = read('aginit.config.json'); + expect(applied).toMatchObject({ preset: 'web', packageManager: 'bun', - extension: 42, - skills: { workflow: 'spec' } + extension: 42 }); + expect(applied.skills).toEqual(config.skills); }); it('rejects invalid existing configuration before changing any files', async () => { fs.writeFileSync(path.join(dir, 'aginit.config.json'), '{}'); @@ -343,3 +343,45 @@ it('sanitizes non-compliant directory names when running init without manifest', const config = JSON.parse(fs.readFileSync(path.join(customSubDir, 'aginit.config.json'), 'utf8')); expect(config.name).toBe('my-uppercase-app'); }); + +it('reports skills on disk alongside lockfile entries', () => { + fs.mkdirSync(path.join(dir, '.agents', 'skills', 'tdd'), { recursive: true }); + fs.writeFileSync( + path.join(dir, 'skills-lock.json'), + JSON.stringify({ + version: 1, + skills: { + humanizer: { + source: 'blader/humanizer', + sourceType: 'github', + skillPath: 'SKILL.md', + computedHash: 'hash' + } + } + }) + ); + expect(getInstalledSkills(dir)).toEqual(expect.arrayContaining(['tdd', 'humanizer'])); + + // An empty lockfile must not hide skills that were installed by hand + fs.writeFileSync(path.join(dir, 'skills-lock.json'), JSON.stringify({ version: 1, skills: {} })); + expect(getInstalledSkills(dir)).toEqual(['tdd']); +}); + +it('does not report missing skills when installation is disabled in config', async () => { + const config = getDefaultConfig('app', 'generic'); + config.skills.sources = []; + config.codebase.graft = false; + fs.writeFileSync(path.join(dir, 'aginit.config.json'), JSON.stringify(config)); + await runDoctor(dir); + const output = log.mock.calls.flat().join('\n'); + expect(output).toContain('Disabled in configuration'); + expect(output).not.toContain('No skills found'); +}); + +it('omits the cd step when the target is the current directory', async () => { + process.chdir(dir); + await createProject('app', { ...options, targetDir: dir, silent: false }); + const output = log.mock.calls.flat().join('\n'); + expect(output).toContain('is ready for development'); + expect(output).not.toContain('cd .'); +}); diff --git a/tests/wizard.test.ts b/tests/wizard.test.ts index e4562df..1c2339e 100644 --- a/tests/wizard.test.ts +++ b/tests/wizard.test.ts @@ -33,28 +33,24 @@ describe('Wizard & Interactive Prompts', () => { const answers = await promptNewProject({ projectName: 'custom-app', preset: 'cli', - packageManager: 'bun', - specWorkflow: false + packageManager: 'bun' }); expect(answers.projectName).toBe('custom-app'); expect(answers.preset).toBe('cli'); expect(answers.framework).toBe('none'); expect(answers.packageManager).toBe('bun'); - expect(answers.specWorkflow).toBe(false); }); it('uses provided defaults for init project', async () => { const answers = await promptInitProject({ preset: 'generic', - packageManager: 'yarn', - specWorkflow: true + packageManager: 'yarn' }); expect(answers.preset).toBe('generic'); expect(answers.framework).toBe('existing'); expect(answers.packageManager).toBe('yarn'); - expect(answers.specWorkflow).toBe(true); }); it('auto-detects and preserves existing package manager in init project without prompting', async () => { @@ -63,8 +59,7 @@ describe('Wizard & Interactive Prompts', () => { fs.writeFileSync(path.join(tmpDir, 'yarn.lock'), ''); const answers = await promptInitProject({ targetDir: tmpDir, - preset: 'generic', - specWorkflow: false + preset: 'generic' }); expect(answers.packageManager).toBe('yarn'); diff --git a/tsconfig.json b/tsconfig.json index 7c09fca..06b8863 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "declaration": true, "declarationMap": true, "sourceMap": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "types": ["node"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "templates", "tests"] diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..19e64e3 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "rootDir": ".", + "declaration": false, + "declarationMap": false, + "sourceMap": false + }, + "include": ["src/**/*", "tests/**/*", "vitest.config.ts"], + "exclude": ["node_modules", "dist", "templates"] +} From 6c7c5e11c2f2f8a0cd7c8aee15b24b25700f8a0e Mon Sep 17 00:00:00 2001 From: Antonio Mancini Date: Fri, 9 Oct 2026 18:16:10 +0200 Subject: [PATCH 2/2] test: make the cd-step assertion platform-independent macos-latest failed on both Node versions: process.cwd() returns the symlink-resolved path there (/var -> /private/var), so handing createProject the raw os.tmpdir() path did not compare equal to cwd and the cd step was legitimately printed. Drive the target with process.cwd(), which is canonical everywhere, and match the next-steps line with a regex after stripping ANSI so the assertion also works when CI renders colors. --- tests/release.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/release.test.ts b/tests/release.test.ts index 3f27c56..c8865cb 100644 --- a/tests/release.test.ts +++ b/tests/release.test.ts @@ -380,8 +380,14 @@ it('does not report missing skills when installation is disabled in config', asy it('omits the cd step when the target is the current directory', async () => { process.chdir(dir); - await createProject('app', { ...options, targetDir: dir, silent: false }); - const output = log.mock.calls.flat().join('\n'); + // process.cwd() is canonical on every platform (macOS resolves /var to + // /private/var, so handing createProject the raw tmpdir path would not + // compare equal) and CI renders colors, so strip ANSI before matching. + await createProject('app', { ...options, targetDir: process.cwd(), silent: false }); + const output = log.mock.calls + .flat() + .join('\n') + .replace(/\x1b\[[0-9;]*m/g, ''); expect(output).toContain('is ready for development'); - expect(output).not.toContain('cd .'); + expect(output).not.toMatch(/\n\s+cd /); });