@glxmart/boss-cli 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/assets/claude-md/docs/conductor.md +139 -0
- package/assets/claude-md/docs/initialization.md +2 -2
- package/assets/claude-md/docs/workers.md +33 -54
- package/assets/claude-md/docs/workflow.md +23 -59
- package/assets/claude-md/template.md +198 -756
- package/assets/git-hooks/commit-msg.sh +4 -8
- package/assets/git-hooks/pre-commit.sh +0 -4
- package/assets/git-hooks/pre-push.sh +21 -0
- package/assets/github-workflows/CODEOWNERS +21 -2
- package/assets/github-workflows/boss-ci.yml +163 -23
- package/assets/github-workflows/boss-gates.yml +100 -13
- package/assets/template-docs/nextjs-app-turbo.md +412 -61
- package/assets/template-loader/gitignore +14 -0
- package/assets/worker-configs/architect/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/architect/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/architect/CLAUDE.md +99 -38
- package/assets/worker-configs/clarifier/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/clarifier/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/clarifier/CLAUDE.md +81 -40
- package/assets/worker-configs/code-reviewer/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/code-reviewer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/code-reviewer/.claude/skills/test-first-methodology.md +745 -0
- package/assets/worker-configs/code-reviewer/CLAUDE.md +85 -79
- package/assets/worker-configs/consolidator/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/consolidator/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/consolidator/CLAUDE.md +94 -88
- package/assets/worker-configs/developer-backend/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/developer-backend/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/developer-backend/.claude/skills/test-first-methodology.md +745 -0
- package/assets/worker-configs/developer-backend/CLAUDE.md +156 -56
- package/assets/worker-configs/developer-frontend/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/developer-frontend/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/developer-frontend/.claude/skills/test-first-methodology.md +745 -0
- package/assets/worker-configs/developer-frontend/CLAUDE.md +152 -54
- package/assets/worker-configs/developer-fullstack/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/developer-fullstack/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/developer-fullstack/.claude/skills/test-first-methodology.md +745 -0
- package/assets/worker-configs/developer-fullstack/CLAUDE.md +155 -57
- package/assets/worker-configs/devops-engineer/.claude/skills/infrastructure-as-code.md +794 -0
- package/assets/worker-configs/devops-engineer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/devops-engineer/CLAUDE.md +92 -85
- package/assets/worker-configs/planner/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/planner/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/planner/CLAUDE.md +143 -46
- package/assets/worker-configs/product-owner/CLAUDE.md +72 -82
- package/assets/worker-configs/reviewer/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/reviewer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/reviewer/CLAUDE.md +108 -50
- package/assets/worker-configs/security-engineer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/security-engineer/CLAUDE.md +83 -83
- package/assets/worker-configs/spec-writer/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/spec-writer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/spec-writer/CLAUDE.md +107 -48
- package/assets/worker-configs/technical-writer/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/technical-writer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/technical-writer/CLAUDE.md +91 -81
- package/assets/worker-configs/tester/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/tester/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/tester/.claude/skills/test-first-methodology.md +745 -0
- package/assets/worker-configs/tester/CLAUDE.md +141 -52
- package/dist/assets/claude-md/docs/conductor.md +139 -0
- package/dist/assets/claude-md/docs/initialization.md +2 -2
- package/dist/assets/claude-md/docs/workers.md +33 -54
- package/dist/assets/claude-md/docs/workflow.md +23 -59
- package/dist/assets/claude-md/template.md +198 -756
- package/dist/assets/git-hooks/commit-msg.sh +4 -8
- package/dist/assets/git-hooks/pre-commit.sh +0 -4
- package/dist/assets/git-hooks/pre-push.sh +21 -0
- package/dist/assets/github-workflows/CODEOWNERS +21 -2
- package/dist/assets/github-workflows/boss-ci.yml +163 -23
- package/dist/assets/github-workflows/boss-gates.yml +100 -13
- package/dist/assets/template-docs/nextjs-app-turbo.md +412 -61
- package/dist/assets/template-loader/gitignore +14 -0
- package/dist/assets/worker-configs/architect/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/architect/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/architect/CLAUDE.md +99 -38
- package/dist/assets/worker-configs/clarifier/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/clarifier/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/clarifier/CLAUDE.md +81 -40
- package/dist/assets/worker-configs/code-reviewer/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/code-reviewer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/code-reviewer/.claude/skills/test-first-methodology.md +745 -0
- package/dist/assets/worker-configs/code-reviewer/CLAUDE.md +85 -79
- package/dist/assets/worker-configs/consolidator/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/consolidator/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/consolidator/CLAUDE.md +94 -88
- package/dist/assets/worker-configs/developer-backend/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/developer-backend/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/developer-backend/.claude/skills/test-first-methodology.md +745 -0
- package/dist/assets/worker-configs/developer-backend/CLAUDE.md +156 -56
- package/dist/assets/worker-configs/developer-frontend/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/developer-frontend/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/developer-frontend/.claude/skills/test-first-methodology.md +745 -0
- package/dist/assets/worker-configs/developer-frontend/CLAUDE.md +152 -54
- package/dist/assets/worker-configs/developer-fullstack/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/developer-fullstack/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/developer-fullstack/.claude/skills/test-first-methodology.md +745 -0
- package/dist/assets/worker-configs/developer-fullstack/CLAUDE.md +155 -57
- package/dist/assets/worker-configs/devops-engineer/.claude/skills/infrastructure-as-code.md +794 -0
- package/dist/assets/worker-configs/devops-engineer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/devops-engineer/CLAUDE.md +92 -85
- package/dist/assets/worker-configs/planner/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/planner/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/planner/CLAUDE.md +143 -46
- package/dist/assets/worker-configs/product-owner/CLAUDE.md +72 -82
- package/dist/assets/worker-configs/reviewer/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/reviewer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/reviewer/CLAUDE.md +108 -50
- package/dist/assets/worker-configs/security-engineer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/security-engineer/CLAUDE.md +83 -83
- package/dist/assets/worker-configs/spec-writer/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/spec-writer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/spec-writer/CLAUDE.md +107 -48
- package/dist/assets/worker-configs/technical-writer/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/technical-writer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/technical-writer/CLAUDE.md +91 -81
- package/dist/assets/worker-configs/tester/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/tester/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/tester/.claude/skills/test-first-methodology.md +745 -0
- package/dist/assets/worker-configs/tester/CLAUDE.md +141 -52
- package/dist/commands/__tests__/bootstrap.test.js +51 -51
- package/dist/commands/__tests__/bootstrap.test.js.map +1 -1
- package/dist/commands/bootstrap.d.ts.map +1 -1
- package/dist/commands/bootstrap.js +23 -20
- package/dist/commands/bootstrap.js.map +1 -1
- package/dist/commands/doctor.js +33 -9
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/templates.d.ts +1 -1
- package/dist/commands/templates.d.ts.map +1 -1
- package/dist/commands/templates.js +1 -1
- package/dist/commands/templates.js.map +1 -1
- package/dist/constants.d.ts +2 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/generators/__tests__/boss-config.test.js +4 -4
- package/dist/generators/__tests__/boss-config.test.js.map +1 -1
- package/dist/generators/__tests__/claude-folder.test.js +8 -8
- package/dist/generators/__tests__/claude-folder.test.js.map +1 -1
- package/dist/generators/__tests__/claude-md.test.js +25 -16
- package/dist/generators/__tests__/claude-md.test.js.map +1 -1
- package/dist/generators/__tests__/container-use-config.test.js +2 -2
- package/dist/generators/__tests__/container-use-config.test.js.map +1 -1
- package/dist/generators/__tests__/git-hooks.test.js.map +1 -1
- package/dist/generators/__tests__/github-workflows.test.js +10 -6
- package/dist/generators/__tests__/github-workflows.test.js.map +1 -1
- package/dist/generators/__tests__/mcp-config.test.js +6 -6
- package/dist/generators/__tests__/project-structure.test.js +2 -2
- package/dist/generators/__tests__/quality-gates.test.js +11 -3
- package/dist/generators/__tests__/quality-gates.test.js.map +1 -1
- package/dist/generators/__tests__/specify-structure.test.js +1 -1
- package/dist/generators/__tests__/specify-structure.test.js.map +1 -1
- package/dist/generators/__tests__/template-docs.test.js +1 -1
- package/dist/generators/__tests__/template-docs.test.js.map +1 -1
- package/dist/generators/__tests__/template-loader.test.js +209 -59
- package/dist/generators/__tests__/template-loader.test.js.map +1 -1
- package/dist/generators/__tests__/worker-configs.test.js +7 -7
- package/dist/generators/boss-config.d.ts.map +1 -1
- package/dist/generators/boss-config.js +46 -51
- package/dist/generators/boss-config.js.map +1 -1
- package/dist/generators/claude-folder.d.ts +1 -1
- package/dist/generators/claude-folder.d.ts.map +1 -1
- package/dist/generators/claude-folder.js +5 -5
- package/dist/generators/claude-folder.js.map +1 -1
- package/dist/generators/claude-md.d.ts.map +1 -1
- package/dist/generators/claude-md.js +83 -42
- package/dist/generators/claude-md.js.map +1 -1
- package/dist/generators/container-use-config.d.ts.map +1 -1
- package/dist/generators/container-use-config.js +5 -7
- package/dist/generators/container-use-config.js.map +1 -1
- package/dist/generators/docker-compose.d.ts.map +1 -1
- package/dist/generators/docker-compose.js.map +1 -1
- package/dist/generators/git-hooks.d.ts.map +1 -1
- package/dist/generators/git-hooks.js +2 -2
- package/dist/generators/git-hooks.js.map +1 -1
- package/dist/generators/github-workflows.js +3 -3
- package/dist/generators/github-workflows.js.map +1 -1
- package/dist/generators/mcp-config.d.ts.map +1 -1
- package/dist/generators/mcp-config.js +18 -16
- package/dist/generators/mcp-config.js.map +1 -1
- package/dist/generators/project-structure.d.ts +1 -1
- package/dist/generators/project-structure.d.ts.map +1 -1
- package/dist/generators/project-structure.js +2 -2
- package/dist/generators/project-structure.js.map +1 -1
- package/dist/generators/quality-gates.d.ts.map +1 -1
- package/dist/generators/quality-gates.js +13 -5
- package/dist/generators/quality-gates.js.map +1 -1
- package/dist/generators/specify-structure.d.ts.map +1 -1
- package/dist/generators/specify-structure.js +1 -2
- package/dist/generators/specify-structure.js.map +1 -1
- package/dist/generators/template-docs.js +2 -2
- package/dist/generators/template-docs.js.map +1 -1
- package/dist/generators/template-loader.d.ts.map +1 -1
- package/dist/generators/template-loader.js +259 -143
- package/dist/generators/template-loader.js.map +1 -1
- package/dist/generators/worker-configs.d.ts.map +1 -1
- package/dist/generators/worker-configs.js +7 -5
- package/dist/generators/worker-configs.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/presets/__tests__/quality-presets.test.js +9 -5
- package/dist/presets/__tests__/quality-presets.test.js.map +1 -1
- package/dist/presets/quality-presets.d.ts.map +1 -1
- package/dist/presets/quality-presets.js +11 -11
- package/dist/presets/quality-presets.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/internal.d.ts +69 -0
- package/dist/types/internal.d.ts.map +1 -0
- package/dist/types/internal.js +4 -0
- package/dist/types/internal.js.map +1 -0
- package/dist/utils/__tests__/file-system.test.js +1 -1
- package/dist/utils/__tests__/file-system.test.js.map +1 -1
- package/dist/utils/__tests__/git.test.js.map +1 -1
- package/dist/utils/__tests__/template-loader.test.js.map +1 -1
- package/dist/utils/__tests__/validators.test.js +1 -1
- package/dist/utils/__tests__/validators.test.js.map +1 -1
- package/dist/utils/file-system.d.ts.map +1 -1
- package/dist/utils/file-system.js +1 -4
- package/dist/utils/file-system.js.map +1 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +17 -11
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/prompts.d.ts.map +1 -1
- package/dist/utils/prompts.js +51 -39
- package/dist/utils/prompts.js.map +1 -1
- package/dist/utils/template-loader.d.ts +2 -1
- package/dist/utils/template-loader.d.ts.map +1 -1
- package/dist/utils/template-loader.js +11 -5
- package/dist/utils/template-loader.js.map +1 -1
- package/dist/utils/validators.d.ts.map +1 -1
- package/dist/utils/validators.js +16 -4
- package/dist/utils/validators.js.map +1 -1
- package/package.json +2 -2
- package/templates/nextjs-turbo-monorepo/base/README.md +167 -0
- package/templates/nextjs-turbo-monorepo/base/_gitignore +71 -0
- package/templates/nextjs-turbo-monorepo/base/_npmrc +12 -0
- package/templates/nextjs-turbo-monorepo/base/apps/admin/app/layout.tsx +19 -0
- package/templates/nextjs-turbo-monorepo/base/apps/admin/app/page.tsx +34 -0
- package/templates/nextjs-turbo-monorepo/base/apps/admin/next.config.ts +20 -0
- package/templates/nextjs-turbo-monorepo/base/apps/admin/package.json +42 -0
- package/templates/nextjs-turbo-monorepo/base/apps/admin/tsconfig.json +16 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/app/globals.css +59 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/app/layout.tsx +20 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/app/page.tsx +17 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/components.json +18 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/env.ts +45 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/next.config.ts +38 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/package.json +45 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/tsconfig.json +19 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/vitest.config.ts +28 -0
- package/templates/nextjs-turbo-monorepo/base/docker/Dockerfile.admin +76 -0
- package/templates/nextjs-turbo-monorepo/base/docker/Dockerfile.web +76 -0
- package/templates/nextjs-turbo-monorepo/base/docker/_dockerignore +48 -0
- package/templates/nextjs-turbo-monorepo/base/docker/docker-compose.yml +39 -0
- package/templates/nextjs-turbo-monorepo/base/package.json +62 -0
- package/templates/nextjs-turbo-monorepo/base/packages/auth/package.json +26 -0
- package/templates/nextjs-turbo-monorepo/base/packages/auth/src/config.ts +88 -0
- package/templates/nextjs-turbo-monorepo/base/packages/auth/src/index.ts +11 -0
- package/templates/nextjs-turbo-monorepo/base/packages/auth/src/types.ts +28 -0
- package/templates/nextjs-turbo-monorepo/base/packages/auth/tsconfig.json +9 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/eslint/library.js +35 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/eslint/nextjs.js +51 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/eslint/react-library.js +45 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/package.json +19 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/tailwind/base.ts +50 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/typescript/base.json +23 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/typescript/nextjs.json +17 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/typescript/react-library.json +11 -0
- package/templates/nextjs-turbo-monorepo/base/packages/database/package.json +32 -0
- package/templates/nextjs-turbo-monorepo/base/packages/database/prisma/schema.prisma +84 -0
- package/templates/nextjs-turbo-monorepo/base/packages/database/src/client.ts +16 -0
- package/templates/nextjs-turbo-monorepo/base/packages/database/src/index.ts +2 -0
- package/templates/nextjs-turbo-monorepo/base/packages/database/tsconfig.json +9 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/package.json +32 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/src/context.ts +17 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/src/index.ts +3 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/src/init.ts +34 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/src/routers/_app.ts +8 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/src/routers/user.ts +36 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/tsconfig.json +9 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/.storybook/main.ts +19 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/.storybook/preview.ts +15 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/components.json +17 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/package.json +62 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/postcss.config.js +6 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/components/index.ts +2 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/components/ui/button.tsx +57 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/components/ui/card.tsx +76 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/hooks/index.ts +2 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/lib/utils.ts +6 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/styles/globals.css +59 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/tailwind.config.ts +15 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/tsconfig.json +12 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/vitest.config.ts +28 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/package.json +24 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/src/date.ts +117 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/src/index.ts +2 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/src/string.ts +59 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/tsconfig.json +9 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/vitest.config.ts +13 -0
- package/templates/nextjs-turbo-monorepo/base/pnpm-workspace.yaml +3 -0
- package/templates/nextjs-turbo-monorepo/base/tsconfig.json +19 -0
- package/templates/nextjs-turbo-monorepo/base/turbo.json +47 -0
- package/templates/nextjs-turbo-monorepo/extras/boss-cli/assets/claude-md/template.md +0 -0
- package/templates/nextjs-turbo-monorepo/extras/boss-cli/assets/github-workflows/CODEOWNERS +0 -0
- package/templates/nextjs-turbo-monorepo/extras/boss-cli/assets/github-workflows/boss-ci.yml +139 -0
- package/templates/nextjs-turbo-monorepo/extras/boss-cli/assets/github-workflows/boss-gates.yml +116 -0
- package/templates/nextjs-turbo-monorepo/extras/config/kamal/_env +18 -0
- package/templates/nextjs-turbo-monorepo/extras/config/kamal/deploy.yml +92 -0
- package/templates/nextjs-turbo-monorepo/extras/scripts/deploy.sh +38 -0
- package/templates/nextjs-turbo-monorepo/extras/scripts/setup-db.sh +29 -0
- package/assets/claude-md/docs/container-use.md +0 -140
- package/dist/assets/claude-md/docs/container-use.md +0 -140
package/dist/assets/worker-configs/developer-fullstack/.claude/skills/test-first-methodology.md
ADDED
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
# Test-First Methodology
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Create, modify, and validate tests using Test-Driven Development (TDD) and Behavior-Driven Development (BDD). Use when implementing features, writing test suites, achieving coverage requirements, or ensuring code quality through test-first practices.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
BOSS mandates Test-First development for all implementation work. This is NON-NEGOTIABLE per the constitution established by the architect. Every line of code must be preceded by a failing test.
|
|
10
|
+
|
|
11
|
+
**Core Principles**:
|
|
12
|
+
- **RED**: Write failing test first
|
|
13
|
+
- **GREEN**: Make test pass with minimal code
|
|
14
|
+
- **REFACTOR**: Clean up while keeping tests green
|
|
15
|
+
- **BDD**: Given/When/Then scenarios from spec.md become tests
|
|
16
|
+
- **Coverage**: Minimum 80% line, branch, and function coverage
|
|
17
|
+
- **Mutation**: Minimum 80% mutation score
|
|
18
|
+
|
|
19
|
+
## TDD Cycle (RED-GREEN-REFACTOR)
|
|
20
|
+
|
|
21
|
+
### Phase 1: RED (Write Failing Test)
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// Step 1: Write test FIRST (before implementation exists)
|
|
25
|
+
import { describe, it, expect } from 'vitest';
|
|
26
|
+
import { registerUser } from './register'; // ← Doesn't exist yet!
|
|
27
|
+
|
|
28
|
+
describe('registerUser', () => {
|
|
29
|
+
it('creates user with valid email and password', async () => {
|
|
30
|
+
const result = await registerUser({
|
|
31
|
+
email: 'test@example.com',
|
|
32
|
+
password: 'SecurePass123',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(result.success).toBe(true);
|
|
36
|
+
expect(result.user.email).toBe('test@example.com');
|
|
37
|
+
expect(result.user.id).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Step 2: Run test → Should FAIL
|
|
42
|
+
// pnpm test src/auth/register.test.ts
|
|
43
|
+
// ❌ FAIL: Cannot find module './register'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Why RED First?**:
|
|
47
|
+
- Ensures test actually runs and can fail
|
|
48
|
+
- Validates test is testing the right thing
|
|
49
|
+
- Prevents false positives from tests that never fail
|
|
50
|
+
|
|
51
|
+
### Phase 2: GREEN (Make Test Pass)
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// src/auth/register.ts - Minimal implementation to pass
|
|
55
|
+
import { prisma } from '@repo/database';
|
|
56
|
+
|
|
57
|
+
export async function registerUser(data: { email: string; password: string }) {
|
|
58
|
+
const user = await prisma.user.create({
|
|
59
|
+
data: {
|
|
60
|
+
email: data.email,
|
|
61
|
+
password: await hashPassword(data.password),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
user: {
|
|
68
|
+
id: user.id,
|
|
69
|
+
email: user.email,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Step 3: Run test → Should PASS
|
|
75
|
+
// pnpm test src/auth/register.test.ts
|
|
76
|
+
// ✅ PASS: 1 test passed
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Why Minimal Code?**:
|
|
80
|
+
- Avoid over-engineering
|
|
81
|
+
- Only write code that's needed to pass tests
|
|
82
|
+
- YAGNI (You Aren't Gonna Need It)
|
|
83
|
+
|
|
84
|
+
### Phase 3: REFACTOR (Clean Up)
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Refactor while keeping tests green
|
|
88
|
+
export async function registerUser(data: RegisterInput) {
|
|
89
|
+
validateEmail(data.email); // Extract validation
|
|
90
|
+
const hashedPassword = await hashPassword(data.password);
|
|
91
|
+
|
|
92
|
+
const user = await createUserInDatabase({
|
|
93
|
+
email: data.email,
|
|
94
|
+
password: hashedPassword,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return formatUserResponse(user); // Extract formatting
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Step 4: Run test → Should STILL PASS
|
|
101
|
+
// pnpm test src/auth/register.test.ts
|
|
102
|
+
// ✅ PASS: 1 test passed (same result, cleaner code)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**What to Refactor**:
|
|
106
|
+
- Extract duplicated code
|
|
107
|
+
- Improve naming
|
|
108
|
+
- Simplify complex logic
|
|
109
|
+
- Add type safety
|
|
110
|
+
- Optimize performance
|
|
111
|
+
|
|
112
|
+
**RED-GREEN-REFACTOR is mandatory for EVERY feature**
|
|
113
|
+
|
|
114
|
+
## BDD: Given/When/Then
|
|
115
|
+
|
|
116
|
+
### From Spec to Test
|
|
117
|
+
|
|
118
|
+
Spec-Kit specifications use BDD format - convert them directly to tests:
|
|
119
|
+
|
|
120
|
+
**spec.md (from spec-writer)**:
|
|
121
|
+
```markdown
|
|
122
|
+
**Scenario 1.1: Successful Registration**
|
|
123
|
+
- **Given** I am on the registration page
|
|
124
|
+
- **And** I have a valid email "user@example.com"
|
|
125
|
+
- **And** I have a strong password "SecurePass123"
|
|
126
|
+
- **When** I submit the registration form
|
|
127
|
+
- **Then** I should see "Registration successful" message
|
|
128
|
+
- **And** I should receive a verification email
|
|
129
|
+
- **And** my account should be created in the database
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Converted to Test**:
|
|
133
|
+
```typescript
|
|
134
|
+
describe('User Registration', () => {
|
|
135
|
+
describe('Scenario 1.1: Successful Registration', () => {
|
|
136
|
+
it('shows success message, sends verification email, and creates account', async () => {
|
|
137
|
+
// GIVEN: Valid inputs
|
|
138
|
+
const email = 'user@example.com';
|
|
139
|
+
const password = 'SecurePass123';
|
|
140
|
+
|
|
141
|
+
// WHEN: Submit registration
|
|
142
|
+
const result = await registerUser({ email, password });
|
|
143
|
+
|
|
144
|
+
// THEN: Success message
|
|
145
|
+
expect(result.message).toBe('Registration successful');
|
|
146
|
+
|
|
147
|
+
// AND: Verification email sent
|
|
148
|
+
expect(emailService.sendVerification).toHaveBeenCalledWith(email);
|
|
149
|
+
|
|
150
|
+
// AND: Account created in database
|
|
151
|
+
const user = await prisma.user.findUnique({ where: { email } });
|
|
152
|
+
expect(user).toBeDefined();
|
|
153
|
+
expect(user.email).toBe(email);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### BDD Best Practices
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// ✅ Good - descriptive test names match scenarios
|
|
163
|
+
describe('Scenario 1.2: Invalid Email Format', () => {
|
|
164
|
+
it('rejects registration and shows error message', async () => {
|
|
165
|
+
// ...
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ✅ Good - clear Given/When/Then structure
|
|
170
|
+
it('calculates total price with tax', () => {
|
|
171
|
+
// GIVEN
|
|
172
|
+
const items = [{ price: 100 }, { price: 200 }];
|
|
173
|
+
const taxRate = 0.1;
|
|
174
|
+
|
|
175
|
+
// WHEN
|
|
176
|
+
const total = calculateTotal(items, taxRate);
|
|
177
|
+
|
|
178
|
+
// THEN
|
|
179
|
+
expect(total).toBe(330); // (100 + 200) * 1.1
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// ❌ Bad - unclear what's being tested
|
|
183
|
+
it('works correctly', () => {
|
|
184
|
+
const result = doSomething();
|
|
185
|
+
expect(result).toBeTruthy();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// ❌ Bad - testing multiple scenarios in one test
|
|
189
|
+
it('handles all cases', () => {
|
|
190
|
+
expect(func(1)).toBe(2);
|
|
191
|
+
expect(func(2)).toBe(4);
|
|
192
|
+
expect(func('invalid')).toThrow();
|
|
193
|
+
// Too many concerns!
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Test Pyramid
|
|
198
|
+
|
|
199
|
+
Maintain 70% unit / 20% integration / 10% E2E distribution:
|
|
200
|
+
|
|
201
|
+
### Unit Tests (70%)
|
|
202
|
+
|
|
203
|
+
**Purpose**: Test individual functions/components in isolation.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Unit test - fast, isolated
|
|
207
|
+
describe('calculateDiscount', () => {
|
|
208
|
+
it('applies 10% discount to regular users', () => {
|
|
209
|
+
expect(calculateDiscount(100, 'regular')).toBe(90);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('applies 20% discount to premium users', () => {
|
|
213
|
+
expect(calculateDiscount(100, 'premium')).toBe(80);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('throws error for negative amounts', () => {
|
|
217
|
+
expect(() => calculateDiscount(-10, 'regular')).toThrow('Amount must be positive');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Mock dependencies
|
|
222
|
+
vi.mock('@repo/database', () => ({
|
|
223
|
+
prisma: {
|
|
224
|
+
user: {
|
|
225
|
+
findUnique: vi.fn(),
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
}));
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Characteristics**:
|
|
232
|
+
- Fast (< 1ms each)
|
|
233
|
+
- No external dependencies (database, API, filesystem)
|
|
234
|
+
- Test pure logic
|
|
235
|
+
- Use mocks/stubs for dependencies
|
|
236
|
+
|
|
237
|
+
### Integration Tests (20%)
|
|
238
|
+
|
|
239
|
+
**Purpose**: Test multiple components working together.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// Integration test - real database, real services
|
|
243
|
+
describe('User Registration Integration', () => {
|
|
244
|
+
beforeEach(async () => {
|
|
245
|
+
await prisma.user.deleteMany(); // Clean database
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('registers user and sends welcome email', async () => {
|
|
249
|
+
// Uses real Prisma, real email service
|
|
250
|
+
const result = await registerUser({
|
|
251
|
+
email: 'test@example.com',
|
|
252
|
+
password: 'SecurePass123',
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Verify database state
|
|
256
|
+
const user = await prisma.user.findUnique({
|
|
257
|
+
where: { email: 'test@example.com' },
|
|
258
|
+
});
|
|
259
|
+
expect(user).toBeDefined();
|
|
260
|
+
|
|
261
|
+
// Verify email was sent (integration with email service)
|
|
262
|
+
expect(emailService.sentEmails).toHaveLength(1);
|
|
263
|
+
expect(emailService.sentEmails[0].to).toBe('test@example.com');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Characteristics**:
|
|
269
|
+
- Slower (10-100ms each)
|
|
270
|
+
- Use real databases (test database, not production)
|
|
271
|
+
- Test multiple layers together
|
|
272
|
+
- Verify integrations between systems
|
|
273
|
+
|
|
274
|
+
### E2E Tests (10%)
|
|
275
|
+
|
|
276
|
+
**Purpose**: Test complete user flows from UI to database.
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// E2E test - real browser, real UI, real backend
|
|
280
|
+
import { test, expect } from '@playwright/test';
|
|
281
|
+
|
|
282
|
+
test('user can register and login', async ({ page }) => {
|
|
283
|
+
// Navigate to registration
|
|
284
|
+
await page.goto('/register');
|
|
285
|
+
|
|
286
|
+
// Fill form
|
|
287
|
+
await page.fill('input[name="email"]', 'test@example.com');
|
|
288
|
+
await page.fill('input[name="password"]', 'SecurePass123');
|
|
289
|
+
await page.click('button[type="submit"]');
|
|
290
|
+
|
|
291
|
+
// Verify success
|
|
292
|
+
await expect(page.locator('text=Registration successful')).toBeVisible();
|
|
293
|
+
|
|
294
|
+
// Navigate to login
|
|
295
|
+
await page.goto('/login');
|
|
296
|
+
await page.fill('input[name="email"]', 'test@example.com');
|
|
297
|
+
await page.fill('input[name="password"]', 'SecurePass123');
|
|
298
|
+
await page.click('button[type="submit"]');
|
|
299
|
+
|
|
300
|
+
// Verify redirect to dashboard
|
|
301
|
+
await expect(page).toHaveURL('/dashboard');
|
|
302
|
+
await expect(page.locator('text=Welcome')).toBeVisible();
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Characteristics**:
|
|
307
|
+
- Slowest (1-10s each)
|
|
308
|
+
- Real browser interaction
|
|
309
|
+
- Test critical user journeys
|
|
310
|
+
- Catch issues that unit/integration tests miss
|
|
311
|
+
|
|
312
|
+
## Coverage Requirements
|
|
313
|
+
|
|
314
|
+
### Line/Branch/Function Coverage (≥80%)
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# Run tests with coverage
|
|
318
|
+
pnpm test:coverage
|
|
319
|
+
|
|
320
|
+
# Output:
|
|
321
|
+
# File | % Stmts | % Branch | % Funcs | % Lines |
|
|
322
|
+
# auth/ | 87.5 | 82.3 | 90.0 | 88.2 | ✅
|
|
323
|
+
# users/ | 92.1 | 88.7 | 95.0 | 91.5 | ✅
|
|
324
|
+
# payments/ | 76.4 | 71.2 | 80.0 | 75.8 | ❌ Below 80%
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Meeting Coverage**:
|
|
328
|
+
```typescript
|
|
329
|
+
// ❌ Low coverage - only happy path
|
|
330
|
+
test('processes payment', () => {
|
|
331
|
+
expect(processPayment(100)).resolves.toBe(true);
|
|
332
|
+
});
|
|
333
|
+
// Coverage: 50% (missing error cases, edge cases)
|
|
334
|
+
|
|
335
|
+
// ✅ High coverage - happy path + edge cases + errors
|
|
336
|
+
describe('processPayment', () => {
|
|
337
|
+
it('processes valid payment', async () => {
|
|
338
|
+
expect(await processPayment(100)).toBe(true);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('rejects negative amounts', async () => {
|
|
342
|
+
await expect(processPayment(-10)).rejects.toThrow('Invalid amount');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('rejects zero amount', async () => {
|
|
346
|
+
await expect(processPayment(0)).rejects.toThrow('Invalid amount');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('handles payment gateway timeout', async () => {
|
|
350
|
+
vi.mocked(paymentGateway.process).mockRejectedValue(new TimeoutError());
|
|
351
|
+
await expect(processPayment(100)).rejects.toThrow('Payment timeout');
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
// Coverage: 95% (all branches tested)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Mutation Testing (≥80%)
|
|
358
|
+
|
|
359
|
+
**Purpose**: Ensure tests actually catch bugs (not just execute code).
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
# Run mutation testing
|
|
363
|
+
pnpm test:mutation
|
|
364
|
+
|
|
365
|
+
# Stryker mutates code and re-runs tests
|
|
366
|
+
# If tests still pass after mutation, tests are weak
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Example**:
|
|
370
|
+
```typescript
|
|
371
|
+
// Original code
|
|
372
|
+
function calculateDiscount(amount: number, userType: string) {
|
|
373
|
+
if (userType === 'premium') {
|
|
374
|
+
return amount * 0.8; // 20% discount
|
|
375
|
+
}
|
|
376
|
+
return amount * 0.9; // 10% discount
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Mutation 1: Change 0.8 to 0.9
|
|
380
|
+
// If test still passes → test doesn't verify actual discount amount!
|
|
381
|
+
|
|
382
|
+
// ❌ Weak test (mutation survives)
|
|
383
|
+
it('applies discount to premium users', () => {
|
|
384
|
+
const result = calculateDiscount(100, 'premium');
|
|
385
|
+
expect(result).toBeLessThan(100); // Too vague!
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// ✅ Strong test (mutation killed)
|
|
389
|
+
it('applies 20% discount to premium users', () => {
|
|
390
|
+
const result = calculateDiscount(100, 'premium');
|
|
391
|
+
expect(result).toBe(80); // Exact value - catches mutation!
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Testing Patterns for BOSS Stack
|
|
396
|
+
|
|
397
|
+
### Testing tRPC Routers
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// src/server/api/routers/user.test.ts
|
|
401
|
+
import { appRouter } from '../root';
|
|
402
|
+
import { createInnerTRPCContext } from '../trpc';
|
|
403
|
+
import { prisma } from '@repo/database';
|
|
404
|
+
|
|
405
|
+
describe('user.list', () => {
|
|
406
|
+
it('returns all users', async () => {
|
|
407
|
+
// Setup
|
|
408
|
+
await prisma.user.createMany({
|
|
409
|
+
data: [
|
|
410
|
+
{ email: 'user1@example.com', name: 'User 1' },
|
|
411
|
+
{ email: 'user2@example.com', name: 'User 2' },
|
|
412
|
+
],
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Create tRPC caller
|
|
416
|
+
const ctx = createInnerTRPCContext({});
|
|
417
|
+
const caller = appRouter.createCaller(ctx);
|
|
418
|
+
|
|
419
|
+
// Execute
|
|
420
|
+
const users = await caller.user.list();
|
|
421
|
+
|
|
422
|
+
// Verify
|
|
423
|
+
expect(users).toHaveLength(2);
|
|
424
|
+
expect(users[0].email).toBe('user1@example.com');
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Testing Server Components
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// app/users/page.test.tsx
|
|
433
|
+
import { render } from '@testing-library/react';
|
|
434
|
+
import UsersPage from './page';
|
|
435
|
+
|
|
436
|
+
// Mock API call
|
|
437
|
+
vi.mock('@/lib/trpc/server', () => ({
|
|
438
|
+
api: {
|
|
439
|
+
user: {
|
|
440
|
+
list: vi.fn().mockResolvedValue([
|
|
441
|
+
{ id: '1', name: 'User 1', email: 'user1@example.com' },
|
|
442
|
+
]),
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
}));
|
|
446
|
+
|
|
447
|
+
test('renders user list', async () => {
|
|
448
|
+
const { findByText } = render(await UsersPage());
|
|
449
|
+
|
|
450
|
+
expect(await findByText('User 1')).toBeDefined();
|
|
451
|
+
expect(await findByText('user1@example.com')).toBeDefined();
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Testing Client Components
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
// components/UserForm.test.tsx
|
|
459
|
+
import { render, screen } from '@testing-library/react';
|
|
460
|
+
import { userEvent } from '@testing-library/user-event';
|
|
461
|
+
import { UserForm } from './UserForm';
|
|
462
|
+
|
|
463
|
+
test('submits form with valid data', async () => {
|
|
464
|
+
const onSubmit = vi.fn();
|
|
465
|
+
render(<UserForm onSubmit={onSubmit} />);
|
|
466
|
+
|
|
467
|
+
await userEvent.type(screen.getByLabelText('Name'), 'John Doe');
|
|
468
|
+
await userEvent.type(screen.getByLabelText('Email'), 'john@example.com');
|
|
469
|
+
await userEvent.click(screen.getByRole('button', { name: 'Submit' }));
|
|
470
|
+
|
|
471
|
+
expect(onSubmit).toHaveBeenCalledWith({
|
|
472
|
+
name: 'John Doe',
|
|
473
|
+
email: 'john@example.com',
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Testing Prisma Queries
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
// services/user.test.ts
|
|
482
|
+
import { prisma } from '@repo/database';
|
|
483
|
+
import { createUser, getUser } from './user';
|
|
484
|
+
|
|
485
|
+
describe('createUser', () => {
|
|
486
|
+
it('creates user in database', async () => {
|
|
487
|
+
const user = await createUser({
|
|
488
|
+
email: 'test@example.com',
|
|
489
|
+
name: 'Test User',
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
expect(user.id).toBeDefined();
|
|
493
|
+
|
|
494
|
+
// Verify in database
|
|
495
|
+
const dbUser = await prisma.user.findUnique({ where: { id: user.id } });
|
|
496
|
+
expect(dbUser).toBeDefined();
|
|
497
|
+
expect(dbUser.email).toBe('test@example.com');
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Test Organization
|
|
503
|
+
|
|
504
|
+
### File Structure
|
|
505
|
+
|
|
506
|
+
```
|
|
507
|
+
src/
|
|
508
|
+
├── components/
|
|
509
|
+
│ ├── Button.tsx
|
|
510
|
+
│ └── Button.test.tsx # Co-located with component
|
|
511
|
+
├── api/
|
|
512
|
+
│ ├── users.ts
|
|
513
|
+
│ └── users.test.ts # Co-located with API route
|
|
514
|
+
└── services/
|
|
515
|
+
├── user.ts
|
|
516
|
+
└── user.test.ts # Co-located with service
|
|
517
|
+
|
|
518
|
+
tests/
|
|
519
|
+
├── integration/
|
|
520
|
+
│ └── user-registration.test.ts # Integration tests
|
|
521
|
+
└── e2e/
|
|
522
|
+
└── auth-flow.spec.ts # Playwright E2E tests
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Test Naming
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
// ✅ Good - describes what and why
|
|
529
|
+
describe('UserService', () => {
|
|
530
|
+
describe('createUser', () => {
|
|
531
|
+
it('creates user with hashed password', () => {});
|
|
532
|
+
it('rejects duplicate email addresses', () => {});
|
|
533
|
+
it('sends welcome email after creation', () => {});
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// ❌ Bad - vague names
|
|
538
|
+
describe('test user stuff', () => {
|
|
539
|
+
it('test1', () => {});
|
|
540
|
+
it('works', () => {});
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Common Patterns
|
|
545
|
+
|
|
546
|
+
### Setup and Teardown
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
describe('User API', () => {
|
|
550
|
+
beforeEach(async () => {
|
|
551
|
+
// Clean database before each test
|
|
552
|
+
await prisma.user.deleteMany();
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
afterEach(async () => {
|
|
556
|
+
// Cleanup if needed
|
|
557
|
+
vi.clearAllMocks();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
afterAll(async () => {
|
|
561
|
+
// Disconnect from database after all tests
|
|
562
|
+
await prisma.$disconnect();
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Test Fixtures
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
// tests/fixtures/users.ts
|
|
571
|
+
export const validUser = {
|
|
572
|
+
email: 'test@example.com',
|
|
573
|
+
name: 'Test User',
|
|
574
|
+
password: 'SecurePass123',
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
export const premiumUser = {
|
|
578
|
+
...validUser,
|
|
579
|
+
tier: 'premium',
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
// In tests:
|
|
583
|
+
import { validUser } from '../fixtures/users';
|
|
584
|
+
|
|
585
|
+
test('creates user', async () => {
|
|
586
|
+
const user = await createUser(validUser);
|
|
587
|
+
expect(user.email).toBe(validUser.email);
|
|
588
|
+
});
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Mocking
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
// Mock external service
|
|
595
|
+
vi.mock('@/lib/email', () => ({
|
|
596
|
+
sendEmail: vi.fn().mockResolvedValue({ success: true }),
|
|
597
|
+
}));
|
|
598
|
+
|
|
599
|
+
// Mock Prisma
|
|
600
|
+
vi.mock('@repo/database', () => ({
|
|
601
|
+
prisma: {
|
|
602
|
+
user: {
|
|
603
|
+
create: vi.fn().mockResolvedValue({ id: '1', email: 'test@example.com' }),
|
|
604
|
+
findUnique: vi.fn(),
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
}));
|
|
608
|
+
|
|
609
|
+
// Verify mock was called
|
|
610
|
+
expect(sendEmail).toHaveBeenCalledWith({
|
|
611
|
+
to: 'test@example.com',
|
|
612
|
+
subject: 'Welcome',
|
|
613
|
+
});
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
## Anti-Patterns
|
|
617
|
+
|
|
618
|
+
### ❌ Testing Implementation Details
|
|
619
|
+
|
|
620
|
+
```typescript
|
|
621
|
+
// ❌ Bad - tests internal state
|
|
622
|
+
test('increments counter', () => {
|
|
623
|
+
const component = new Counter();
|
|
624
|
+
component.count = 5; // Testing private state
|
|
625
|
+
expect(component.count).toBe(5);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// ✅ Good - tests behavior
|
|
629
|
+
test('increments counter', async () => {
|
|
630
|
+
render(<Counter />);
|
|
631
|
+
await userEvent.click(screen.getByRole('button'));
|
|
632
|
+
expect(screen.getByText('Count: 1')).toBeDefined();
|
|
633
|
+
});
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### ❌ Testing Third-Party Libraries
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
// ❌ Bad - testing Next.js router
|
|
640
|
+
test('Next.js router works', () => {
|
|
641
|
+
const router = useRouter();
|
|
642
|
+
router.push('/test');
|
|
643
|
+
expect(router.pathname).toBe('/test');
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// ✅ Good - test YOUR code that uses router
|
|
647
|
+
test('navigates to dashboard on login', async () => {
|
|
648
|
+
const mockPush = vi.fn();
|
|
649
|
+
vi.mock('next/router', () => ({ useRouter: () => ({ push: mockPush }) }));
|
|
650
|
+
|
|
651
|
+
await userEvent.click(screen.getByRole('button', { name: 'Login' }));
|
|
652
|
+
expect(mockPush).toHaveBeenCalledWith('/dashboard');
|
|
653
|
+
});
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### ❌ Skipping Edge Cases
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
// ❌ Bad - only happy path
|
|
660
|
+
test('adds numbers', () => {
|
|
661
|
+
expect(add(2, 3)).toBe(5);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// ✅ Good - edge cases covered
|
|
665
|
+
describe('add', () => {
|
|
666
|
+
it('adds positive numbers', () => {
|
|
667
|
+
expect(add(2, 3)).toBe(5);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it('adds negative numbers', () => {
|
|
671
|
+
expect(add(-2, -3)).toBe(-5);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it('adds zero', () => {
|
|
675
|
+
expect(add(0, 5)).toBe(5);
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it('handles floating point', () => {
|
|
679
|
+
expect(add(0.1, 0.2)).toBeCloseTo(0.3);
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
## Workflow Integration
|
|
685
|
+
|
|
686
|
+
### Developer Workflow
|
|
687
|
+
|
|
688
|
+
```bash
|
|
689
|
+
# 1. Read spec.md scenario
|
|
690
|
+
cat .specify/specs/user-authentication/spec.md
|
|
691
|
+
|
|
692
|
+
# 2. Write FAILING test (RED)
|
|
693
|
+
cat > src/auth/register.test.ts << 'EOF'
|
|
694
|
+
test('registers user', () => {
|
|
695
|
+
expect(await registerUser({ ... })).toBeDefined();
|
|
696
|
+
});
|
|
697
|
+
EOF
|
|
698
|
+
pnpm test src/auth/register.test.ts
|
|
699
|
+
# ❌ FAIL: registerUser is not defined
|
|
700
|
+
|
|
701
|
+
# 3. Implement minimal code (GREEN)
|
|
702
|
+
cat > src/auth/register.ts << 'EOF'
|
|
703
|
+
export async function registerUser() { ... }
|
|
704
|
+
EOF
|
|
705
|
+
pnpm test src/auth/register.test.ts
|
|
706
|
+
# ✅ PASS
|
|
707
|
+
|
|
708
|
+
# 4. Refactor (keep GREEN)
|
|
709
|
+
# Clean up code, add edge cases, improve structure
|
|
710
|
+
pnpm test src/auth/register.test.ts
|
|
711
|
+
# ✅ PASS
|
|
712
|
+
|
|
713
|
+
# 5. Verify coverage
|
|
714
|
+
pnpm test:coverage
|
|
715
|
+
# Coverage: 87.5% ✅
|
|
716
|
+
|
|
717
|
+
# 6. Commit
|
|
718
|
+
git add src/auth/register.ts src/auth/register.test.ts
|
|
719
|
+
git commit -m "feat: implement user registration with TDD"
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
## When to Use This Skill
|
|
723
|
+
|
|
724
|
+
- Implementing any feature (TDD is mandatory)
|
|
725
|
+
- Converting BDD scenarios from spec.md to tests
|
|
726
|
+
- Achieving coverage requirements (≥80%)
|
|
727
|
+
- Writing mutation-resistant tests
|
|
728
|
+
- Organizing test suites
|
|
729
|
+
- Testing tRPC routers, Prisma queries, React components
|
|
730
|
+
|
|
731
|
+
## Related Skills
|
|
732
|
+
|
|
733
|
+
- `nextjs-turbo-stack.md` - Testing with Vitest in the stack
|
|
734
|
+
- `spec-kit-workflow.md` - Converting spec.md to tests
|
|
735
|
+
- `boss-manifest-protocol.md` - Reporting test results
|
|
736
|
+
|
|
737
|
+
## Key Takeaways
|
|
738
|
+
|
|
739
|
+
1. **RED-GREEN-REFACTOR** - Mandatory cycle for ALL code
|
|
740
|
+
2. **Test First, Always** - No code without a failing test
|
|
741
|
+
3. **BDD Scenarios → Tests** - Direct conversion from spec.md
|
|
742
|
+
4. **Test Pyramid** - 70% unit, 20% integration, 10% E2E
|
|
743
|
+
5. **Coverage Minimums** - 80% line/branch/function, 80% mutation
|
|
744
|
+
6. **Strong Assertions** - Exact values, not vague checks
|
|
745
|
+
7. **Test Behavior** - Not implementation details
|