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,35 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
4
|
+
from sqlalchemy import text
|
|
5
|
+
from <%= packageName %>.adapters.config import settings
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
async def cleanup_idempotency_keys():
|
|
10
|
+
"""
|
|
11
|
+
Background worker to delete idempotency keys older than 24 hours.
|
|
12
|
+
Designed to be run as a cron job or Kubernetes CronJob.
|
|
13
|
+
"""
|
|
14
|
+
logger.info("Starting idempotency key cleanup worker...")
|
|
15
|
+
|
|
16
|
+
if not settings.database_url:
|
|
17
|
+
logger.error("DATABASE_URL not set.")
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
engine = create_async_engine(settings.database_url)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
async with engine.begin() as conn:
|
|
24
|
+
result = await conn.execute(
|
|
25
|
+
text("DELETE FROM idempotency_keys WHERE created_at < NOW() - INTERVAL '24 hours'")
|
|
26
|
+
)
|
|
27
|
+
logger.info(f"Cleanup complete. Deleted {result.rowcount} expired idempotency keys.")
|
|
28
|
+
except Exception as e:
|
|
29
|
+
logger.error(f"Error during idempotency cleanup: {e}", exc_info=True)
|
|
30
|
+
finally:
|
|
31
|
+
await engine.dispose()
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
logging.basicConfig(level=logging.INFO)
|
|
35
|
+
asyncio.run(cleanup_idempotency_keys())
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import runpod
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
<% if (llm) { %>
|
|
5
|
+
from <%= packageName %>.adapters.llm import LLMClient
|
|
6
|
+
<% } %>
|
|
7
|
+
<% if (postgres) { %>
|
|
8
|
+
from <%= packageName %>.adapters.database import get_db_session
|
|
9
|
+
from <%= packageName %>.adapters.repository import PostgresExampleRepository
|
|
10
|
+
from <%= packageName %>.core.domain.entities import ExampleEntity
|
|
11
|
+
import uuid
|
|
12
|
+
import asyncio
|
|
13
|
+
<% } %>
|
|
14
|
+
|
|
15
|
+
def handler(job: Dict[str, Any]) -> Dict[str, Any]:
|
|
16
|
+
job_input = job['input']
|
|
17
|
+
|
|
18
|
+
# Example boundary validation
|
|
19
|
+
if 'prompt' not in job_input:
|
|
20
|
+
return {"error": "Missing required field 'prompt' in input"}
|
|
21
|
+
|
|
22
|
+
prompt = job_input['prompt']
|
|
23
|
+
|
|
24
|
+
# In a real app, initialize services here. For async, use asyncio.run
|
|
25
|
+
return {"status": "success", "echo": prompt}
|
|
26
|
+
|
|
27
|
+
if __name__ == '__main__':
|
|
28
|
+
runpod.serverless.start({'handler': handler})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from fastapi import FastAPI, Request
|
|
3
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
+
import asyncio
|
|
5
|
+
|
|
6
|
+
from <%= packageName %>.adapters.config import settings
|
|
7
|
+
from <%= packageName %>.adapters.telemetry import setup_telemetry, setup_logging
|
|
8
|
+
<% if (rest) { %>
|
|
9
|
+
from <%= packageName %>.entrypoints.api.router import router as example_router
|
|
10
|
+
from <%= packageName %>.entrypoints.api.middleware import (
|
|
11
|
+
DomainExceptionHandlerMiddleware,
|
|
12
|
+
ConcurrencyLimitingMiddleware,
|
|
13
|
+
GlobalTimeoutMiddleware,
|
|
14
|
+
IdempotencyMiddleware
|
|
15
|
+
)
|
|
16
|
+
<% } %>
|
|
17
|
+
<% if (postgres) { %>
|
|
18
|
+
from <%= packageName %>.adapters.database import engine
|
|
19
|
+
from <%= packageName %>.adapters.repository import PostgresIdempotencyRepository
|
|
20
|
+
<% } %>
|
|
21
|
+
|
|
22
|
+
<% if (rest && websockets) { %>
|
|
23
|
+
from <%= packageName %>.entrypoints.api.websocket_handler import router as ws_router
|
|
24
|
+
<% } %>
|
|
25
|
+
|
|
26
|
+
@asynccontextmanager
|
|
27
|
+
async def lifespan(app: FastAPI):
|
|
28
|
+
# Initialize resources like Redis pools, DB connections
|
|
29
|
+
app.state.startup_failed = False
|
|
30
|
+
# Configure JSON logging with trace_id/span_id correlation. (Tracing itself is
|
|
31
|
+
# set up at module scope below — instrumenting the app must happen before
|
|
32
|
+
# Starlette builds its middleware stack, which is already done by the time the
|
|
33
|
+
# lifespan runs, so it cannot be done here.)
|
|
34
|
+
setup_logging()
|
|
35
|
+
try:
|
|
36
|
+
<% if (postgres) { %>
|
|
37
|
+
# Provide the repository for the idempotency middleware
|
|
38
|
+
# For simplicity, we just inject the class/engine. In a real app, DI container is better.
|
|
39
|
+
# But wait, PostgresIdempotencyRepository needs an AsyncSession which is bound to a request scope.
|
|
40
|
+
# For the middleware, we can bind an engine or sessionmaker to app.state.
|
|
41
|
+
app.state.db_engine = engine
|
|
42
|
+
<% } %>
|
|
43
|
+
pass
|
|
44
|
+
except Exception:
|
|
45
|
+
app.state.startup_failed = True
|
|
46
|
+
|
|
47
|
+
yield
|
|
48
|
+
|
|
49
|
+
# Cleanup resources gracefully
|
|
50
|
+
# Flush buffered spans before exit (BatchSpanProcessor flushes on shutdown).
|
|
51
|
+
_tracer_provider.shutdown()
|
|
52
|
+
<% if (postgres) { %>
|
|
53
|
+
await engine.dispose()
|
|
54
|
+
<% } %>
|
|
55
|
+
|
|
56
|
+
app = FastAPI(
|
|
57
|
+
title="<%= name %> API",
|
|
58
|
+
lifespan=lifespan
|
|
59
|
+
)
|
|
60
|
+
# Set up tracing + instrument the app at import time, BEFORE Starlette builds its
|
|
61
|
+
# middleware stack (which happens on first request / at startup). FastAPIInstrumentor
|
|
62
|
+
# adds an ASGI middleware; doing this inside the lifespan is too late and silently
|
|
63
|
+
# produces no spans. The provider is flushed on shutdown in the lifespan above.
|
|
64
|
+
_tracer_provider = setup_telemetry(app, settings.otel_exporter_otlp_endpoint)
|
|
65
|
+
|
|
66
|
+
<% if (rest) { %>
|
|
67
|
+
# Middlewares are executed in reverse order of addition
|
|
68
|
+
# 1. Exception Handler catches all
|
|
69
|
+
app.add_middleware(DomainExceptionHandlerMiddleware)
|
|
70
|
+
# 2. Timeout prevents hanging
|
|
71
|
+
app.add_middleware(GlobalTimeoutMiddleware, timeout=30.0)
|
|
72
|
+
# 3. Concurrency Limits protect from overload
|
|
73
|
+
app.add_middleware(ConcurrencyLimitingMiddleware, max_concurrent_requests=100)
|
|
74
|
+
# 5. Idempotency protects mutating endpoints
|
|
75
|
+
app.add_middleware(IdempotencyMiddleware)
|
|
76
|
+
<% } %>
|
|
77
|
+
# 4. CORS — always add
|
|
78
|
+
app.add_middleware(
|
|
79
|
+
CORSMiddleware,
|
|
80
|
+
allow_origins=["https://your-frontend.com"],
|
|
81
|
+
allow_credentials=True,
|
|
82
|
+
allow_methods=["GET", "POST", "PUT", "DELETE"],
|
|
83
|
+
allow_headers=["*"],
|
|
84
|
+
)
|
|
85
|
+
<% if (rest) { %>
|
|
86
|
+
app.include_router(example_router)
|
|
87
|
+
<% } %>
|
|
88
|
+
<% if (rest && websockets) { %>
|
|
89
|
+
app.include_router(ws_router)
|
|
90
|
+
<% } %>
|
|
91
|
+
|
|
92
|
+
@app.get("/health", status_code=200)
|
|
93
|
+
@app.get("/healthz", status_code=200)
|
|
94
|
+
async def liveness_probe():
|
|
95
|
+
"""Liveness probe."""
|
|
96
|
+
return {"status": "alive"}
|
|
97
|
+
|
|
98
|
+
@app.get("/readyz")
|
|
99
|
+
async def readiness_probe(request: Request):
|
|
100
|
+
"""Kubernetes readiness probe. Checks backing services."""
|
|
101
|
+
from fastapi.responses import JSONResponse
|
|
102
|
+
if getattr(request.app.state, "startup_failed", False):
|
|
103
|
+
return JSONResponse(status_code=503, content={"status": "not ready"})
|
|
104
|
+
return {"status": "ready"}
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
import uvicorn
|
|
108
|
+
uvicorn.run("<%= packageName %>.main:app", host="0.0.0.0", port=settings.server_port, reload=True)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from httpx import AsyncClient, ASGITransport
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
<% if (rest) { %>
|
|
5
|
+
from <%= packageName %>.main import app
|
|
6
|
+
<% } %>
|
|
7
|
+
|
|
8
|
+
<% if (postgres) { %>
|
|
9
|
+
from testcontainers.postgres import PostgresContainer
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
13
|
+
def setup_postgres():
|
|
14
|
+
with PostgresContainer("postgres:15-alpine") as postgres:
|
|
15
|
+
os.environ["DB_HOST"] = postgres.get_container_host_ip()
|
|
16
|
+
os.environ["DB_PORT"] = postgres.get_exposed_port(5432)
|
|
17
|
+
os.environ["DB_USER"] = postgres.username
|
|
18
|
+
os.environ["DB_PASSWORD"] = postgres.password
|
|
19
|
+
os.environ["DB_NAME"] = postgres.dbname
|
|
20
|
+
yield
|
|
21
|
+
<% } %>
|
|
22
|
+
|
|
23
|
+
<% if (messaging === 'redis' || websockets) { %>
|
|
24
|
+
from testcontainers.redis import RedisContainer
|
|
25
|
+
|
|
26
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
27
|
+
def setup_redis():
|
|
28
|
+
with RedisContainer("redis:7-alpine") as redis:
|
|
29
|
+
os.environ["REDIS_URL"] = f"redis://{redis.get_container_host_ip()}:{redis.get_exposed_port(6379)}"
|
|
30
|
+
yield
|
|
31
|
+
<% } %>
|
|
32
|
+
|
|
33
|
+
<% if (rest) { %>
|
|
34
|
+
@pytest_asyncio.fixture
|
|
35
|
+
async def async_client():
|
|
36
|
+
async with AsyncClient(
|
|
37
|
+
transport=ASGITransport(app=app), base_url="http://test"
|
|
38
|
+
) as client:
|
|
39
|
+
yield client
|
|
40
|
+
|
|
41
|
+
@pytest.mark.asyncio
|
|
42
|
+
async def test_liveness_probe(async_client: AsyncClient):
|
|
43
|
+
response = await async_client.get("/healthz")
|
|
44
|
+
assert response.status_code == 200
|
|
45
|
+
assert response.json() == {"status": "alive"}
|
|
46
|
+
|
|
47
|
+
@pytest.mark.asyncio
|
|
48
|
+
async def test_readiness_probe(async_client: AsyncClient):
|
|
49
|
+
response = await async_client.get("/readyz")
|
|
50
|
+
assert response.status_code == 200
|
|
51
|
+
assert response.json() == {"status": "ready"}
|
|
52
|
+
|
|
53
|
+
@pytest.mark.asyncio
|
|
54
|
+
async def test_health_alias_reports_alive(async_client: AsyncClient):
|
|
55
|
+
# /health and /healthz are both wired to the liveness probe.
|
|
56
|
+
response = await async_client.get("/health")
|
|
57
|
+
assert response.status_code == 200
|
|
58
|
+
assert response.json() == {"status": "alive"}
|
|
59
|
+
|
|
60
|
+
@pytest.mark.asyncio
|
|
61
|
+
async def test_readyz_503_when_dependency_down(async_client: AsyncClient):
|
|
62
|
+
# Drives the real readiness 503 branch (main.py readiness_probe): when a
|
|
63
|
+
# backing dependency failed to start, /readyz must report not-ready.
|
|
64
|
+
# Phase 1-4 is expected to replace this flag with a live DB ping; tighten
|
|
65
|
+
# this test to exercise that ping once it lands.
|
|
66
|
+
app.state.startup_failed = True
|
|
67
|
+
try:
|
|
68
|
+
response = await async_client.get("/readyz")
|
|
69
|
+
assert response.status_code == 503
|
|
70
|
+
assert response.json() == {"status": "not ready"}
|
|
71
|
+
finally:
|
|
72
|
+
app.state.startup_failed = False
|
|
73
|
+
|
|
74
|
+
<% } %>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<% if (rest) { %>
|
|
2
|
+
import pytest
|
|
3
|
+
from httpx import AsyncClient, ASGITransport
|
|
4
|
+
import pytest_asyncio
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
|
|
7
|
+
from <%= packageName %>.entrypoints.api.middleware import (
|
|
8
|
+
IdempotencyMiddleware,
|
|
9
|
+
ConcurrencyLimitingMiddleware,
|
|
10
|
+
)
|
|
11
|
+
from <%= packageName %>.core.domain.entities import IdempotencyResponse
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InMemoryIdempotencyRepo:
|
|
15
|
+
"""Real (non-mock) IdempotencyRepository implementation for tests."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.completed = {} # (key, user_id) -> IdempotencyResponse
|
|
19
|
+
self.in_progress = set()
|
|
20
|
+
|
|
21
|
+
async def get(self, key, user_id):
|
|
22
|
+
k = (key, user_id)
|
|
23
|
+
if k in self.completed:
|
|
24
|
+
return ("COMPLETED", self.completed[k])
|
|
25
|
+
if k in self.in_progress:
|
|
26
|
+
return ("IN_PROGRESS", None)
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
async def lock(self, key, user_id):
|
|
30
|
+
k = (key, user_id)
|
|
31
|
+
if k in self.in_progress or k in self.completed:
|
|
32
|
+
return False
|
|
33
|
+
self.in_progress.add(k)
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
async def save(self, key, user_id, response: IdempotencyResponse):
|
|
37
|
+
k = (key, user_id)
|
|
38
|
+
self.in_progress.discard(k)
|
|
39
|
+
self.completed[k] = response
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_app_with_idempotency():
|
|
43
|
+
app = FastAPI()
|
|
44
|
+
app.add_middleware(IdempotencyMiddleware)
|
|
45
|
+
app.state.idempotency_repo = InMemoryIdempotencyRepo()
|
|
46
|
+
app.state.calls = 0
|
|
47
|
+
|
|
48
|
+
@app.post("/thing")
|
|
49
|
+
async def create_thing():
|
|
50
|
+
app.state.calls += 1
|
|
51
|
+
return {"created": True}
|
|
52
|
+
|
|
53
|
+
return app
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest_asyncio.fixture
|
|
57
|
+
async def idem_client():
|
|
58
|
+
app = build_app_with_idempotency()
|
|
59
|
+
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
|
|
60
|
+
yield client, app
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_idempotency_replays_cached_response(idem_client):
|
|
65
|
+
client, app = idem_client
|
|
66
|
+
headers = {"Idempotency-Key": "abc-123"}
|
|
67
|
+
|
|
68
|
+
r1 = await client.post("/thing", headers=headers)
|
|
69
|
+
assert r1.status_code == 200
|
|
70
|
+
|
|
71
|
+
r2 = await client.post("/thing", headers=headers)
|
|
72
|
+
# Second call replays the cached response; the handler runs exactly once.
|
|
73
|
+
assert r2.status_code == r1.status_code
|
|
74
|
+
assert app.state.calls == 1
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.asyncio
|
|
78
|
+
async def test_idempotency_get_is_not_deduplicated(idem_client):
|
|
79
|
+
client, app = idem_client
|
|
80
|
+
# GET bypasses the middleware entirely (no /thing GET route -> 405, never cached).
|
|
81
|
+
r = await client.get("/thing", headers={"Idempotency-Key": "abc-123"})
|
|
82
|
+
assert r.status_code in (404, 405)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.mark.asyncio
|
|
86
|
+
async def test_concurrency_limit_sheds_with_503():
|
|
87
|
+
import asyncio
|
|
88
|
+
|
|
89
|
+
app = FastAPI()
|
|
90
|
+
app.add_middleware(ConcurrencyLimitingMiddleware, max_concurrent_requests=1)
|
|
91
|
+
release = asyncio.Event()
|
|
92
|
+
|
|
93
|
+
@app.get("/slow")
|
|
94
|
+
async def slow():
|
|
95
|
+
await release.wait()
|
|
96
|
+
return {"ok": True}
|
|
97
|
+
|
|
98
|
+
transport = ASGITransport(app=app)
|
|
99
|
+
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
100
|
+
# Hold the single slot, then fire a second request which must be shed (503).
|
|
101
|
+
first = asyncio.create_task(client.get("/slow"))
|
|
102
|
+
await asyncio.sleep(0.05)
|
|
103
|
+
second = await client.get("/slow")
|
|
104
|
+
assert second.status_code == 503
|
|
105
|
+
assert second.json()["detail"].startswith("Service overloaded")
|
|
106
|
+
release.set()
|
|
107
|
+
r1 = await first
|
|
108
|
+
assert r1.status_code == 200
|
|
109
|
+
<% } %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<% if (runpod) { %>
|
|
2
|
+
from <%= packageName %>.entrypoints.worker.worker import handler
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_worker_rejects_missing_prompt():
|
|
6
|
+
# Boundary validation: a job without 'prompt' must return a structured error,
|
|
7
|
+
# never raise an unhandled exception.
|
|
8
|
+
result = handler({"input": {}})
|
|
9
|
+
assert "error" in result
|
|
10
|
+
assert "prompt" in result["error"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_worker_echoes_valid_prompt():
|
|
14
|
+
result = handler({"input": {"prompt": "hello"}})
|
|
15
|
+
assert result == {"status": "success", "echo": "hello"}
|
|
16
|
+
<% } %>
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatFiles,
|
|
3
|
+
generateFiles,
|
|
4
|
+
Tree,
|
|
5
|
+
names,
|
|
6
|
+
} from '@nx/devkit';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { recordGeneratorProvenance } from '../shared/provenance';
|
|
9
|
+
import {
|
|
10
|
+
promoteEngineerSkill,
|
|
11
|
+
deployStackDocs,
|
|
12
|
+
ensureOptionalInfra,
|
|
13
|
+
readProjectPrefix,
|
|
14
|
+
registerRunner,
|
|
15
|
+
} from '../shared/scaffold-helpers';
|
|
16
|
+
import { applyCapability } from '../shared/capabilities';
|
|
17
|
+
|
|
18
|
+
export interface PythonMicroserviceGeneratorSchema {
|
|
19
|
+
name: string;
|
|
20
|
+
rest: boolean;
|
|
21
|
+
postgres: boolean;
|
|
22
|
+
messaging: 'none' | 'redis' | 'kafka' | 'gcp-pubsub';
|
|
23
|
+
websockets: boolean;
|
|
24
|
+
runpod: boolean;
|
|
25
|
+
llm: boolean;
|
|
26
|
+
llmProvider: 'openai' | 'anthropic' | 'local' | 'none';
|
|
27
|
+
native?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default async function (tree: Tree, options: PythonMicroserviceGeneratorSchema) {
|
|
31
|
+
const serviceNames = names(options.name);
|
|
32
|
+
const projectRoot = `services/${serviceNames.fileName}`;
|
|
33
|
+
// src-layout: the importable package is `src/<packageName>` (PEP src-layout),
|
|
34
|
+
// so the package name is the service name in snake_case (a valid Python
|
|
35
|
+
// identifier). It names the `src/__packageName__/` template dir, every
|
|
36
|
+
// `from <pkg>.…` import, the hatch wheel package, and the import-linter root.
|
|
37
|
+
// Derive it from the kebab fileName (hyphens → underscores), NOT constantName:
|
|
38
|
+
// nx's constantName collapses single-char segments (`a-b1` → `AB1`), which would
|
|
39
|
+
// make the package name diverge from the intuitive `a_b1` a developer expects.
|
|
40
|
+
const packageName = serviceNames.fileName.replace(/-/g, '_');
|
|
41
|
+
|
|
42
|
+
let assignedPort = 8000;
|
|
43
|
+
let composeDoc: any = null;
|
|
44
|
+
if (tree.exists('docker-compose.yml')) {
|
|
45
|
+
try {
|
|
46
|
+
const yaml = require('yaml');
|
|
47
|
+
const composeContent = tree.read('docker-compose.yml', 'utf-8');
|
|
48
|
+
composeDoc = yaml.parseDocument(composeContent);
|
|
49
|
+
|
|
50
|
+
const usedPorts = new Set<number>();
|
|
51
|
+
|
|
52
|
+
// Collect ports from docker-compose.yml
|
|
53
|
+
const servicesMap = composeDoc.get('services');
|
|
54
|
+
if (servicesMap && servicesMap.items) {
|
|
55
|
+
for (const item of servicesMap.items) {
|
|
56
|
+
const service = item.value;
|
|
57
|
+
if (service && service.get) {
|
|
58
|
+
const ports = service.get('ports');
|
|
59
|
+
if (ports && ports.items) {
|
|
60
|
+
for (const pItem of ports.items) {
|
|
61
|
+
const portStr = String(pItem.value || pItem);
|
|
62
|
+
const match = portStr.match(/^(\d+):/);
|
|
63
|
+
if (match) {
|
|
64
|
+
usedPorts.add(parseInt(match[1], 10));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Also collect ports from .env files of natively-run services (not in docker-compose)
|
|
73
|
+
if (tree.exists('services')) {
|
|
74
|
+
for (const svcName of tree.children('services') ?? []) {
|
|
75
|
+
const envPath = `services/${svcName}/.env`;
|
|
76
|
+
if (tree.exists(envPath)) {
|
|
77
|
+
const envContent = tree.read(envPath, 'utf-8') ?? '';
|
|
78
|
+
for (const line of envContent.split('\n')) {
|
|
79
|
+
const m = line.match(/^(?:PORT|SERVER_PORT)=(\d+)/);
|
|
80
|
+
if (m) usedPorts.add(parseInt(m[1], 10));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
while (usedPorts.has(assignedPort)) {
|
|
87
|
+
assignedPort++;
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.warn('Failed to parse docker-compose.yml for port calculation:', e);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Default the provider so programmatic callers (e.g. tests) that omit it get
|
|
95
|
+
// today's OpenAI behaviour unchanged. Only consumed inside `--llm` templates.
|
|
96
|
+
const llmProvider = options.llmProvider ?? 'openai';
|
|
97
|
+
|
|
98
|
+
const templateOptions = {
|
|
99
|
+
...options,
|
|
100
|
+
...serviceNames,
|
|
101
|
+
packageName,
|
|
102
|
+
assignedPort,
|
|
103
|
+
llmProvider,
|
|
104
|
+
tmpl: '' // required by generateFiles
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
generateFiles(
|
|
108
|
+
tree,
|
|
109
|
+
path.join(__dirname, '..', '..', '..', '..', 'src', 'generators', 'python-microservice', 'files'),
|
|
110
|
+
projectRoot,
|
|
111
|
+
templateOptions
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
deployStackDocs(tree, path.join(__dirname, '..', '..', '..', '..', 'src', 'generators', 'python-microservice', 'docs'));
|
|
115
|
+
|
|
116
|
+
// Auto-inject into docker-compose.yml if it exists
|
|
117
|
+
if (composeDoc) {
|
|
118
|
+
try {
|
|
119
|
+
if (!composeDoc.get('services')) {
|
|
120
|
+
// createNode so the result is a YAMLMap with .has/.set (a plain {} is not).
|
|
121
|
+
composeDoc.set('services', composeDoc.createNode({}));
|
|
122
|
+
}
|
|
123
|
+
const servicesMap = composeDoc.get('services');
|
|
124
|
+
// Only wire the infrastructure this service actually uses. Redis is needed
|
|
125
|
+
// for the WebSocket backplane or Redis pub/sub; the Pub/Sub emulator only
|
|
126
|
+
// for GCP Pub/Sub messaging. A plain --rest --postgres --llm service
|
|
127
|
+
// provisions neither.
|
|
128
|
+
const usesRedis = options.websockets || options.messaging === 'redis';
|
|
129
|
+
const usesPubSub = options.messaging === 'gcp-pubsub';
|
|
130
|
+
if (!options.native && !servicesMap.has(serviceNames.fileName)) {
|
|
131
|
+
const environment: string[] = [`SERVER_PORT=${assignedPort}`];
|
|
132
|
+
if (options.postgres) {
|
|
133
|
+
environment.push(
|
|
134
|
+
'DB_HOST=db',
|
|
135
|
+
'DB_PORT=5432',
|
|
136
|
+
'DB_USER=${DB_USER:-postgres}',
|
|
137
|
+
'DB_PASSWORD=${DB_PASSWORD:-postgres}',
|
|
138
|
+
`DB_NAME=\${DB_NAME:-${serviceNames.fileName}}`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (usesRedis) {
|
|
142
|
+
environment.push('REDIS_URL=redis:6379');
|
|
143
|
+
}
|
|
144
|
+
if (usesPubSub) {
|
|
145
|
+
environment.push('PUBSUB_EMULATOR_HOST=pubsub:${PUBSUB_PORT:-8085}');
|
|
146
|
+
}
|
|
147
|
+
environment.push(
|
|
148
|
+
'OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317',
|
|
149
|
+
'OTEL_EXPORTER_OTLP_PROTOCOL=grpc'
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const dependsOn: Record<string, { condition: string }> = {};
|
|
153
|
+
if (options.postgres) {
|
|
154
|
+
dependsOn.db = { condition: 'service_healthy' };
|
|
155
|
+
}
|
|
156
|
+
if (usesRedis) {
|
|
157
|
+
dependsOn.redis = { condition: 'service_healthy' };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const newService: Record<string, unknown> = {
|
|
161
|
+
build: {
|
|
162
|
+
context: `./${projectRoot}`,
|
|
163
|
+
dockerfile: 'Dockerfile'
|
|
164
|
+
},
|
|
165
|
+
container_name: serviceNames.fileName,
|
|
166
|
+
restart: 'unless-stopped',
|
|
167
|
+
ports: [
|
|
168
|
+
`${assignedPort}:${assignedPort}`
|
|
169
|
+
],
|
|
170
|
+
environment
|
|
171
|
+
};
|
|
172
|
+
// Only the REST entrypoint serves /healthz; the runpod worker image
|
|
173
|
+
// runs a background worker with no HTTP server, so it gets no probe.
|
|
174
|
+
if (options.rest && !options.runpod) {
|
|
175
|
+
// The python:slim runtime image has no curl, so probe the health
|
|
176
|
+
// endpoint with the interpreter that is guaranteed to be present.
|
|
177
|
+
newService.healthcheck = {
|
|
178
|
+
test: [
|
|
179
|
+
'CMD-SHELL',
|
|
180
|
+
`python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:${assignedPort}/healthz').status==200 else 1)"`
|
|
181
|
+
],
|
|
182
|
+
interval: '10s',
|
|
183
|
+
timeout: '5s',
|
|
184
|
+
retries: 5,
|
|
185
|
+
start_period: '20s'
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (Object.keys(dependsOn).length > 0) {
|
|
189
|
+
newService.depends_on = dependsOn;
|
|
190
|
+
}
|
|
191
|
+
newService.networks = ['groundwork-net'];
|
|
192
|
+
|
|
193
|
+
servicesMap.set(serviceNames.fileName, newService);
|
|
194
|
+
// db only when this service uses Postgres; jaeger always for a
|
|
195
|
+
// containerized service (it exports OTLP). Neither is in the base compose.
|
|
196
|
+
ensureOptionalInfra(composeDoc, servicesMap, {
|
|
197
|
+
usesRedis,
|
|
198
|
+
usesPubSub,
|
|
199
|
+
usesDb: !!options.postgres,
|
|
200
|
+
usesTelemetry: true,
|
|
201
|
+
projectPrefix: readProjectPrefix(tree),
|
|
202
|
+
});
|
|
203
|
+
tree.write('docker-compose.yml', composeDoc.toString());
|
|
204
|
+
} else if (options.native && (options.postgres || usesRedis || usesPubSub)) {
|
|
205
|
+
// Native sidecar: no app container in compose, but provision the backing
|
|
206
|
+
// infra it connects to. A host process gets no forced tracing backend —
|
|
207
|
+
// telemetry is wired only if the project explicitly runs jaeger.
|
|
208
|
+
ensureOptionalInfra(composeDoc, servicesMap, {
|
|
209
|
+
usesRedis,
|
|
210
|
+
usesPubSub,
|
|
211
|
+
usesDb: !!options.postgres,
|
|
212
|
+
usesTelemetry: false,
|
|
213
|
+
projectPrefix: readProjectPrefix(tree),
|
|
214
|
+
});
|
|
215
|
+
tree.write('docker-compose.yml', composeDoc.toString());
|
|
216
|
+
}
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.warn('Failed to update docker-compose.yml:', e);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Native mode: the service runs as a host process (e.g. it needs Metal/MPS that
|
|
223
|
+
// Docker on macOS can't provide), so register it as a sidecar runner instead of
|
|
224
|
+
// a compose service. `./dev start` launches it via `uv run`.
|
|
225
|
+
if (options.native) {
|
|
226
|
+
registerRunner(tree, {
|
|
227
|
+
name: serviceNames.fileName,
|
|
228
|
+
kind: 'sidecar',
|
|
229
|
+
cmd: `uv run python -m ${packageName}.main`,
|
|
230
|
+
cwd: projectRoot,
|
|
231
|
+
autostart: true,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const pkgRoot = `${projectRoot}/src/${packageName}`;
|
|
236
|
+
|
|
237
|
+
if (!options.rest) {
|
|
238
|
+
tree.delete(`${pkgRoot}/entrypoints/api`);
|
|
239
|
+
// Middleware tests import the API entrypoint, which only exists with --rest.
|
|
240
|
+
tree.delete(`${projectRoot}/tests/test_middleware.py`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!options.postgres) {
|
|
244
|
+
tree.delete(`${pkgRoot}/adapters/database.py`);
|
|
245
|
+
tree.delete(`${pkgRoot}/adapters/repository.py`);
|
|
246
|
+
// Schema lives in db/ (parity with Go); migrate runs scripts/apply-schema.sh.
|
|
247
|
+
tree.delete(`${projectRoot}/db`);
|
|
248
|
+
tree.delete(`${projectRoot}/scripts/apply-schema.sh`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!options.websockets) {
|
|
252
|
+
tree.delete(`${pkgRoot}/entrypoints/api/websocket_handler.py`);
|
|
253
|
+
tree.delete(`${pkgRoot}/adapters/websocket_hub.py`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (options.messaging === 'none') {
|
|
257
|
+
tree.delete(`${pkgRoot}/adapters/message_queue.py`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!options.runpod) {
|
|
261
|
+
tree.delete(`${pkgRoot}/entrypoints/worker`);
|
|
262
|
+
tree.delete(`${pkgRoot}/adapters/comfyui.py`);
|
|
263
|
+
// Worker test imports the runpod worker handler, deleted above.
|
|
264
|
+
tree.delete(`${projectRoot}/tests/test_worker.py`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// LLM is a composable capability port (plan WS-F). The chosen provider —
|
|
268
|
+
// anthropic | openai | local | none — selects the adapter; the port, the
|
|
269
|
+
// contract test, the provider dependency and its env footprint all come from
|
|
270
|
+
// the capability registry via one shared injector (no inline per-provider
|
|
271
|
+
// template here). `none` ships the bare port: the interface + a stub + a red bet.
|
|
272
|
+
if (options.llm) {
|
|
273
|
+
applyCapability(tree, {
|
|
274
|
+
capability: 'llm',
|
|
275
|
+
provider: llmProvider,
|
|
276
|
+
stack: 'python',
|
|
277
|
+
serviceRoot: projectRoot,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
promoteEngineerSkill(tree, 'groundwork-python-engineer');
|
|
282
|
+
|
|
283
|
+
await formatFiles(tree);
|
|
284
|
+
|
|
285
|
+
recordGeneratorProvenance(tree, 'python-microservice', options as unknown as Record<string, unknown>);
|
|
286
|
+
}
|