groundwork-method 0.0.1 → 0.10.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/CHANGELOG.md +781 -0
- package/LICENSE +21 -0
- package/README.md +44 -29
- package/bin/groundwork.js +1654 -0
- package/dist/src/generators/add-capability/generator.d.ts +8 -0
- package/dist/src/generators/add-capability/generator.js +60 -0
- package/dist/src/generators/add-capability/generator.js.map +1 -0
- package/dist/src/generators/cli-app/generator.d.ts +9 -0
- package/dist/src/generators/cli-app/generator.js +140 -0
- package/dist/src/generators/cli-app/generator.js.map +1 -0
- package/dist/src/generators/docs-site/generator.d.ts +5 -0
- package/dist/src/generators/docs-site/generator.js +441 -0
- package/dist/src/generators/docs-site/generator.js.map +1 -0
- package/dist/src/generators/electron-app/generator.d.ts +6 -0
- package/dist/src/generators/electron-app/generator.js +261 -0
- package/dist/src/generators/electron-app/generator.js.map +1 -0
- package/dist/src/generators/flutter-app/generator.d.ts +6 -0
- package/dist/src/generators/flutter-app/generator.js +314 -0
- package/dist/src/generators/flutter-app/generator.js.map +1 -0
- package/dist/src/generators/go-microservice/generator.d.ts +8 -0
- package/dist/src/generators/go-microservice/generator.js +232 -0
- package/dist/src/generators/go-microservice/generator.js.map +1 -0
- package/dist/src/generators/nextjs-app/generator.d.ts +8 -0
- package/dist/src/generators/nextjs-app/generator.js +294 -0
- package/dist/src/generators/nextjs-app/generator.js.map +1 -0
- package/dist/src/generators/python-microservice/generator.d.ts +13 -0
- package/dist/src/generators/python-microservice/generator.js +265 -0
- package/dist/src/generators/python-microservice/generator.js.map +1 -0
- package/dist/src/generators/shared/brand-tokens.d.ts +89 -0
- package/dist/src/generators/shared/brand-tokens.js +308 -0
- package/dist/src/generators/shared/brand-tokens.js.map +1 -0
- package/dist/src/generators/shared/capabilities.d.ts +101 -0
- package/dist/src/generators/shared/capabilities.js +279 -0
- package/dist/src/generators/shared/capabilities.js.map +1 -0
- package/dist/src/generators/shared/provenance.d.ts +2 -0
- package/dist/src/generators/shared/provenance.js +85 -0
- package/dist/src/generators/shared/provenance.js.map +1 -0
- package/dist/src/generators/shared/scaffold-helpers.d.ts +72 -0
- package/dist/src/generators/shared/scaffold-helpers.js +309 -0
- package/dist/src/generators/shared/scaffold-helpers.js.map +1 -0
- package/dist/src/generators/system-test-runner/generator.d.ts +23 -0
- package/dist/src/generators/system-test-runner/generator.js +125 -0
- package/dist/src/generators/system-test-runner/generator.js.map +1 -0
- package/dist/src/generators/workspace-dev-cli/generator.d.ts +7 -0
- package/dist/src/generators/workspace-dev-cli/generator.js +138 -0
- package/dist/src/generators/workspace-dev-cli/generator.js.map +1 -0
- package/generators.json +57 -0
- package/lib/repo-map/grammars/tree-sitter-c.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-cpp.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-csharp.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-dart.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-go.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-java.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-javascript.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-kotlin.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-lua.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-php.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-python.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-ruby.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-rust.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-scala.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-swift.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-tsx.wasm +0 -0
- package/lib/repo-map/grammars/tree-sitter-typescript.wasm +0 -0
- package/lib/repo-map/index.js +386 -0
- package/lib/repo-map/languages.js +514 -0
- package/lib/repo-map/pagerank.js +59 -0
- package/migrations/README.md +60 -0
- package/migrations/_template/cli-migration.js +27 -0
- package/migrations/gw-bet-prose-redesign.js +105 -0
- package/migrations/gw-drop-test-manifest.js +37 -0
- package/migrations/gw-register-serena-mcp.js +42 -0
- package/migrations/gw-relocate-hidden-skills.js +40 -0
- package/migrations/gw-seed-config-toml.js +24 -0
- package/migrations/index.json +40 -0
- package/package.json +70 -6
- package/src/AGENTS.md +36 -0
- package/src/config/config.toml +30 -0
- package/src/config/groundwork-state.json +5 -0
- package/src/docs/llms.txt +72 -0
- package/src/docs/principles/ai-native/agent-native-systems.md +90 -0
- package/src/docs/principles/ai-native/agentic-systems.md +78 -0
- package/src/docs/principles/ai-native/ai-engineering.md +100 -0
- package/src/docs/principles/ai-native/ai-native-product.md +76 -0
- package/src/docs/principles/delivery/cost-engineering.md +89 -0
- package/src/docs/principles/delivery/day-2-operational-baseline.md +57 -0
- package/src/docs/principles/delivery/devex.md +88 -0
- package/src/docs/principles/delivery/platform.md +101 -0
- package/src/docs/principles/delivery/progressive-delivery.md +92 -0
- package/src/docs/principles/design/ai-native-design.md +73 -0
- package/src/docs/principles/design/design-foundations.md +80 -0
- package/src/docs/principles/design/design-systems-and-tokens.md +72 -0
- package/src/docs/principles/design/interaction-and-motion.md +69 -0
- package/src/docs/principles/design/layout-and-space.md +72 -0
- package/src/docs/principles/design/usability-and-ux.md +68 -0
- package/src/docs/principles/design/visual-design.md +84 -0
- package/src/docs/principles/foundations/code-craft.md +86 -0
- package/src/docs/principles/foundations/continuous-discovery.md +75 -0
- package/src/docs/principles/foundations/documentation.md +102 -0
- package/src/docs/principles/foundations/prioritization-and-appetite.md +78 -0
- package/src/docs/principles/foundations/product-engineering.md +90 -0
- package/src/docs/principles/foundations/product-risks.md +89 -0
- package/src/docs/principles/foundations/requirements-and-specs.md +80 -0
- package/src/docs/principles/foundations/success-metrics.md +66 -0
- package/src/docs/principles/foundations/testing.md +82 -0
- package/src/docs/principles/index.md +23 -0
- package/src/docs/principles/quality/accessibility.md +88 -0
- package/src/docs/principles/quality/observability.md +84 -0
- package/src/docs/principles/quality/performance.md +84 -0
- package/src/docs/principles/quality/privacy.md +92 -0
- package/src/docs/principles/quality/reliability.md +89 -0
- package/src/docs/principles/quality/security.md +78 -0
- package/src/docs/principles/stack/postgres.md +100 -0
- package/src/docs/principles/system-design/api-design.md +86 -0
- package/src/docs/principles/system-design/architecture-decisions.md +81 -0
- package/src/docs/principles/system-design/code-structure.md +104 -0
- package/src/docs/principles/system-design/data-engineering.md +87 -0
- package/src/docs/principles/system-design/durable-execution.md +89 -0
- package/src/docs/principles/system-design/evolutionary-architecture.md +81 -0
- package/src/docs/principles/system-design/identity-and-access.md +76 -0
- package/src/docs/principles/system-design/integration-patterns.md +84 -0
- package/src/docs/principles/system-design/real-time.md +83 -0
- package/src/docs/principles/system-design/surface-architecture.md +74 -0
- package/src/docs/ways-of-working/documentation.md +69 -0
- package/src/docs/ways-of-working/how-we-work.md +76 -0
- package/src/docs/ways-of-working/units-of-work.md +40 -0
- package/src/engineer-skills/groundwork-electron-engineer/SKILL.md +118 -0
- package/src/engineer-skills/groundwork-electron-engineer/references/ipc-contracts.md +138 -0
- package/src/engineer-skills/groundwork-electron-engineer/references/packaging-and-updates.md +82 -0
- package/src/engineer-skills/groundwork-electron-engineer/references/process-model.md +94 -0
- package/src/engineer-skills/groundwork-electron-engineer/references/security.md +107 -0
- package/src/engineer-skills/groundwork-electron-engineer/references/testing-and-smoke.md +107 -0
- package/src/engineer-skills/groundwork-electron-engineer/references/theming-and-tokens.md +74 -0
- package/src/engineer-skills/groundwork-electron-engineer/sync-anchor.md +14 -0
- package/src/engineer-skills/groundwork-flutter-engineer/SKILL.md +108 -0
- package/src/engineer-skills/groundwork-flutter-engineer/references/accessibility.md +92 -0
- package/src/engineer-skills/groundwork-flutter-engineer/references/architecture.md +189 -0
- package/src/engineer-skills/groundwork-flutter-engineer/references/data-and-contracts.md +136 -0
- package/src/engineer-skills/groundwork-flutter-engineer/references/navigation.md +122 -0
- package/src/engineer-skills/groundwork-flutter-engineer/references/platform-channels.md +93 -0
- package/src/engineer-skills/groundwork-flutter-engineer/references/releases-and-distribution.md +84 -0
- package/src/engineer-skills/groundwork-flutter-engineer/references/state-management.md +166 -0
- package/src/engineer-skills/groundwork-flutter-engineer/references/testing.md +135 -0
- package/src/engineer-skills/groundwork-flutter-engineer/references/theming-and-design-tokens.md +109 -0
- package/src/engineer-skills/groundwork-flutter-engineer/references/widgets-and-composition.md +123 -0
- package/src/engineer-skills/groundwork-flutter-engineer/sync-anchor.md +15 -0
- package/src/engineer-skills/groundwork-go-engineer/SKILL.md +171 -0
- package/src/engineer-skills/groundwork-go-engineer/references/api-design.md +82 -0
- package/src/engineer-skills/groundwork-go-engineer/references/architecture.md +42 -0
- package/src/engineer-skills/groundwork-go-engineer/references/capability-ports.md +50 -0
- package/src/engineer-skills/groundwork-go-engineer/references/code-craft-security.md +34 -0
- package/src/engineer-skills/groundwork-go-engineer/references/concurrency.md +108 -0
- package/src/engineer-skills/groundwork-go-engineer/references/go-services.md +77 -0
- package/src/engineer-skills/groundwork-go-engineer/references/http-handlers.md +172 -0
- package/src/engineer-skills/groundwork-go-engineer/references/implementation-patterns.md +156 -0
- package/src/engineer-skills/groundwork-go-engineer/references/integration-realtime-data.md +57 -0
- package/src/engineer-skills/groundwork-go-engineer/references/observability.md +49 -0
- package/src/engineer-skills/groundwork-go-engineer/references/postgres.md +41 -0
- package/src/engineer-skills/groundwork-go-engineer/references/reliability-performance.md +105 -0
- package/src/engineer-skills/groundwork-go-engineer/references/testing.md +139 -0
- package/src/engineer-skills/groundwork-go-engineer/sync-anchor.md +11 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/SKILL.md +107 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/architecture.md +323 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/data-fetching.md +458 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/documentation.md +324 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/error-boundaries.md +383 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/mutations-and-forms.md +396 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/performance-and-deployment.md +947 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/routing-and-navigation.md +405 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/server-components.md +394 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/tailwind-and-styling.md +134 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/testing.md +433 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/type-system.md +368 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/ux-principles.md +278 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/references/visual-language.md +69 -0
- package/src/engineer-skills/groundwork-nextjs-engineer/sync-anchor.md +9 -0
- package/src/engineer-skills/groundwork-python-engineer/SKILL.md +196 -0
- package/src/engineer-skills/groundwork-python-engineer/references/api-standards.md +88 -0
- package/src/engineer-skills/groundwork-python-engineer/references/architecture.md +57 -0
- package/src/engineer-skills/groundwork-python-engineer/references/async-patterns.md +103 -0
- package/src/engineer-skills/groundwork-python-engineer/references/capability-ports.md +44 -0
- package/src/engineer-skills/groundwork-python-engineer/references/database.md +88 -0
- package/src/engineer-skills/groundwork-python-engineer/references/documentation-mcp.md +167 -0
- package/src/engineer-skills/groundwork-python-engineer/references/implementation-patterns.md +166 -0
- package/src/engineer-skills/groundwork-python-engineer/references/ml-pipelines.md +119 -0
- package/src/engineer-skills/groundwork-python-engineer/references/ml-systems-ai-engineering.md +74 -0
- package/src/engineer-skills/groundwork-python-engineer/references/observability.md +57 -0
- package/src/engineer-skills/groundwork-python-engineer/references/resilience.md +126 -0
- package/src/engineer-skills/groundwork-python-engineer/references/testing.md +177 -0
- package/src/engineer-skills/groundwork-python-engineer/sync-anchor.md +13 -0
- package/src/generators/add-capability/generator.ts +70 -0
- package/src/generators/add-capability/schema.json +30 -0
- package/src/generators/capabilities/llm/capability.json +28 -0
- package/src/generators/capabilities/llm/providers/anthropic/footprint.json +13 -0
- package/src/generators/capabilities/llm/providers/anthropic/stacks/go/internal/llm/llm.go.template +102 -0
- package/src/generators/capabilities/llm/providers/anthropic/stacks/python/src/__packageName__/adapters/llm.py.template +61 -0
- package/src/generators/capabilities/llm/providers/local/footprint.json +13 -0
- package/src/generators/capabilities/llm/providers/local/stacks/go/internal/llm/llm.go.template +102 -0
- package/src/generators/capabilities/llm/providers/local/stacks/python/src/__packageName__/adapters/llm.py.template +53 -0
- package/src/generators/capabilities/llm/providers/localai/footprint.json +29 -0
- package/src/generators/capabilities/llm/providers/localai/stacks/go/internal/llm/llm.go.template +102 -0
- package/src/generators/capabilities/llm/providers/localai/stacks/python/src/__packageName__/adapters/llm.py.template +53 -0
- package/src/generators/capabilities/llm/providers/none/footprint.json +9 -0
- package/src/generators/capabilities/llm/providers/none/stacks/go/internal/llm/llm.go.template +35 -0
- package/src/generators/capabilities/llm/providers/none/stacks/python/src/__packageName__/adapters/llm.py.template +25 -0
- package/src/generators/capabilities/llm/providers/ollama/footprint.json +20 -0
- package/src/generators/capabilities/llm/providers/ollama/stacks/go/internal/llm/llm.go.template +102 -0
- package/src/generators/capabilities/llm/providers/ollama/stacks/python/src/__packageName__/adapters/llm.py.template +53 -0
- package/src/generators/capabilities/llm/providers/openai/footprint.json +13 -0
- package/src/generators/capabilities/llm/providers/openai/stacks/go/internal/llm/llm.go.template +98 -0
- package/src/generators/capabilities/llm/providers/openai/stacks/python/src/__packageName__/adapters/llm.py.template +60 -0
- package/src/generators/capabilities/llm/stacks/go/internal/core/service/llm.go.template +12 -0
- package/src/generators/capabilities/llm/stacks/go/internal/llm/llm_test.go.template +33 -0
- package/src/generators/capabilities/llm/stacks/python/src/__packageName__/core/llm.py.template +15 -0
- package/src/generators/capabilities/llm/stacks/python/tests/contracts/test_llm.py.template +37 -0
- package/src/generators/cli-app/files/README.md.template +76 -0
- package/src/generators/cli-app/files/build.mjs.template +15 -0
- package/src/generators/cli-app/files/package.json.template +21 -0
- package/src/generators/cli-app/files/src/cli.ts.template +67 -0
- package/src/generators/cli-app/files/src/commands/hello.ts.template +17 -0
- package/src/generators/cli-app/files/src/commands/status.ts.template +23 -0
- package/src/generators/cli-app/files/src/core/client.test.ts.template +80 -0
- package/src/generators/cli-app/files/src/core/client.ts.template +64 -0
- package/src/generators/cli-app/files/src/registry.test.ts.template +35 -0
- package/src/generators/cli-app/files/src/registry.ts.template +31 -0
- package/src/generators/cli-app/files/tsconfig.json.template +16 -0
- package/src/generators/cli-app/files/tsconfig.test.json.template +11 -0
- package/src/generators/cli-app/generator.ts +138 -0
- package/src/generators/cli-app/schema.json +24 -0
- package/src/generators/docs-site/files/.gitignore.ejs +40 -0
- package/src/generators/docs-site/files/app/docs/__slug__/page.tsx +101 -0
- package/src/generators/docs-site/files/app/docs/layout.tsx +14 -0
- package/src/generators/docs-site/files/app/docs.css +43 -0
- package/src/generators/docs-site/files/app/layout.tsx +24 -0
- package/src/generators/docs-site/files/app/page.tsx +135 -0
- package/src/generators/docs-site/files/app/source.ts +8 -0
- package/src/generators/docs-site/files/components/mermaid.tsx +67 -0
- package/src/generators/docs-site/files/next.config.mjs +10 -0
- package/src/generators/docs-site/files/package.json +32 -0
- package/src/generators/docs-site/files/pnpm-workspace.yaml +7 -0
- package/src/generators/docs-site/files/postcss.config.mjs +6 -0
- package/src/generators/docs-site/files/source.config.ts +77 -0
- package/src/generators/docs-site/files/tailwind.config.js +10 -0
- package/src/generators/docs-site/files/tsconfig.json +27 -0
- package/src/generators/docs-site/generator.ts +476 -0
- package/src/generators/docs-site/schema.json +17 -0
- package/src/generators/electron-app/docs/principles/stack/electron/index.md +47 -0
- package/src/generators/electron-app/docs/principles/stack/electron/ipc-contracts.md +71 -0
- package/src/generators/electron-app/docs/principles/stack/electron/packaging-and-updates.md +59 -0
- package/src/generators/electron-app/docs/principles/stack/electron/process-model.md +53 -0
- package/src/generators/electron-app/docs/principles/stack/electron/security.md +70 -0
- package/src/generators/electron-app/docs/principles/stack/typescript/frontend.md +65 -0
- package/src/generators/electron-app/files/.gitignore.template +20 -0
- package/src/generators/electron-app/files/README.md.template +125 -0
- package/src/generators/electron-app/files/electron.vite.config.ts +31 -0
- package/src/generators/electron-app/files/eslint.config.mjs +92 -0
- package/src/generators/electron-app/files/forge.config.ts.template +44 -0
- package/src/generators/electron-app/files/package.json.template +54 -0
- package/src/generators/electron-app/files/playwright.config.ts +18 -0
- package/src/generators/electron-app/files/project.json.template +65 -0
- package/src/generators/electron-app/files/src/main/core-client.test.ts +81 -0
- package/src/generators/electron-app/files/src/main/core-client.ts +55 -0
- package/src/generators/electron-app/files/src/main/index.ts +157 -0
- package/src/generators/electron-app/files/src/main/ipc.ts +52 -0
- package/src/generators/electron-app/files/src/main/policy.test.ts +71 -0
- package/src/generators/electron-app/files/src/main/policy.ts +73 -0
- package/src/generators/electron-app/files/src/preload/index.ts +23 -0
- package/src/generators/electron-app/files/src/renderer/index.html.template +20 -0
- package/src/generators/electron-app/files/src/renderer/src/App.test.tsx +61 -0
- package/src/generators/electron-app/files/src/renderer/src/App.tsx.template +43 -0
- package/src/generators/electron-app/files/src/renderer/src/assets/main.css +40 -0
- package/src/generators/electron-app/files/src/renderer/src/env.d.ts +14 -0
- package/src/generators/electron-app/files/src/renderer/src/main.tsx +25 -0
- package/src/generators/electron-app/files/src/shared/ipc.ts +54 -0
- package/src/generators/electron-app/files/tests/smoke/app.spec.ts.template +68 -0
- package/src/generators/electron-app/files/tool/electron_exec.sh.template +83 -0
- package/src/generators/electron-app/files/tsconfig.json +7 -0
- package/src/generators/electron-app/files/tsconfig.node.json +27 -0
- package/src/generators/electron-app/files/tsconfig.web.json +22 -0
- package/src/generators/electron-app/files/vitest.config.ts +32 -0
- package/src/generators/electron-app/files/vitest.setup.ts +1 -0
- package/src/generators/electron-app/generator.ts +288 -0
- package/src/generators/electron-app/schema.json +23 -0
- package/src/generators/flutter-app/docs/principles/stack/flutter/architecture.md +78 -0
- package/src/generators/flutter-app/docs/principles/stack/flutter/index.md +38 -0
- package/src/generators/flutter-app/docs/principles/stack/flutter/platform-channels.md +51 -0
- package/src/generators/flutter-app/docs/principles/stack/flutter/releases-and-distribution.md +59 -0
- package/src/generators/flutter-app/docs/principles/stack/flutter/state-management.md +85 -0
- package/src/generators/flutter-app/docs/principles/stack/flutter/testing.md +74 -0
- package/src/generators/flutter-app/docs/principles/stack/flutter/widgets-and-composition.md +69 -0
- package/src/generators/flutter-app/files/.gitignore.template +30 -0
- package/src/generators/flutter-app/files/README.md.template +100 -0
- package/src/generators/flutter-app/files/analysis_options.yaml.template +18 -0
- package/src/generators/flutter-app/files/integration_test/app_test.dart.template +30 -0
- package/src/generators/flutter-app/files/lib/app.dart.template +24 -0
- package/src/generators/flutter-app/files/lib/config/app_config.dart +15 -0
- package/src/generators/flutter-app/files/lib/data/repositories/status_repository.dart +36 -0
- package/src/generators/flutter-app/files/lib/data/services/api_client.dart +71 -0
- package/src/generators/flutter-app/files/lib/domain/models/health_status.dart +23 -0
- package/src/generators/flutter-app/files/lib/main.dart +11 -0
- package/src/generators/flutter-app/files/lib/router.dart +23 -0
- package/src/generators/flutter-app/files/lib/ui/core/theme/app_theme.dart +110 -0
- package/src/generators/flutter-app/files/lib/ui/home/home_view.dart +89 -0
- package/src/generators/flutter-app/files/lib/ui/home/home_view_model.dart.template +38 -0
- package/src/generators/flutter-app/files/project.json.template +51 -0
- package/src/generators/flutter-app/files/pubspec.yaml.template +47 -0
- package/src/generators/flutter-app/files/test/api_client_test.dart.template +63 -0
- package/src/generators/flutter-app/files/test/fakes/fake_status_repository.dart.template +19 -0
- package/src/generators/flutter-app/files/test/home_view_test.dart.template +58 -0
- package/src/generators/flutter-app/files/tool/flutter_exec.sh.template +60 -0
- package/src/generators/flutter-app/generator.ts +362 -0
- package/src/generators/flutter-app/schema.json +23 -0
- package/src/generators/go-microservice/docs/principles/stack/go/concurrency.md +123 -0
- package/src/generators/go-microservice/docs/principles/stack/go/index.md +70 -0
- package/src/generators/go-microservice/docs/principles/stack/go/testing.md +152 -0
- package/src/generators/go-microservice/files/.air.toml.template +38 -0
- package/src/generators/go-microservice/files/.env.template +4 -0
- package/src/generators/go-microservice/files/.golangci.yml.template +82 -0
- package/src/generators/go-microservice/files/Dockerfile.dev.template +12 -0
- package/src/generators/go-microservice/files/asyncapi-pubsub.yaml.template +33 -0
- package/src/generators/go-microservice/files/asyncapi-ws.yaml.template +34 -0
- package/src/generators/go-microservice/files/cmd/api/main.go.template +149 -0
- package/src/generators/go-microservice/files/cmd/api/main_test.go.template +99 -0
- package/src/generators/go-microservice/files/cmd/worker/cleanup/main.go.template +39 -0
- package/src/generators/go-microservice/files/db/schema.sql.template +24 -0
- package/src/generators/go-microservice/files/go.mod.template +39 -0
- package/src/generators/go-microservice/files/internal/config/config.go.template +52 -0
- package/src/generators/go-microservice/files/internal/config/otel.go.template +93 -0
- package/src/generators/go-microservice/files/internal/core/domain/errors.go.template +16 -0
- package/src/generators/go-microservice/files/internal/core/domain/model.go.template +28 -0
- package/src/generators/go-microservice/files/internal/core/domain/user.go.template +13 -0
- package/src/generators/go-microservice/files/internal/core/pagination.go.template +16 -0
- package/src/generators/go-microservice/files/internal/core/service/app_service.go.template +79 -0
- package/src/generators/go-microservice/files/internal/core/service/event_hub.go.template +9 -0
- package/src/generators/go-microservice/files/internal/core/service/message_queue.go.template +10 -0
- package/src/generators/go-microservice/files/internal/core/service/outbox_repository.go.template +31 -0
- package/src/generators/go-microservice/files/internal/core/service/repository.go.template +23 -0
- package/src/generators/go-microservice/files/internal/core/service/user_repository.go.template +15 -0
- package/src/generators/go-microservice/files/internal/core/service/user_service.go.template +43 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/app_handler.go.template +108 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/auth_middleware_test.go.template +52 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/clerk_webhook.go.template +202 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/clerk_webhook_test.go.template +82 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/health_handler.go.template +80 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/idempotency/middleware.go.template +87 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/idempotency/middleware_test.go.template +76 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/idempotency/repository.go.template +37 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/middleware_auth.go.template +40 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/middleware_loadshed.go.template +38 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/middleware_logging.go.template +40 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/middleware_ratelimit.go.template +48 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/middleware_test.go.template +81 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/router.go.template +105 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/types.go.template +70 -0
- package/src/generators/go-microservice/files/internal/entrypoints/api/websocket_handler.go.template +39 -0
- package/src/generators/go-microservice/files/internal/httpclient/http_client.go.template +87 -0
- package/src/generators/go-microservice/files/internal/kafka/kafka.go.template +34 -0
- package/src/generators/go-microservice/files/internal/postgres/postgres.go.template +195 -0
- package/src/generators/go-microservice/files/internal/postgres/postgres_test.go.template +156 -0
- package/src/generators/go-microservice/files/internal/postgres/user_repository.go.template +56 -0
- package/src/generators/go-microservice/files/internal/pubsub/gcp_pubsub.go.template +35 -0
- package/src/generators/go-microservice/files/internal/websocket/client.go.template +151 -0
- package/src/generators/go-microservice/files/internal/websocket/hub.go.template +261 -0
- package/src/generators/go-microservice/files/scripts/apply-schema.sh.template +21 -0
- package/src/generators/go-microservice/files/tools/tools.go.template +10 -0
- package/src/generators/go-microservice/generator.ts +240 -0
- package/src/generators/go-microservice/schema.json +63 -0
- package/src/generators/nextjs-app/docs/principles/stack/typescript/frontend.md +65 -0
- package/src/generators/nextjs-app/files/.dockerignore.template +7 -0
- package/src/generators/nextjs-app/files/.env.example.template +24 -0
- package/src/generators/nextjs-app/files/.gitignore.template +5 -0
- package/src/generators/nextjs-app/files/Dockerfile +53 -0
- package/src/generators/nextjs-app/files/app/(auth)/sign-in/__sign-in__/page.tsx.template +9 -0
- package/src/generators/nextjs-app/files/app/(auth)/sign-up/__sign-up__/page.tsx.template +9 -0
- package/src/generators/nextjs-app/files/app/api/config/route.ts.template +39 -0
- package/src/generators/nextjs-app/files/app/api/healthz/route.test.ts +15 -0
- package/src/generators/nextjs-app/files/app/api/healthz/route.ts +5 -0
- package/src/generators/nextjs-app/files/app/api/proxy/__path__/route.test.ts.template +55 -0
- package/src/generators/nextjs-app/files/app/api/proxy/__path__/route.ts.template +126 -0
- package/src/generators/nextjs-app/files/app/error.tsx +39 -0
- package/src/generators/nextjs-app/files/app/global-error.tsx +68 -0
- package/src/generators/nextjs-app/files/app/globals.css +105 -0
- package/src/generators/nextjs-app/files/app/layout.tsx +59 -0
- package/src/generators/nextjs-app/files/app/loading.tsx +13 -0
- package/src/generators/nextjs-app/files/app/not-found.tsx +30 -0
- package/src/generators/nextjs-app/files/app/page.tsx +20 -0
- package/src/generators/nextjs-app/files/components/providers/default.tsx +19 -0
- package/src/generators/nextjs-app/files/components/providers/production.tsx +32 -0
- package/src/generators/nextjs-app/files/components/providers/telemetry.tsx +76 -0
- package/src/generators/nextjs-app/files/components/render-smoke.test.tsx +29 -0
- package/src/generators/nextjs-app/files/components/theme-provider.tsx +11 -0
- package/src/generators/nextjs-app/files/components.json +21 -0
- package/src/generators/nextjs-app/files/eslint.config.mjs +120 -0
- package/src/generators/nextjs-app/files/hooks/use-toast.ts +7 -0
- package/src/generators/nextjs-app/files/instrumentation.ts +90 -0
- package/src/generators/nextjs-app/files/lib/api/fetcher.ts.template +130 -0
- package/src/generators/nextjs-app/files/lib/config.ts +21 -0
- package/src/generators/nextjs-app/files/lib/logger.ts +29 -0
- package/src/generators/nextjs-app/files/lib/schemas/index.ts +19 -0
- package/src/generators/nextjs-app/files/lib/utils.ts +6 -0
- package/src/generators/nextjs-app/files/next.config.mjs +9 -0
- package/src/generators/nextjs-app/files/package.json +70 -0
- package/src/generators/nextjs-app/files/postcss.config.mjs +8 -0
- package/src/generators/nextjs-app/files/proxy.test.ts.template +30 -0
- package/src/generators/nextjs-app/files/proxy.ts +31 -0
- package/src/generators/nextjs-app/files/public/.gitkeep +1 -0
- package/src/generators/nextjs-app/files/tsconfig.json +42 -0
- package/src/generators/nextjs-app/files/vitest.config.mts +15 -0
- package/src/generators/nextjs-app/files/vitest.setup.ts +7 -0
- package/src/generators/nextjs-app/generator.ts +307 -0
- package/src/generators/nextjs-app/schema.json +44 -0
- package/src/generators/python-microservice/docs/principles/stack/python/async.md +168 -0
- package/src/generators/python-microservice/docs/principles/stack/python/documentation.md +240 -0
- package/src/generators/python-microservice/docs/principles/stack/python/mcp.md +147 -0
- package/src/generators/python-microservice/docs/principles/stack/python/resilience.md +193 -0
- package/src/generators/python-microservice/docs/principles/stack/python/testing.md +281 -0
- package/src/generators/python-microservice/files/.env.example.template +30 -0
- package/src/generators/python-microservice/files/Dockerfile.template +36 -0
- package/src/generators/python-microservice/files/db/schema.sql.template +19 -0
- package/src/generators/python-microservice/files/pyproject.toml.template +76 -0
- package/src/generators/python-microservice/files/scripts/apply-schema.sh.template +25 -0
- package/src/generators/python-microservice/files/src/__packageName__/adapters/comfyui.py.template +87 -0
- package/src/generators/python-microservice/files/src/__packageName__/adapters/config.py.template +48 -0
- package/src/generators/python-microservice/files/src/__packageName__/adapters/database.py.template +21 -0
- package/src/generators/python-microservice/files/src/__packageName__/adapters/message_queue.py.template +29 -0
- package/src/generators/python-microservice/files/src/__packageName__/adapters/repository.py.template +130 -0
- package/src/generators/python-microservice/files/src/__packageName__/adapters/telemetry.py.template +68 -0
- package/src/generators/python-microservice/files/src/__packageName__/adapters/websocket_hub.py.template +36 -0
- package/src/generators/python-microservice/files/src/__packageName__/core/domain/entities.py.template +22 -0
- package/src/generators/python-microservice/files/src/__packageName__/core/domain/exceptions.py.template +43 -0
- package/src/generators/python-microservice/files/src/__packageName__/core/ports.py.template +42 -0
- package/src/generators/python-microservice/files/src/__packageName__/core/service/example_service.py.template +68 -0
- package/src/generators/python-microservice/files/src/__packageName__/entrypoints/api/dependencies.py.template +50 -0
- package/src/generators/python-microservice/files/src/__packageName__/entrypoints/api/middleware.py.template +131 -0
- package/src/generators/python-microservice/files/src/__packageName__/entrypoints/api/router.py.template +37 -0
- package/src/generators/python-microservice/files/src/__packageName__/entrypoints/api/websocket_handler.py.template +20 -0
- package/src/generators/python-microservice/files/src/__packageName__/entrypoints/worker/cleanup.py.template +35 -0
- package/src/generators/python-microservice/files/src/__packageName__/entrypoints/worker/worker.py.template +28 -0
- package/src/generators/python-microservice/files/src/__packageName__/main.py.template +108 -0
- package/src/generators/python-microservice/files/tests/test_main.py.template +74 -0
- package/src/generators/python-microservice/files/tests/test_middleware.py.template +109 -0
- package/src/generators/python-microservice/files/tests/test_worker.py.template +16 -0
- package/src/generators/python-microservice/generator.ts +286 -0
- package/src/generators/python-microservice/schema.json +86 -0
- package/src/generators/shared/brand-tokens.ts +301 -0
- package/src/generators/shared/capabilities.ts +349 -0
- package/src/generators/shared/provenance.ts +61 -0
- package/src/generators/shared/scaffold-helpers.ts +309 -0
- package/src/generators/system-test-runner/files/tests/bets/.gitkeep +0 -0
- package/src/generators/system-test-runner/files/tests/bets/_archive/.gitkeep +0 -0
- package/src/generators/system-test-runner/files/tests/conftest.py.template +503 -0
- package/src/generators/system-test-runner/files/tests/pyproject.toml.template +20 -0
- package/src/generators/system-test-runner/files/tests/system/pages/__init__.py.template +9 -0
- package/src/generators/system-test-runner/files/tests/system/pages/base_page.py.template +36 -0
- package/src/generators/system-test-runner/files/tests/system/test_a11y_smoke.py.template +132 -0
- package/src/generators/system-test-runner/files/tests/system/test_contract_conformance.py.template +140 -0
- package/src/generators/system-test-runner/files/tests/system/test_layout_geometry.py.template +109 -0
- package/src/generators/system-test-runner/files/tests/system/test_render_smoke.py.template +227 -0
- package/src/generators/system-test-runner/files/tests/system/test_system.py.template +158 -0
- package/src/generators/system-test-runner/files/tests/system/test_token_conformance.py.template +206 -0
- package/src/generators/system-test-runner/files/tests/system/test_visual_regression.py.template +104 -0
- package/src/generators/system-test-runner/generator.ts +142 -0
- package/src/generators/system-test-runner/schema.json +24 -0
- package/src/generators/workspace-dev-cli/cli-src/build.mjs +42 -0
- package/src/generators/workspace-dev-cli/cli-src/dist/dev-bundle.js +2168 -0
- package/src/generators/workspace-dev-cli/cli-src/src/commands/bet.ts +442 -0
- package/src/generators/workspace-dev-cli/cli-src/src/commands/completion.ts +87 -0
- package/src/generators/workspace-dev-cli/cli-src/src/commands/doctor.ts +139 -0
- package/src/generators/workspace-dev-cli/cli-src/src/commands/lifecycle.ts +548 -0
- package/src/generators/workspace-dev-cli/cli-src/src/commands/quality.ts +127 -0
- package/src/generators/workspace-dev-cli/cli-src/src/commands/surface.ts +214 -0
- package/src/generators/workspace-dev-cli/cli-src/src/index.ts +127 -0
- package/src/generators/workspace-dev-cli/cli-src/src/registry.ts +194 -0
- package/src/generators/workspace-dev-cli/cli-src/src/theme/color.ts +130 -0
- package/src/generators/workspace-dev-cli/cli-src/src/theme/render.ts +158 -0
- package/src/generators/workspace-dev-cli/cli-src/src/theme/tokens.ts +122 -0
- package/src/generators/workspace-dev-cli/cli-src/src/util/context.ts +43 -0
- package/src/generators/workspace-dev-cli/cli-src/src/util/extensions.ts +99 -0
- package/src/generators/workspace-dev-cli/cli-src/src/util/paths.ts +46 -0
- package/src/generators/workspace-dev-cli/cli-src/src/util/proc.ts +106 -0
- package/src/generators/workspace-dev-cli/cli-src/src/util/prompt.ts +108 -0
- package/src/generators/workspace-dev-cli/cli-src/src/util/runners.ts +70 -0
- package/src/generators/workspace-dev-cli/cli-src/src/util/services.ts +221 -0
- package/src/generators/workspace-dev-cli/cli-src/src/util/version.ts +21 -0
- package/src/generators/workspace-dev-cli/cli-src/tsconfig.json +16 -0
- package/src/generators/workspace-dev-cli/files/.agents/skills/workspace-cli/SKILL.md.template +74 -0
- package/src/generators/workspace-dev-cli/files/dev.template +16 -0
- package/src/generators/workspace-dev-cli/files/docker-compose.yml.template +20 -0
- package/src/generators/workspace-dev-cli/files/scripts/cli/templates/milestone-test.pytmpl.template +46 -0
- package/src/generators/workspace-dev-cli/files/scripts/cli/templates/slice-test.pytmpl.template +38 -0
- package/src/generators/workspace-dev-cli/generator.ts +136 -0
- package/src/generators/workspace-dev-cli/schema.json +22 -0
- package/src/hidden-skills/code-intelligence.md +129 -0
- package/src/hidden-skills/groundwork-architect/SKILL.md +114 -0
- package/src/hidden-skills/groundwork-architect/references/agentic-systems.md +44 -0
- package/src/hidden-skills/groundwork-architect/references/ai-native-architecture.md +37 -0
- package/src/hidden-skills/groundwork-architect/references/api-and-contracts.md +45 -0
- package/src/hidden-skills/groundwork-architect/references/core-and-boundaries.md +45 -0
- package/src/hidden-skills/groundwork-architect/references/data-architecture.md +33 -0
- package/src/hidden-skills/groundwork-architect/references/decision-records.md +34 -0
- package/src/hidden-skills/groundwork-architect/references/durable-execution.md +45 -0
- package/src/hidden-skills/groundwork-architect/references/evolutionary-architecture.md +37 -0
- package/src/hidden-skills/groundwork-architect/references/identity-and-access.md +41 -0
- package/src/hidden-skills/groundwork-architect/references/integration-patterns.md +39 -0
- package/src/hidden-skills/groundwork-architect/references/observability.md +36 -0
- package/src/hidden-skills/groundwork-architect/references/performance-and-scale.md +41 -0
- package/src/hidden-skills/groundwork-architect/references/platform-and-delivery.md +47 -0
- package/src/hidden-skills/groundwork-architect/references/realtime-and-async.md +28 -0
- package/src/hidden-skills/groundwork-architect/references/reliability.md +31 -0
- package/src/hidden-skills/groundwork-architect/references/security-and-trust.md +47 -0
- package/src/hidden-skills/groundwork-architect/references/surface-architecture.md +40 -0
- package/src/hidden-skills/groundwork-architect/sync-anchor.md +34 -0
- package/src/hidden-skills/groundwork-architecture/architecture-template.md +50 -0
- package/src/hidden-skills/groundwork-architecture/instructions.md +139 -0
- package/src/hidden-skills/groundwork-architecture/phases/01-context-ingestion.md +18 -0
- package/src/hidden-skills/groundwork-architecture/phases/02-technical-constraints.md +27 -0
- package/src/hidden-skills/groundwork-architecture/phases/03-service-design.md +19 -0
- package/src/hidden-skills/groundwork-architecture/phases/04-data-flow-communication.md +23 -0
- package/src/hidden-skills/groundwork-architecture/phases/05-component-boundaries-contracts.md +17 -0
- package/src/hidden-skills/groundwork-architecture/phases/06-draft-review-present.md +38 -0
- package/src/hidden-skills/groundwork-architecture/phases/07-commit.md +33 -0
- package/src/hidden-skills/groundwork-architecture/templates/architecture-cache.md +43 -0
- package/src/hidden-skills/groundwork-architecture-extract/instructions.md +163 -0
- package/src/hidden-skills/groundwork-architecture-extract/templates/architecture-extract-cache.md +21 -0
- package/src/hidden-skills/groundwork-bet/briefs/slice-worker.md +191 -0
- package/src/hidden-skills/groundwork-bet/instructions.md +88 -0
- package/src/hidden-skills/groundwork-bet/templates/bet-progress-test.md +126 -0
- package/src/hidden-skills/groundwork-bet/templates/change-proposal.md +38 -0
- package/src/hidden-skills/groundwork-bet/templates/decomposition/meta.json +4 -0
- package/src/hidden-skills/groundwork-bet/templates/decomposition/milestone-index.md +35 -0
- package/src/hidden-skills/groundwork-bet/templates/decomposition/slice.md +35 -0
- package/src/hidden-skills/groundwork-bet/templates/pitch.md +45 -0
- package/src/hidden-skills/groundwork-bet/templates/technical-design/01-ui-design.md +51 -0
- package/src/hidden-skills/groundwork-bet/templates/technical-design/02-data-flows.md +36 -0
- package/src/hidden-skills/groundwork-bet/templates/technical-design/03-api-design.md +90 -0
- package/src/hidden-skills/groundwork-bet/templates/technical-design/04-data-design.md +29 -0
- package/src/hidden-skills/groundwork-bet/workflows/01-discovery.md +198 -0
- package/src/hidden-skills/groundwork-bet/workflows/02-design.md +168 -0
- package/src/hidden-skills/groundwork-bet/workflows/03-decomposition.md +246 -0
- package/src/hidden-skills/groundwork-bet/workflows/04-delivery.md +193 -0
- package/src/hidden-skills/groundwork-bet/workflows/05-validation.md +199 -0
- package/src/hidden-skills/groundwork-design-system/instructions.md +125 -0
- package/src/hidden-skills/groundwork-design-system/templates/brand-tokens.md +182 -0
- package/src/hidden-skills/groundwork-design-system/templates/design-system-cache.md +64 -0
- package/src/hidden-skills/groundwork-design-system/tracks/_foundation.md +136 -0
- package/src/hidden-skills/groundwork-design-system/tracks/agentic-protocol.md +269 -0
- package/src/hidden-skills/groundwork-design-system/tracks/cli.md +355 -0
- package/src/hidden-skills/groundwork-design-system/tracks/graphical-ui.md +330 -0
- package/src/hidden-skills/groundwork-design-system-extract/instructions.md +124 -0
- package/src/hidden-skills/groundwork-design-system-extract/templates/design-system-extract-cache.md +19 -0
- package/src/hidden-skills/groundwork-designer/SKILL.md +108 -0
- package/src/hidden-skills/groundwork-designer/references/accessibility.md +33 -0
- package/src/hidden-skills/groundwork-designer/references/ai-native-design.md +37 -0
- package/src/hidden-skills/groundwork-designer/references/design-review.md +29 -0
- package/src/hidden-skills/groundwork-designer/references/design-systems-and-tokens.md +33 -0
- package/src/hidden-skills/groundwork-designer/references/interaction-and-motion.md +37 -0
- package/src/hidden-skills/groundwork-designer/references/layout-and-space.md +33 -0
- package/src/hidden-skills/groundwork-designer/references/usability-and-ux.md +33 -0
- package/src/hidden-skills/groundwork-designer/references/visual-craft.md +49 -0
- package/src/hidden-skills/groundwork-designer/sync-anchor.md +20 -0
- package/src/hidden-skills/groundwork-doc-sync/instructions.md +100 -0
- package/src/hidden-skills/groundwork-elicit/instructions.md +66 -0
- package/src/hidden-skills/groundwork-elicit/methods.md +65 -0
- package/src/hidden-skills/groundwork-infra-adopt/instructions.md +168 -0
- package/src/hidden-skills/groundwork-infra-adopt/templates/infra-adopt-cache.md +21 -0
- package/src/hidden-skills/groundwork-mvp/instructions.md +223 -0
- package/src/hidden-skills/groundwork-mvp/templates/mvp-cache.md +9 -0
- package/src/hidden-skills/groundwork-patch/instructions.md +40 -0
- package/src/hidden-skills/groundwork-persona/instructions.md +54 -0
- package/src/hidden-skills/groundwork-product/SKILL.md +102 -0
- package/src/hidden-skills/groundwork-product/references/ai-native-product.md +45 -0
- package/src/hidden-skills/groundwork-product/references/discovery-and-opportunity.md +38 -0
- package/src/hidden-skills/groundwork-product/references/product-risks.md +52 -0
- package/src/hidden-skills/groundwork-product/references/requirements-and-specs.md +39 -0
- package/src/hidden-skills/groundwork-product/references/scope-and-sequencing.md +35 -0
- package/src/hidden-skills/groundwork-product/references/shaping-and-appetite.md +48 -0
- package/src/hidden-skills/groundwork-product/references/success-metrics-and-signals.md +37 -0
- package/src/hidden-skills/groundwork-product/sync-anchor.md +19 -0
- package/src/hidden-skills/groundwork-product-brief/instructions.md +231 -0
- package/src/hidden-skills/groundwork-product-brief-extract/instructions.md +139 -0
- package/src/hidden-skills/groundwork-product-brief-extract/templates/product-brief-extract-cache.md +17 -0
- package/src/hidden-skills/groundwork-review/checklists/architecture.md +93 -0
- package/src/hidden-skills/groundwork-review/checklists/bet-pitch.md +94 -0
- package/src/hidden-skills/groundwork-review/checklists/decomposition.md +135 -0
- package/src/hidden-skills/groundwork-review/checklists/design-system.md +85 -0
- package/src/hidden-skills/groundwork-review/checklists/domain-entity.md +66 -0
- package/src/hidden-skills/groundwork-review/checklists/implementation-readiness.md +46 -0
- package/src/hidden-skills/groundwork-review/checklists/infrastructure.md +68 -0
- package/src/hidden-skills/groundwork-review/checklists/maturity.md +71 -0
- package/src/hidden-skills/groundwork-review/checklists/product-brief.md +69 -0
- package/src/hidden-skills/groundwork-review/checklists/technical-design.md +112 -0
- package/src/hidden-skills/groundwork-review/instructions.md +181 -0
- package/src/hidden-skills/groundwork-scaffold/instructions.md +254 -0
- package/src/hidden-skills/groundwork-scaffold/phases/01-ingestion-service-mapping.md +87 -0
- package/src/hidden-skills/groundwork-scaffold/phases/02-scaffolding-execution.md +15 -0
- package/src/hidden-skills/groundwork-scaffold/phases/03-service-documentation-api-stubs.md +100 -0
- package/src/hidden-skills/groundwork-scaffold/phases/04-infrastructure-verification.md +17 -0
- package/src/hidden-skills/groundwork-scaffold/phases/05-draft-review.md +19 -0
- package/src/hidden-skills/groundwork-scaffold/phases/06-commit.md +19 -0
- package/src/hidden-skills/groundwork-scaffold/templates/scaffold-cache.md +23 -0
- package/src/hidden-skills/groundwork-scan/instructions.md +164 -0
- package/src/hidden-skills/groundwork-scan/references/digest-schema.md +66 -0
- package/src/hidden-skills/groundwork-scan/references/exclusions.md +44 -0
- package/src/hidden-skills/groundwork-scan/templates/architecture-findings.md +42 -0
- package/src/hidden-skills/groundwork-scan/templates/design-findings.md +23 -0
- package/src/hidden-skills/groundwork-scan/templates/overview.md +26 -0
- package/src/hidden-skills/groundwork-scan/templates/product-findings.md +23 -0
- package/src/hidden-skills/groundwork-scan/templates/scan-state.json +19 -0
- package/src/hidden-skills/groundwork-stack-forge/instructions.md +150 -0
- package/src/hidden-skills/groundwork-stack-forge/references/authoring-engineer-skills.md +107 -0
- package/src/hidden-skills/groundwork-surface-activation/instructions.md +138 -0
- package/src/hidden-skills/groundwork-update/briefs/reconcile-worker.md +196 -0
- package/src/hidden-skills/groundwork-update/instructions.md +200 -0
- package/src/hidden-skills/groundwork-writer/SKILL.md +278 -0
- package/src/hidden-skills/maturity-model.md +125 -0
- package/src/hidden-skills/operating-contract.md +400 -0
- package/src/hidden-skills/repo-map-schema.md +90 -0
- package/src/hidden-skills/templates/adr.md +57 -0
- package/src/hidden-skills/templates/capability-ports.md +71 -0
- package/src/hidden-skills/templates/discovery-notes.md +33 -0
- package/src/hidden-skills/templates/domain-entity.md +80 -0
- package/src/hidden-skills/templates/gap-ledger.md +21 -0
- package/src/hidden-skills/templates/handoff.md +37 -0
- package/src/hidden-skills/templates/maturity.md +39 -0
- package/src/hidden-skills/templates/surfaces.md +207 -0
- package/src/skills/groundwork-check/SKILL.md +56 -0
- package/src/skills/groundwork-check/instructions.md +70 -0
- package/src/skills/groundwork-orchestrator/SKILL.md +176 -0
- package/src/skills/groundwork-orchestrator/workflow-index.md +50 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Real, discovery-driven system tests. Parametrized over every APP service
|
|
2
|
+
found in the workspace docker-compose.yml (see conftest.services_manifest).
|
|
3
|
+
|
|
4
|
+
FAIL LOUD: the span tests are gated by GROUNDWORK_REQUIRE_TRACES=1. When that
|
|
5
|
+
flag is set (CI, `./dev test integration`), an unreachable backend or a
|
|
6
|
+
missing span is a FAILURE, not a skip.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from conftest import JAEGER_URL, _base_url, _discover_services
|
|
17
|
+
|
|
18
|
+
REQUIRE_TRACES = os.environ.get("GROUNDWORK_REQUIRE_TRACES") == "1"
|
|
19
|
+
# BatchSpanProcessor flushes ~every 5s; give spans time to land.
|
|
20
|
+
EXPORT_TIMEOUT_S = 30
|
|
21
|
+
|
|
22
|
+
# Build params at import time so pytest can show one case per service. An empty
|
|
23
|
+
# workspace yields zero params (and the suite is a no-op, which is correct).
|
|
24
|
+
_SERVICES = _discover_services()
|
|
25
|
+
_PARAMS = [pytest.param(s, id=s["name"]) for s in _SERVICES]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _fetch_trace(trace_id: str) -> dict | None:
|
|
29
|
+
deadline = time.time() + EXPORT_TIMEOUT_S
|
|
30
|
+
while time.time() < deadline:
|
|
31
|
+
resp = httpx.get(f"{JAEGER_URL}/api/traces/{trace_id}", timeout=3.0)
|
|
32
|
+
if resp.status_code == 200 and resp.json().get("data"):
|
|
33
|
+
return resp.json()["data"][0]
|
|
34
|
+
time.sleep(1)
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.parametrize("svc", _PARAMS)
|
|
39
|
+
def test_every_service_is_healthy(cluster, svc):
|
|
40
|
+
"""Each discovered service answers its health endpoint. Synchronous on
|
|
41
|
+
purpose (like the trace probes below): a plain GET needs no event loop, and
|
|
42
|
+
avoiding an async fixture keeps the suite robust across pytest-asyncio /
|
|
43
|
+
Python versions."""
|
|
44
|
+
if svc["host_port"] is None:
|
|
45
|
+
pytest.skip(f"{svc['name']} has no published host port")
|
|
46
|
+
url = f"{_base_url(svc)}{svc['health_path']}"
|
|
47
|
+
resp = httpx.get(url, timeout=5.0)
|
|
48
|
+
assert resp.status_code == 200, f"{url} -> {resp.status_code}"
|
|
49
|
+
data = resp.json()
|
|
50
|
+
if svc["type"] == "go":
|
|
51
|
+
assert data.get("status") == "ok", f"{svc['name']} status: {data}"
|
|
52
|
+
assert data.get("checks", {}).get("db") == "ok", f"{svc['name']} db: {data}"
|
|
53
|
+
else:
|
|
54
|
+
# Python FastAPI returns {"status": "alive"}; Next returns its own shape.
|
|
55
|
+
assert data.get("status") in {"ok", "alive", "ready", "healthy"}, f"{svc['name']}: {data}"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.parametrize("svc", _PARAMS)
|
|
59
|
+
def test_instrumented_get_exports_span(cluster, svc):
|
|
60
|
+
"""A traced GET to an instrumented route produces a span that reaches jaeger.
|
|
61
|
+
|
|
62
|
+
Go and Python both export spans over OTLP: Go via otel.go, Python via the
|
|
63
|
+
FastAPI instrumentation wired in adapters/telemetry.py. Next.js server-side
|
|
64
|
+
tracing is unverified through this harness (no domain GET route to probe),
|
|
65
|
+
so it xfails here.
|
|
66
|
+
"""
|
|
67
|
+
if svc["type"] not in ("go", "python"):
|
|
68
|
+
pytest.xfail(f"{svc['type']} trace export not verified through this harness (TODO)")
|
|
69
|
+
if svc["host_port"] is None:
|
|
70
|
+
pytest.skip(f"{svc['name']} has no published host port")
|
|
71
|
+
|
|
72
|
+
trace_id = uuid.uuid4().hex
|
|
73
|
+
span_id = uuid.uuid4().hex[:16]
|
|
74
|
+
# The trailing -01 (sampled) flag is load-bearing: ParentBased(AlwaysSample)
|
|
75
|
+
# inherits it. -00 exports nothing.
|
|
76
|
+
traceparent = f"00-{trace_id}-{span_id}-01"
|
|
77
|
+
# /health is excluded from tracing by the otelhttp middleware; use a real
|
|
78
|
+
# route. Default is per-service-type (Go /api/v1/entities, Python /examples);
|
|
79
|
+
# TRACE_PROBE_PATH overrides for ad-hoc runs.
|
|
80
|
+
probe = os.environ.get("TRACE_PROBE_PATH", svc["probe_path"])
|
|
81
|
+
|
|
82
|
+
# Warm up: a just-booted service can serve liveness before its DB-backed routes
|
|
83
|
+
# are ready (e.g. a schema migration is still settling), so the first requests
|
|
84
|
+
# may transiently 5xx. Retry until the route is ready so this asserts span
|
|
85
|
+
# export, not startup ordering (test_every_service_is_healthy covers liveness).
|
|
86
|
+
resp = None
|
|
87
|
+
for attempt in range(8):
|
|
88
|
+
resp = httpx.get(
|
|
89
|
+
f"{_base_url(svc)}{probe}",
|
|
90
|
+
headers={"traceparent": traceparent},
|
|
91
|
+
timeout=5.0,
|
|
92
|
+
)
|
|
93
|
+
if resp.status_code == 200:
|
|
94
|
+
break
|
|
95
|
+
time.sleep(2)
|
|
96
|
+
assert resp.status_code == 200, f"probe failed after warmup retries: {resp.status_code}"
|
|
97
|
+
|
|
98
|
+
trace = _fetch_trace(trace_id)
|
|
99
|
+
if trace is None:
|
|
100
|
+
msg = (
|
|
101
|
+
f"no span with trace_id={trace_id} reached jaeger within "
|
|
102
|
+
f"{EXPORT_TIMEOUT_S}s — the OTLP export pipeline is broken"
|
|
103
|
+
)
|
|
104
|
+
if REQUIRE_TRACES:
|
|
105
|
+
pytest.fail(msg)
|
|
106
|
+
pytest.skip(msg)
|
|
107
|
+
|
|
108
|
+
services = {p["serviceName"] for p in trace["processes"].values()}
|
|
109
|
+
assert svc["otel_name"] in services, (
|
|
110
|
+
f"expected span from '{svc['otel_name']}', got {services}"
|
|
111
|
+
)
|
|
112
|
+
assert trace["spans"], "trace arrived but contains no spans"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.parametrize("svc", _PARAMS)
|
|
116
|
+
def test_trace_context_propagates_unchanged(cluster, svc):
|
|
117
|
+
"""The service continues the incoming trace id rather than starting a new one."""
|
|
118
|
+
if svc["type"] not in ("go", "python"):
|
|
119
|
+
pytest.xfail(f"{svc['type']} trace export not verified through this harness (TODO)")
|
|
120
|
+
if svc["host_port"] is None:
|
|
121
|
+
pytest.skip(f"{svc['name']} has no published host port")
|
|
122
|
+
|
|
123
|
+
trace_id = uuid.uuid4().hex
|
|
124
|
+
span_id = uuid.uuid4().hex[:16]
|
|
125
|
+
traceparent = f"00-{trace_id}-{span_id}-01"
|
|
126
|
+
probe = os.environ.get("TRACE_PROBE_PATH", svc["probe_path"])
|
|
127
|
+
|
|
128
|
+
httpx.get(
|
|
129
|
+
f"{_base_url(svc)}{probe}",
|
|
130
|
+
headers={"traceparent": traceparent},
|
|
131
|
+
timeout=5.0,
|
|
132
|
+
)
|
|
133
|
+
trace = _fetch_trace(trace_id)
|
|
134
|
+
if trace is None:
|
|
135
|
+
msg = "injected trace context was not propagated/exported"
|
|
136
|
+
if REQUIRE_TRACES:
|
|
137
|
+
pytest.fail(msg)
|
|
138
|
+
pytest.skip(msg)
|
|
139
|
+
# Compare numerically so leading-zero normalisation doesn't cause a false fail.
|
|
140
|
+
assert int(trace["traceID"], 16) == int(trace_id, 16)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.mark.skip(reason="real CRUD lands in Phase 4")
|
|
144
|
+
@pytest.mark.asyncio
|
|
145
|
+
@pytest.mark.parametrize("svc", _PARAMS)
|
|
146
|
+
async def test_crud_round_trip(cluster, api_client: httpx.AsyncClient, svc):
|
|
147
|
+
"""POST then GET an entity and assert the round-trip through the real DB."""
|
|
148
|
+
if svc["type"] != "go":
|
|
149
|
+
pytest.skip("CRUD round-trip targets Go services for now")
|
|
150
|
+
base = _base_url(svc)
|
|
151
|
+
payload = {"name": "phase0-roundtrip"}
|
|
152
|
+
created = await api_client.post(f"{base}/api/v1/entities", json=payload, timeout=5.0)
|
|
153
|
+
assert created.status_code in (200, 201), created.text
|
|
154
|
+
entity_id = created.json().get("id")
|
|
155
|
+
assert entity_id, "POST did not return an id"
|
|
156
|
+
fetched = await api_client.get(f"{base}/api/v1/entities/{entity_id}", timeout=5.0)
|
|
157
|
+
assert fetched.status_code == 200
|
|
158
|
+
assert fetched.json().get("name") == payload["name"]
|
package/src/generators/system-test-runner/files/tests/system/test_token_conformance.py.template
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<% if (!surfaces) { -%>
|
|
2
|
+
"""Token-conformance gate — Tier 1 of the visual verification loop.
|
|
3
|
+
|
|
4
|
+
The deterministic answer to "did the polish actually land?" A behavioural test
|
|
5
|
+
passes while the atmosphere has silently degraded to a flat default — the build
|
|
6
|
+
dropped the design system's surface treatment, or the projected tokens never
|
|
7
|
+
reached the page. This gate reads computed styles and asserts the opposite:
|
|
8
|
+
the atmosphere tokens resolve, the elevation is a multi-layer stack (not one
|
|
9
|
+
default drop shadow), and any surface treatment in the DOM renders with its
|
|
10
|
+
backdrop blur and layered shadow rather than collapsing to a framework default.
|
|
11
|
+
|
|
12
|
+
It is deliberately structural, not aesthetic: it cannot judge taste (that is the
|
|
13
|
+
designer's spec-conformance pass), but it makes "the tokens are applied" a fact,
|
|
14
|
+
not an opinion. Skips cleanly when no frontend service is discovered.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import pathlib
|
|
19
|
+
|
|
20
|
+
from playwright.sync_api import Page
|
|
21
|
+
|
|
22
|
+
# Atmosphere tokens the nextjs-app generator projects into brand.css from the
|
|
23
|
+
# design system's visual block. Their presence proves the projection landed.
|
|
24
|
+
_PROBE = """
|
|
25
|
+
() => {
|
|
26
|
+
const root = getComputedStyle(document.documentElement);
|
|
27
|
+
const tok = (n) => (root.getPropertyValue(n) || '').trim();
|
|
28
|
+
const countLayers = (s) => {
|
|
29
|
+
s = (s || '').trim();
|
|
30
|
+
if (!s || s === 'none') return 0;
|
|
31
|
+
let depth = 0, n = 1;
|
|
32
|
+
for (const ch of s) {
|
|
33
|
+
if (ch === '(') depth++;
|
|
34
|
+
else if (ch === ')') depth--;
|
|
35
|
+
else if (ch === ',' && depth === 0) n++;
|
|
36
|
+
}
|
|
37
|
+
return n;
|
|
38
|
+
};
|
|
39
|
+
const surfaces = [];
|
|
40
|
+
for (const el of document.querySelectorAll('.surface-glass, .surface-elevated, .surface-hero')) {
|
|
41
|
+
const cs = getComputedStyle(el);
|
|
42
|
+
const bf = (cs.backdropFilter || cs.webkitBackdropFilter || '').trim();
|
|
43
|
+
surfaces.push({
|
|
44
|
+
cls: el.className,
|
|
45
|
+
hasBlur: /blur\\(/.test(bf),
|
|
46
|
+
shadowLayers: countLayers(cs.boxShadow),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
shadowMid: tok('--gw-shadow-mid'),
|
|
51
|
+
shadowMidLayers: countLayers(tok('--gw-shadow-mid')),
|
|
52
|
+
blurStandard: tok('--gw-blur-standard'),
|
|
53
|
+
surfaces,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _load_routes() -> tuple[str, ...]:
|
|
60
|
+
manifest = pathlib.Path("tests/system/routes.json")
|
|
61
|
+
if manifest.exists():
|
|
62
|
+
try:
|
|
63
|
+
routes = json.loads(manifest.read_text())
|
|
64
|
+
if isinstance(routes, list) and routes:
|
|
65
|
+
return tuple(routes)
|
|
66
|
+
except Exception: # noqa: BLE001 — a malformed manifest falls back to root
|
|
67
|
+
pass
|
|
68
|
+
return ("/",)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
ROUTES = _load_routes()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _assert_token_conformance(page: Page, surface_slug: str, base_url: str | None) -> None:
|
|
75
|
+
for route in ROUTES:
|
|
76
|
+
target = (base_url.rstrip("/") + route) if base_url else route
|
|
77
|
+
page.goto(target, wait_until="load")
|
|
78
|
+
page.wait_for_load_state("networkidle")
|
|
79
|
+
r = page.evaluate(_PROBE)
|
|
80
|
+
|
|
81
|
+
# The atmosphere layer projected into :root.
|
|
82
|
+
assert r["shadowMid"], (
|
|
83
|
+
f"{surface_slug} {route}: --gw-shadow-mid is undefined — the brand.css "
|
|
84
|
+
f"projection did not reach the page (atmosphere tokens missing)."
|
|
85
|
+
)
|
|
86
|
+
assert r["blurStandard"], (
|
|
87
|
+
f"{surface_slug} {route}: --gw-blur-standard is undefined — blur tokens "
|
|
88
|
+
f"did not project."
|
|
89
|
+
)
|
|
90
|
+
assert r["shadowMidLayers"] >= 2, (
|
|
91
|
+
f"{surface_slug} {route}: --gw-shadow-mid is a single-layer shadow "
|
|
92
|
+
f"({r['shadowMidLayers']} layer) — the multi-layer elevation stack "
|
|
93
|
+
f"degraded to a default drop shadow."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Any surface treatment in the DOM rendered its atmosphere, not a flat default.
|
|
97
|
+
for s in r["surfaces"]:
|
|
98
|
+
assert s["hasBlur"], (
|
|
99
|
+
f"{surface_slug} {route}: '{s['cls']}' rendered with no backdrop "
|
|
100
|
+
f"blur — the glass treatment was stripped to a flat fill."
|
|
101
|
+
)
|
|
102
|
+
assert s["shadowLayers"] >= 2, (
|
|
103
|
+
f"{surface_slug} {route}: '{s['cls']}' rendered with "
|
|
104
|
+
f"{s['shadowLayers']} shadow layer(s) — the elevation stack was lost."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_frontend_token_conformance(cluster, frontend_base_url, page: Page):
|
|
109
|
+
_assert_token_conformance(page, "frontend", frontend_base_url)
|
|
110
|
+
<% } else { -%>
|
|
111
|
+
"""Token-conformance gate, one test per graphical surface — Tier 1 of the visual loop.
|
|
112
|
+
|
|
113
|
+
The deterministic answer to "did the polish actually land?" A behavioural test
|
|
114
|
+
passes while the atmosphere has silently degraded to a flat default. This gate
|
|
115
|
+
reads computed styles and asserts the atmosphere tokens resolve, the elevation is
|
|
116
|
+
a multi-layer stack, and any surface treatment in the DOM renders with its
|
|
117
|
+
backdrop blur and layered shadow rather than a framework default. Structural, not
|
|
118
|
+
aesthetic — taste is the designer's spec-conformance pass. Skips cleanly when a
|
|
119
|
+
surface is not reachable.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
import json
|
|
123
|
+
import pathlib
|
|
124
|
+
|
|
125
|
+
from playwright.sync_api import Page
|
|
126
|
+
|
|
127
|
+
_PROBE = """
|
|
128
|
+
() => {
|
|
129
|
+
const root = getComputedStyle(document.documentElement);
|
|
130
|
+
const tok = (n) => (root.getPropertyValue(n) || '').trim();
|
|
131
|
+
const countLayers = (s) => {
|
|
132
|
+
s = (s || '').trim();
|
|
133
|
+
if (!s || s === 'none') return 0;
|
|
134
|
+
let depth = 0, n = 1;
|
|
135
|
+
for (const ch of s) {
|
|
136
|
+
if (ch === '(') depth++;
|
|
137
|
+
else if (ch === ')') depth--;
|
|
138
|
+
else if (ch === ',' && depth === 0) n++;
|
|
139
|
+
}
|
|
140
|
+
return n;
|
|
141
|
+
};
|
|
142
|
+
const surfaces = [];
|
|
143
|
+
for (const el of document.querySelectorAll('.surface-glass, .surface-elevated, .surface-hero')) {
|
|
144
|
+
const cs = getComputedStyle(el);
|
|
145
|
+
const bf = (cs.backdropFilter || cs.webkitBackdropFilter || '').trim();
|
|
146
|
+
surfaces.push({
|
|
147
|
+
cls: el.className,
|
|
148
|
+
hasBlur: /blur\\(/.test(bf),
|
|
149
|
+
shadowLayers: countLayers(cs.boxShadow),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
shadowMid: tok('--gw-shadow-mid'),
|
|
154
|
+
shadowMidLayers: countLayers(tok('--gw-shadow-mid')),
|
|
155
|
+
blurStandard: tok('--gw-blur-standard'),
|
|
156
|
+
surfaces,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _load_routes() -> tuple[str, ...]:
|
|
163
|
+
manifest = pathlib.Path("tests/system/routes.json")
|
|
164
|
+
if manifest.exists():
|
|
165
|
+
try:
|
|
166
|
+
routes = json.loads(manifest.read_text())
|
|
167
|
+
if isinstance(routes, list) and routes:
|
|
168
|
+
return tuple(routes)
|
|
169
|
+
except Exception: # noqa: BLE001 — a malformed manifest falls back to root
|
|
170
|
+
pass
|
|
171
|
+
return ("/",)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
ROUTES = _load_routes()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _assert_token_conformance(page: Page, surface_slug: str) -> None:
|
|
178
|
+
for route in ROUTES:
|
|
179
|
+
page.goto(route, wait_until="load")
|
|
180
|
+
page.wait_for_load_state("networkidle")
|
|
181
|
+
r = page.evaluate(_PROBE)
|
|
182
|
+
|
|
183
|
+
assert r["shadowMid"], (
|
|
184
|
+
f"{surface_slug} {route}: --gw-shadow-mid is undefined — the brand.css "
|
|
185
|
+
f"projection did not reach the page."
|
|
186
|
+
)
|
|
187
|
+
assert r["blurStandard"], (
|
|
188
|
+
f"{surface_slug} {route}: --gw-blur-standard is undefined."
|
|
189
|
+
)
|
|
190
|
+
assert r["shadowMidLayers"] >= 2, (
|
|
191
|
+
f"{surface_slug} {route}: --gw-shadow-mid degraded to a single-layer "
|
|
192
|
+
f"shadow ({r['shadowMidLayers']} layer)."
|
|
193
|
+
)
|
|
194
|
+
for s in r["surfaces"]:
|
|
195
|
+
assert s["hasBlur"], (
|
|
196
|
+
f"{surface_slug} {route}: '{s['cls']}' rendered with no backdrop blur."
|
|
197
|
+
)
|
|
198
|
+
assert s["shadowLayers"] >= 2, (
|
|
199
|
+
f"{surface_slug} {route}: '{s['cls']}' rendered with "
|
|
200
|
+
f"{s['shadowLayers']} shadow layer(s) — elevation stack lost."
|
|
201
|
+
)
|
|
202
|
+
<% graphicalSurfaces.forEach((s) => { %>
|
|
203
|
+
|
|
204
|
+
def test_<%= s.ident %>_token_conformance(cluster, <%= s.ident %>_page: Page):
|
|
205
|
+
_assert_token_conformance(<%= s.ident %>_page, "<%= s.slug %>")
|
|
206
|
+
<% }) %><% } -%>
|
package/src/generators/system-test-runner/files/tests/system/test_visual_regression.py.template
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Visual regression gate — Tier 1 of the visual verification loop, OPT-IN.
|
|
2
|
+
|
|
3
|
+
Screenshot-baseline diffing catches *unintended* visual change against a known-good
|
|
4
|
+
baseline. It is off by default (decision D8): it carries real baseline-management and
|
|
5
|
+
flakiness cost, and it does nothing for a screen's *first* render — that is what the
|
|
6
|
+
render-smoke, a11y, and geometry gates plus the agent's own inspection cover. Turn it
|
|
7
|
+
on only once a surface is visually stable and worth pinning.
|
|
8
|
+
|
|
9
|
+
Enable: set GROUNDWORK_VISUAL_REGRESSION=1. Baselines live in
|
|
10
|
+
tests/system/visual-baselines/<surface>/<route>.png and are committed to the repo.
|
|
11
|
+
Update protocol: review the diff, then re-run with GROUNDWORK_VISUAL_REGRESSION=update
|
|
12
|
+
to overwrite the baseline deliberately — never auto-update in CI.
|
|
13
|
+
|
|
14
|
+
Determinism note: when enabled the gate pins the viewport, disables animations, and
|
|
15
|
+
masks nothing by default; add per-project masks for dynamic regions (timestamps,
|
|
16
|
+
avatars) before trusting it. Requires Pillow for the pixel diff; skips if absent.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import pathlib
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
from playwright.sync_api import Page
|
|
25
|
+
|
|
26
|
+
_MODE = os.environ.get("GROUNDWORK_VISUAL_REGRESSION", "")
|
|
27
|
+
_BASELINE_DIR = pathlib.Path("tests/system/visual-baselines")
|
|
28
|
+
# Fraction of differing pixels tolerated before a route is considered changed.
|
|
29
|
+
_DIFF_THRESHOLD = 0.01
|
|
30
|
+
DESKTOP = (1280, 800)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _load_routes() -> tuple[str, ...]:
|
|
34
|
+
manifest = pathlib.Path("tests/system/routes.json")
|
|
35
|
+
if manifest.exists():
|
|
36
|
+
try:
|
|
37
|
+
routes = json.loads(manifest.read_text())
|
|
38
|
+
if isinstance(routes, list) and routes:
|
|
39
|
+
return tuple(routes)
|
|
40
|
+
except Exception: # noqa: BLE001 — a malformed manifest falls back to root
|
|
41
|
+
pass
|
|
42
|
+
return ("/",)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
ROUTES = _load_routes()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _diff_fraction(a_bytes: bytes, b_bytes: bytes) -> float:
|
|
49
|
+
from io import BytesIO
|
|
50
|
+
|
|
51
|
+
from PIL import Image, ImageChops
|
|
52
|
+
|
|
53
|
+
a = Image.open(BytesIO(a_bytes)).convert("RGB")
|
|
54
|
+
b = Image.open(BytesIO(b_bytes)).convert("RGB")
|
|
55
|
+
if a.size != b.size:
|
|
56
|
+
return 1.0
|
|
57
|
+
diff = ImageChops.difference(a, b)
|
|
58
|
+
bbox = diff.getbbox()
|
|
59
|
+
if bbox is None:
|
|
60
|
+
return 0.0
|
|
61
|
+
changed = sum(1 for px in diff.crop(bbox).getdata() if any(px))
|
|
62
|
+
return changed / float(a.size[0] * a.size[1])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _visual_regression(page: Page, surface_slug: str, base_url: str | None) -> None:
|
|
66
|
+
if not _MODE:
|
|
67
|
+
pytest.skip("visual regression is opt-in; set GROUNDWORK_VISUAL_REGRESSION=1")
|
|
68
|
+
try:
|
|
69
|
+
import PIL # noqa: F401
|
|
70
|
+
except ImportError:
|
|
71
|
+
pytest.skip("visual regression needs Pillow (pip install Pillow)")
|
|
72
|
+
|
|
73
|
+
page.set_viewport_size({"width": DESKTOP[0], "height": DESKTOP[1]})
|
|
74
|
+
out_dir = _BASELINE_DIR / surface_slug
|
|
75
|
+
for route in ROUTES:
|
|
76
|
+
target = (base_url.rstrip("/") + route) if base_url else route
|
|
77
|
+
page.goto(target, wait_until="load")
|
|
78
|
+
page.wait_for_load_state("networkidle")
|
|
79
|
+
shot = page.screenshot(animations="disabled", full_page=True)
|
|
80
|
+
|
|
81
|
+
slug = route.strip("/").replace("/", "_") or "root"
|
|
82
|
+
baseline = out_dir / f"{slug}.png"
|
|
83
|
+
if _MODE == "update" or not baseline.exists():
|
|
84
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
baseline.write_bytes(shot)
|
|
86
|
+
if _MODE != "update":
|
|
87
|
+
pytest.skip(f"no baseline for {surface_slug} {route}; wrote one — review and commit it")
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
frac = _diff_fraction(baseline.read_bytes(), shot)
|
|
91
|
+
assert frac <= _DIFF_THRESHOLD, (
|
|
92
|
+
f"{surface_slug} {route} changed visually: {frac:.1%} of pixels differ "
|
|
93
|
+
f"from the baseline (threshold {_DIFF_THRESHOLD:.0%}). Review the change; "
|
|
94
|
+
f"re-run with GROUNDWORK_VISUAL_REGRESSION=update to accept it."
|
|
95
|
+
)
|
|
96
|
+
<% if (!surfaces) { -%>
|
|
97
|
+
|
|
98
|
+
def test_frontend_visual_regression(cluster, frontend_base_url, page: Page):
|
|
99
|
+
_visual_regression(page, "frontend", frontend_base_url)
|
|
100
|
+
<% } else { -%><% graphicalSurfaces.forEach((s) => { %>
|
|
101
|
+
|
|
102
|
+
def test_<%= s.ident %>_visual_regression(cluster, <%= s.ident %>_page: Page):
|
|
103
|
+
_visual_regression(<%= s.ident %>_page, "<%= s.slug %>", None)
|
|
104
|
+
<% }) %><% } -%>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatFiles,
|
|
3
|
+
generateFiles,
|
|
4
|
+
Tree,
|
|
5
|
+
} from '@nx/devkit';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { recordGeneratorProvenance } from '../shared/provenance';
|
|
8
|
+
|
|
9
|
+
/** One surface from the registry (`.groundwork/surfaces.json`). The slug is the
|
|
10
|
+
* join key everywhere — ledger cells, fixture map keys, generated fixture
|
|
11
|
+
* names. `medium` is the registry's `testMedium`; `reach` is an optional
|
|
12
|
+
* static base URL (playwright / protocol-client), launch command
|
|
13
|
+
* (subprocess-cli), or test-harness command (flutter-integration /
|
|
14
|
+
* playwright-electron) — omitted, URL mediums are discovered at test time
|
|
15
|
+
* from the docker-compose service named after the slug, and harness mediums
|
|
16
|
+
* resolve to `npx nx run <slug>:<target>` when services/<slug> exists. */
|
|
17
|
+
export interface SurfaceSpec {
|
|
18
|
+
slug: string;
|
|
19
|
+
medium: string;
|
|
20
|
+
reach?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SystemTestRunnerGeneratorSchema {
|
|
24
|
+
projectPrefix?: string;
|
|
25
|
+
/** Deprecated single-surface alias — `surfaces` supersedes it. */
|
|
26
|
+
interfaceMedium?: 'graphical-ui' | 'cli' | 'agentic-protocol';
|
|
27
|
+
/** JSON array of SurfaceSpec (string form when invoked from the CLI). */
|
|
28
|
+
surfaces?: string | SurfaceSpec[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Parsed spec plus the python-identifier form of the slug, for fixture names. */
|
|
32
|
+
interface SurfaceTemplateSpec extends SurfaceSpec {
|
|
33
|
+
ident: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseSurfaces(
|
|
37
|
+
raw: string | SurfaceSpec[] | undefined
|
|
38
|
+
): SurfaceTemplateSpec[] | null {
|
|
39
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
let specs: unknown;
|
|
43
|
+
if (typeof raw === 'string') {
|
|
44
|
+
try {
|
|
45
|
+
specs = JSON.parse(raw);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
throw new Error(`--surfaces is not valid JSON: ${(e as Error).message}`);
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
specs = raw;
|
|
51
|
+
}
|
|
52
|
+
if (!Array.isArray(specs) || specs.length === 0) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'--surfaces must be a non-empty JSON array of {slug, medium, reach?} objects'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
for (const spec of specs as SurfaceSpec[]) {
|
|
58
|
+
if (
|
|
59
|
+
!spec ||
|
|
60
|
+
typeof spec.slug !== 'string' ||
|
|
61
|
+
spec.slug.length === 0 ||
|
|
62
|
+
typeof spec.medium !== 'string' ||
|
|
63
|
+
spec.medium.length === 0
|
|
64
|
+
) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`every --surfaces entry needs a slug and a medium: ${JSON.stringify(spec)}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return (specs as SurfaceSpec[]).map((s) => ({
|
|
71
|
+
...s,
|
|
72
|
+
ident: s.slug.replace(/-/g, '_'),
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function systemTestRunnerGenerator(
|
|
77
|
+
tree: Tree,
|
|
78
|
+
options: SystemTestRunnerGeneratorSchema
|
|
79
|
+
) {
|
|
80
|
+
const projectPrefix = options.projectPrefix || 'groundwork';
|
|
81
|
+
const interfaceMedium = options.interfaceMedium || 'graphical-ui';
|
|
82
|
+
// Registry mode: `surfaces` carries the full set of test mediums and wins
|
|
83
|
+
// over the deprecated single-medium alias when both are given.
|
|
84
|
+
const surfaces = parseSurfaces(options.surfaces);
|
|
85
|
+
|
|
86
|
+
const graphicalSurfaces = (surfaces ?? []).filter((s) => s.medium === 'playwright');
|
|
87
|
+
const cliSurfaces = (surfaces ?? []).filter((s) => s.medium === 'subprocess-cli');
|
|
88
|
+
const protocolSurfaces = (surfaces ?? []).filter((s) => s.medium === 'protocol-client');
|
|
89
|
+
// App-harness mediums: the surface ships its own test harness (Flutter
|
|
90
|
+
// integration_test / Playwright _electron smoke); the runner fixture drives
|
|
91
|
+
// it as a subprocess through the app's Nx target.
|
|
92
|
+
const flutterSurfaces = (surfaces ?? []).filter((s) => s.medium === 'flutter-integration');
|
|
93
|
+
const electronSurfaces = (surfaces ?? []).filter((s) => s.medium === 'playwright-electron');
|
|
94
|
+
|
|
95
|
+
// Playwright structure follows graphical surfaces: any playwright surface in
|
|
96
|
+
// registry mode, the graphical-ui value in single-medium mode. pexpect ships
|
|
97
|
+
// alongside the subprocess runners so interactive (REPL) CLI flows are testable.
|
|
98
|
+
const includePlaywright = surfaces
|
|
99
|
+
? graphicalSurfaces.length > 0
|
|
100
|
+
: interfaceMedium === 'graphical-ui';
|
|
101
|
+
const includePexpect = cliSurfaces.length > 0;
|
|
102
|
+
|
|
103
|
+
// Generate files into the workspace root
|
|
104
|
+
generateFiles(
|
|
105
|
+
tree,
|
|
106
|
+
path.join(__dirname, '..', '..', '..', '..', 'src', 'generators', 'system-test-runner', 'files'),
|
|
107
|
+
'.',
|
|
108
|
+
{
|
|
109
|
+
...options,
|
|
110
|
+
projectPrefix,
|
|
111
|
+
interfaceMedium,
|
|
112
|
+
surfaces,
|
|
113
|
+
graphicalSurfaces,
|
|
114
|
+
cliSurfaces,
|
|
115
|
+
protocolSurfaces,
|
|
116
|
+
flutterSurfaces,
|
|
117
|
+
electronSurfaces,
|
|
118
|
+
includePlaywright,
|
|
119
|
+
includePexpect,
|
|
120
|
+
tmpl: ''
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Playwright structure ships only with a graphical surface: the page-object
|
|
125
|
+
// package, the axe-core a11y smoke, and the render-smoke gate depend on
|
|
126
|
+
// pytest-playwright, which the pyproject template declares only when
|
|
127
|
+
// includePlaywright is set.
|
|
128
|
+
if (!includePlaywright) {
|
|
129
|
+
tree.delete('tests/system/pages');
|
|
130
|
+
tree.delete('tests/system/test_a11y_smoke.py');
|
|
131
|
+
tree.delete('tests/system/test_render_smoke.py');
|
|
132
|
+
tree.delete('tests/system/test_layout_geometry.py');
|
|
133
|
+
tree.delete('tests/system/test_visual_regression.py');
|
|
134
|
+
tree.delete('tests/system/test_token_conformance.py');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await formatFiles(tree);
|
|
138
|
+
|
|
139
|
+
recordGeneratorProvenance(tree, 'system-test-runner', options as unknown as Record<string, unknown>);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default systemTestRunnerGenerator;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "SystemTestRunner",
|
|
4
|
+
"title": "System Test Runner",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"projectPrefix": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "The prefix for Docker container names (e.g. 'myapp')",
|
|
10
|
+
"default": "groundwork"
|
|
11
|
+
},
|
|
12
|
+
"surfaces": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "JSON array of surface specs from the surface registry (.groundwork/surfaces.json): [{\"slug\": \"web-app\", \"medium\": \"playwright\", \"reach\": \"...\"}]. 'medium' is the registry's testMedium (playwright | subprocess-cli | protocol-client | flutter-integration | playwright-electron | others register without a runner fixture); 'reach' is an optional static base URL (playwright/protocol-client), launch command (subprocess-cli), or test-harness command (flutter-integration/playwright-electron) — omitted, URL mediums are discovered from docker-compose.yml by slug and harness mediums resolve to 'npx nx run <slug>:test-integration' / 'npx nx run <slug>:smoke' when services/<slug> exists. Generates the slug-keyed 'surfaces' fixture plus one runner fixture per surface. Supersedes interfaceMedium when both are given."
|
|
15
|
+
},
|
|
16
|
+
"interfaceMedium": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "DEPRECATED single-surface alias for 'surfaces' — the project's interface medium, from docs/design-system.md's interface track. Used only when no surface registry exists; generates the legacy frontend_base_url fixture instead of the surfaces map",
|
|
19
|
+
"enum": ["graphical-ui", "cli", "agentic-protocol"],
|
|
20
|
+
"default": "graphical-ui"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"required": []
|
|
24
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Bundles the ./dev CLI source into a single zero-runtime-dependency file that the
|
|
2
|
+
// workspace-dev-cli generator copies verbatim into generated projects.
|
|
3
|
+
//
|
|
4
|
+
// The output is intentionally written OUTSIDE the generator's `files/` directory so it
|
|
5
|
+
// never passes through Nx's EJS templating (a bundle can contain `<%` in strings/regexes
|
|
6
|
+
// that EJS would corrupt). generator.ts reads dist/dev-bundle.js and writes it raw.
|
|
7
|
+
//
|
|
8
|
+
// Run from the repo root: `npm run build:dev-cli`.
|
|
9
|
+
|
|
10
|
+
import { build } from 'esbuild';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
|
|
15
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
// Embed the package version so the deployed bundle knows its vintage (./dev --version,
|
|
18
|
+
// doctor's framework-alignment check). NOTE: this makes the committed bundle
|
|
19
|
+
// version-dependent — rebuild it after every `npm version` bump (the bundle-freshness
|
|
20
|
+
// contract test fails the release gates if you forget).
|
|
21
|
+
const PKG_VERSION = JSON.parse(
|
|
22
|
+
fs.readFileSync(path.join(here, '..', '..', '..', '..', 'package.json'), 'utf8'),
|
|
23
|
+
).version;
|
|
24
|
+
|
|
25
|
+
// Default writes the committed bundle. Tests set DEV_CLI_OUTFILE to a temp path to
|
|
26
|
+
// build a fresh bundle without mutating the working tree, then diff it against the
|
|
27
|
+
// committed one (the freshness contract) — sharing this one esbuild config.
|
|
28
|
+
const outfile = process.env.DEV_CLI_OUTFILE || path.join(here, 'dist', 'dev-bundle.js');
|
|
29
|
+
|
|
30
|
+
await build({
|
|
31
|
+
entryPoints: [path.join(here, 'src', 'index.ts')],
|
|
32
|
+
bundle: true,
|
|
33
|
+
platform: 'node',
|
|
34
|
+
target: 'node18',
|
|
35
|
+
format: 'cjs',
|
|
36
|
+
outfile,
|
|
37
|
+
legalComments: 'none',
|
|
38
|
+
logLevel: 'info',
|
|
39
|
+
define: { 'process.env.GW_DEV_CLI_VERSION': JSON.stringify(PKG_VERSION) },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
console.log(`Built ${outfile}`);
|