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..c8865cb 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,51 @@ 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); + // 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.toMatch(/\n\s+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"] +}