workspai 0.43.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.
Files changed (255) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +624 -0
  3. package/contracts/agent-customization-pack.v1.json +229 -0
  4. package/contracts/analyze-last-run.v1.json +106 -0
  5. package/contracts/artifact-remediation-plan.v1.json +163 -0
  6. package/contracts/backend-import-stack-parity.snapshot.json +37 -0
  7. package/contracts/cli-log-event.v1.json +51 -0
  8. package/contracts/create-planner-capabilities.v1.json +251 -0
  9. package/contracts/doctor-project-evidence.v1.json +304 -0
  10. package/contracts/doctor-remediation-plan.v1.json +496 -0
  11. package/contracts/doctor-workspace-evidence.v1.json +315 -0
  12. package/contracts/extension-cli-compatibility.v1.json +26 -0
  13. package/contracts/infra-stack.v1.json +305 -0
  14. package/contracts/module-layout.v1.json +34 -0
  15. package/contracts/module-support.v1.json +20 -0
  16. package/contracts/pipeline-last-run.v1.json +80 -0
  17. package/contracts/release-readiness.v1.json +60 -0
  18. package/contracts/runtime-command-surface.v1.json +364 -0
  19. package/contracts/workspace-intelligence/agent-action-outcome.v1.json +38 -0
  20. package/contracts/workspace-intelligence/blocker-resolution.v1.json +65 -0
  21. package/contracts/workspace-intelligence/doctor-fix-result.v1.json +44 -0
  22. package/contracts/workspace-intelligence/fact-freshness.v1.json +58 -0
  23. package/contracts/workspace-intelligence/studio-blocker-handoff.v1.json +115 -0
  24. package/contracts/workspace-intelligence/workspace-context.v1.json +142 -0
  25. package/contracts/workspace-intelligence/workspace-contract-verify.v1.json +51 -0
  26. package/contracts/workspace-intelligence/workspace-dependency-graph.v1.json +152 -0
  27. package/contracts/workspace-intelligence/workspace-explain.v1.json +31 -0
  28. package/contracts/workspace-intelligence/workspace-impact.v1.json +114 -0
  29. package/contracts/workspace-intelligence/workspace-intelligence-history.v1.json +40 -0
  30. package/contracts/workspace-intelligence/workspace-model-diff.v1.json +105 -0
  31. package/contracts/workspace-intelligence/workspace-model-snapshot.v1.json +36 -0
  32. package/contracts/workspace-intelligence/workspace-model.v1.json +135 -0
  33. package/contracts/workspace-intelligence/workspace-operational-skill.v1.json +37 -0
  34. package/contracts/workspace-intelligence/workspace-skills-index.v1.json +27 -0
  35. package/contracts/workspace-intelligence/workspace-verify.v1.json +213 -0
  36. package/contracts/workspace-registry.v1.json +83 -0
  37. package/contracts/workspace-run-last.v1.json +112 -0
  38. package/data/modules-embeddings.json +16953 -0
  39. package/dist/analyze-5BU4QIPK.js +1 -0
  40. package/dist/artifact-remediation-plan-5GJWPPRS.js +3 -0
  41. package/dist/autopilot-release-QYUOVB26.js +1 -0
  42. package/dist/chunk-22DT744Z.js +1 -0
  43. package/dist/chunk-2E6WGUN6.js +1 -0
  44. package/dist/chunk-2FIX2MDC.js +1 -0
  45. package/dist/chunk-3KXW53QI.js +1 -0
  46. package/dist/chunk-3ZK2GU7C.js +1 -0
  47. package/dist/chunk-4KUIFXHM.js +5 -0
  48. package/dist/chunk-54XJNLYC.js +13 -0
  49. package/dist/chunk-5SWNSJGW.js +1 -0
  50. package/dist/chunk-5XATWNME.js +1 -0
  51. package/dist/chunk-753LOKFZ.js +2 -0
  52. package/dist/chunk-7YHK5NM3.js +2 -0
  53. package/dist/chunk-A5FBGRJA.js +1 -0
  54. package/dist/chunk-AAKUNWPR.js +78 -0
  55. package/dist/chunk-AFSZHLQT.js +1 -0
  56. package/dist/chunk-AQ4XZZC6.js +1 -0
  57. package/dist/chunk-CVTCPPEF.js +1 -0
  58. package/dist/chunk-DNAY5ALC.js +1 -0
  59. package/dist/chunk-EWYYLJQY.js +2 -0
  60. package/dist/chunk-GSWPGELT.js +2 -0
  61. package/dist/chunk-GZTYAEWX.js +1 -0
  62. package/dist/chunk-HTYMXMQM.js +1 -0
  63. package/dist/chunk-IBJEQHJR.js +205 -0
  64. package/dist/chunk-ICGWHIMK.js +1 -0
  65. package/dist/chunk-JEI6BTZI.js +3 -0
  66. package/dist/chunk-JHC6SCJC.js +1 -0
  67. package/dist/chunk-LO4C4VXR.js +4 -0
  68. package/dist/chunk-LPZFK3Y6.js +920 -0
  69. package/dist/chunk-LUP4U6EE.js +1 -0
  70. package/dist/chunk-LYC6AIF2.js +2 -0
  71. package/dist/chunk-LZQNVH7D.js +1 -0
  72. package/dist/chunk-NAAQMSO3.js +1 -0
  73. package/dist/chunk-NFUXULIF.js +1 -0
  74. package/dist/chunk-OCGZNSOE.js +1 -0
  75. package/dist/chunk-OJEWQO3Z.js +33 -0
  76. package/dist/chunk-OL64GWQC.js +9 -0
  77. package/dist/chunk-PI5KUXDA.js +7 -0
  78. package/dist/chunk-PYCJWW4B.js +1 -0
  79. package/dist/chunk-RE773WOX.js +13 -0
  80. package/dist/chunk-RO73VVHW.js +2 -0
  81. package/dist/chunk-S5KTATOU.js +1 -0
  82. package/dist/chunk-SI3AQKIP.js +1 -0
  83. package/dist/chunk-T3GJ7YVG.js +1 -0
  84. package/dist/chunk-T6OR6YOI.js +2 -0
  85. package/dist/chunk-VRDF2J46.js +653 -0
  86. package/dist/chunk-VZKSNUEE.js +2 -0
  87. package/dist/chunk-WU4ZRRA5.js +4 -0
  88. package/dist/chunk-XEJD4KCV.js +33 -0
  89. package/dist/chunk-XZGVNGRB.js +1 -0
  90. package/dist/chunk-Y45WZR5N.js +5 -0
  91. package/dist/create-WRZO4HK2.js +1 -0
  92. package/dist/demo-kit-AJ5LIM5G.js +149 -0
  93. package/dist/doctor-MF3M3FS3.js +1 -0
  94. package/dist/dotnet-webapi-clean-IPY3MCGQ.js +425 -0
  95. package/dist/gofiber-standard-N5N3QGH3.js +1397 -0
  96. package/dist/gogin-standard-LAWQTBWL.js +1459 -0
  97. package/dist/imported-projects-registry-FOIE27WT.js +1 -0
  98. package/dist/index.d.ts +141 -0
  99. package/dist/index.js +454 -0
  100. package/dist/managed-agent-markers-AXUM75OE.js +8 -0
  101. package/dist/module-layout-NZ43RSC5.js +1 -0
  102. package/dist/pipeline-QCUHFWDY.js +5 -0
  103. package/dist/platform-capabilities-XAVU4HSM.js +1 -0
  104. package/dist/pythonRapidkitExec-3L3XJBSY.js +1 -0
  105. package/dist/springboot-standard-OZYF7ZM7.js +697 -0
  106. package/dist/workspace-PR6VZRBG.js +1 -0
  107. package/dist/workspace-agent-sync-KTYF3HQR.js +1 -0
  108. package/dist/workspace-archive-GCZNI44O.js +3 -0
  109. package/dist/workspace-context-QUTOP2XV.js +1 -0
  110. package/dist/workspace-contract-XUGMCUSQ.js +1 -0
  111. package/dist/workspace-explain-JEONEGZZ.js +1 -0
  112. package/dist/workspace-explain-contract-DNS2YEL7.js +1 -0
  113. package/dist/workspace-feedback-R6H4UFZB.js +1 -0
  114. package/dist/workspace-foundation-KGSV4E6D.js +1 -0
  115. package/dist/workspace-graph-2A5THUCI.js +1 -0
  116. package/dist/workspace-history-CGCK2DJI.js +1 -0
  117. package/dist/workspace-intelligence-QJGVL37U.js +1 -0
  118. package/dist/workspace-marker-SMBC3Z2Q.js +1 -0
  119. package/dist/workspace-mcp-serve-AK4MYEHQ.js +3 -0
  120. package/dist/workspace-model-PNXXOGPO.js +1 -0
  121. package/dist/workspace-python-engine-state-MTWIIZPY.js +2 -0
  122. package/dist/workspace-registry-summary-RUJE6RPH.js +1 -0
  123. package/dist/workspace-run-Y7YMUIHX.js +1 -0
  124. package/dist/workspace-verify-WSDUUTR5.js +1 -0
  125. package/dist/workspace-watch-AGPOJ74Q.js +1 -0
  126. package/docs/AI_DYNAMIC_INTEGRATION.md +440 -0
  127. package/docs/AI_EXAMPLES.md +419 -0
  128. package/docs/AI_FEATURES.md +460 -0
  129. package/docs/AI_QUICKSTART.md +245 -0
  130. package/docs/DEVELOPMENT.md +90 -0
  131. package/docs/From Code to Shared Understanding.png +0 -0
  132. package/docs/OPEN_SOURCE_USER_SCENARIOS.md +170 -0
  133. package/docs/OPTIMIZATION_GUIDE.md +504 -0
  134. package/docs/PACKAGE_MANAGER_POLICY.md +25 -0
  135. package/docs/README.md +125 -0
  136. package/docs/SECURITY.md +63 -0
  137. package/docs/SETUP.md +107 -0
  138. package/docs/UTILITIES.md +221 -0
  139. package/docs/WORKSPACE_MARKER_SPEC.md +276 -0
  140. package/docs/ci-workflows.md +59 -0
  141. package/docs/commands-reference.md +172 -0
  142. package/docs/config-file-guide.md +299 -0
  143. package/docs/contracts/ARTIFACT_CATALOG.md +333 -0
  144. package/docs/contracts/CLI_LOG_EVENT_STREAM.md +123 -0
  145. package/docs/contracts/COMMAND_OWNERSHIP_MATRIX.md +138 -0
  146. package/docs/contracts/NAMING_AND_COEXISTENCE.md +60 -0
  147. package/docs/contracts/README.md +86 -0
  148. package/docs/contracts/RUNTIME_ACCEPTANCE_MATRIX.md +98 -0
  149. package/docs/contracts/RUNTIME_SUPPORT_MATRIX.md +75 -0
  150. package/docs/contracts/rapidkit-cli-contracts.json +239 -0
  151. package/docs/create-planner-capabilities.md +81 -0
  152. package/docs/doctor-command.md +467 -0
  153. package/docs/examples/ci-agent-grounding.yml +65 -0
  154. package/docs/from-code-to-shared-understanding.md +45 -0
  155. package/docs/governance-policy.enterprise.example.json +40 -0
  156. package/docs/mirror-config.enterprise.example.json +60 -0
  157. package/docs/policies.workspace.example.yml +23 -0
  158. package/docs/workspace-operations.md +160 -0
  159. package/docs/workspace-run.md +104 -0
  160. package/package.json +179 -0
  161. package/rapidkit.config.example.cjs +62 -0
  162. package/scripts/check-agent-customization-drift.mjs +78 -0
  163. package/scripts/check-cli-resolution.cjs +72 -0
  164. package/scripts/enforce-package-manager.cjs +10 -0
  165. package/scripts/enterprise-package-smoke.mjs +463 -0
  166. package/scripts/prepack-enterprise.mjs +43 -0
  167. package/templates/generator.js +179 -0
  168. package/templates/kits/fastapi-ddd/README.md.j2 +117 -0
  169. package/templates/kits/fastapi-ddd/common/env.example.j2 +10 -0
  170. package/templates/kits/fastapi-ddd/env.example.j2 +1 -0
  171. package/templates/kits/fastapi-ddd/pyproject.toml.j2 +64 -0
  172. package/templates/kits/fastapi-ddd/src/__init__.py.j2 +3 -0
  173. package/templates/kits/fastapi-ddd/src/app/__init__.py.j2 +11 -0
  174. package/templates/kits/fastapi-ddd/src/app/application/__init__.py.j2 +5 -0
  175. package/templates/kits/fastapi-ddd/src/app/application/interfaces.py.j2 +43 -0
  176. package/templates/kits/fastapi-ddd/src/app/application/use_cases/__init__.py.j2 +6 -0
  177. package/templates/kits/fastapi-ddd/src/app/application/use_cases/health.py.j2 +14 -0
  178. package/templates/kits/fastapi-ddd/src/app/application/use_cases/notes.py.j2 +24 -0
  179. package/templates/kits/fastapi-ddd/src/app/config/__init__.py.j2 +16 -0
  180. package/templates/kits/fastapi-ddd/src/app/domain/__init__.py.j2 +3 -0
  181. package/templates/kits/fastapi-ddd/src/app/domain/models/__init__.py.j2 +6 -0
  182. package/templates/kits/fastapi-ddd/src/app/domain/models/health.py.j2 +16 -0
  183. package/templates/kits/fastapi-ddd/src/app/domain/models/note.py.j2 +27 -0
  184. package/templates/kits/fastapi-ddd/src/app/infrastructure/__init__.py.j2 +5 -0
  185. package/templates/kits/fastapi-ddd/src/app/infrastructure/repositories/__init__.py.j2 +6 -0
  186. package/templates/kits/fastapi-ddd/src/app/infrastructure/repositories/health.py.j2 +17 -0
  187. package/templates/kits/fastapi-ddd/src/app/infrastructure/repositories/notes.py.j2 +28 -0
  188. package/templates/kits/fastapi-ddd/src/app/main.py.j2 +61 -0
  189. package/templates/kits/fastapi-ddd/src/app/presentation/__init__.py.j2 +3 -0
  190. package/templates/kits/fastapi-ddd/src/app/presentation/api/__init__.py.j2 +5 -0
  191. package/templates/kits/fastapi-ddd/src/app/presentation/api/dependencies/__init__.py.j2 +19 -0
  192. package/templates/kits/fastapi-ddd/src/app/presentation/api/router.py.j2 +10 -0
  193. package/templates/kits/fastapi-ddd/src/app/presentation/api/routes/__init__.py.j2 +5 -0
  194. package/templates/kits/fastapi-ddd/src/app/presentation/api/routes/health.py.j2 +27 -0
  195. package/templates/kits/fastapi-ddd/src/app/presentation/api/routes/notes.py.j2 +50 -0
  196. package/templates/kits/fastapi-ddd/src/app/shared/__init__.py.j2 +5 -0
  197. package/templates/kits/fastapi-ddd/src/app/shared/result.py.j2 +28 -0
  198. package/templates/kits/fastapi-ddd/src/cli.py.j2 +170 -0
  199. package/templates/kits/fastapi-ddd/src/main.py.j2 +35 -0
  200. package/templates/kits/fastapi-ddd/src/modules/__init__.py.j2 +3 -0
  201. package/templates/kits/fastapi-ddd/src/routing/__init__.py.j2 +13 -0
  202. package/templates/kits/fastapi-ddd/src/routing/health.py.j2 +7 -0
  203. package/templates/kits/fastapi-ddd/src/routing/notes.py.j2 +7 -0
  204. package/templates/kits/fastapi-ddd/tests/__init__.py.j2 +1 -0
  205. package/templates/kits/fastapi-ddd/tests/test_app_factory.py.j2 +22 -0
  206. package/templates/kits/fastapi-ddd/tests/test_health.py.j2 +17 -0
  207. package/templates/kits/fastapi-ddd/tests/test_notes.py.j2 +27 -0
  208. package/templates/kits/fastapi-standard/README.md.j2 +140 -0
  209. package/templates/kits/fastapi-standard/common/env.example.j2 +10 -0
  210. package/templates/kits/fastapi-standard/env.example.j2 +1 -0
  211. package/templates/kits/fastapi-standard/pyproject.toml.j2 +64 -0
  212. package/templates/kits/fastapi-standard/src/__init__.py.j2 +3 -0
  213. package/templates/kits/fastapi-standard/src/cli.py.j2 +171 -0
  214. package/templates/kits/fastapi-standard/src/main.py.j2 +66 -0
  215. package/templates/kits/fastapi-standard/src/modules/__init__.py.j2 +3 -0
  216. package/templates/kits/fastapi-standard/src/routing/__init__.py.j2 +16 -0
  217. package/templates/kits/fastapi-standard/src/routing/examples.py.j2 +71 -0
  218. package/templates/kits/fastapi-standard/src/routing/health.py.j2 +22 -0
  219. package/templates/kits/fastapi-standard/tests/__init__.py.j2 +1 -0
  220. package/templates/kits/fastapi-standard/tests/test_examples.py.j2 +29 -0
  221. package/templates/kits/fastapi-standard/tests/test_health.py.j2 +17 -0
  222. package/templates/kits/nestjs-standard/Dockerfile.j2 +41 -0
  223. package/templates/kits/nestjs-standard/README.md.j2 +134 -0
  224. package/templates/kits/nestjs-standard/docker-compose.yml.j2 +94 -0
  225. package/templates/kits/nestjs-standard/docs/README.md.j2 +15 -0
  226. package/templates/kits/nestjs-standard/env.example.j2 +18 -0
  227. package/templates/kits/nestjs-standard/eslint.config.cjs.j2 +9 -0
  228. package/templates/kits/nestjs-standard/jest.config.ts.j2 +28 -0
  229. package/templates/kits/nestjs-standard/nest-cli.json.j2 +10 -0
  230. package/templates/kits/nestjs-standard/package.json.j2 +101 -0
  231. package/templates/kits/nestjs-standard/src/app.controller.ts.j2 +14 -0
  232. package/templates/kits/nestjs-standard/src/app.module.ts.j2 +26 -0
  233. package/templates/kits/nestjs-standard/src/app.service.ts.j2 +16 -0
  234. package/templates/kits/nestjs-standard/src/auth/auth.controller.ts.j2 +20 -0
  235. package/templates/kits/nestjs-standard/src/auth/auth.module.ts.j2 +13 -0
  236. package/templates/kits/nestjs-standard/src/auth/auth.service.ts.j2 +6 -0
  237. package/templates/kits/nestjs-standard/src/auth/entities/token.entity.ts.j2 +3 -0
  238. package/templates/kits/nestjs-standard/src/auth/entities/user.entity.ts.j2 +3 -0
  239. package/templates/kits/nestjs-standard/src/auth/entities/webauthn.entity.ts.j2 +3 -0
  240. package/templates/kits/nestjs-standard/src/config/configuration.ts.j2 +85 -0
  241. package/templates/kits/nestjs-standard/src/config/index.ts.j2 +2 -0
  242. package/templates/kits/nestjs-standard/src/config/validation.ts.j2 +21 -0
  243. package/templates/kits/nestjs-standard/src/examples/dto/create-note.dto.ts.j2 +11 -0
  244. package/templates/kits/nestjs-standard/src/examples/examples.controller.ts.j2 +24 -0
  245. package/templates/kits/nestjs-standard/src/examples/examples.module.ts.j2 +10 -0
  246. package/templates/kits/nestjs-standard/src/examples/examples.service.ts.j2 +33 -0
  247. package/templates/kits/nestjs-standard/src/main.ts.j2 +53 -0
  248. package/templates/kits/nestjs-standard/src/modules/index.ts.j2 +25 -0
  249. package/templates/kits/nestjs-standard/test/app.controller.spec.ts.j2 +24 -0
  250. package/templates/kits/nestjs-standard/test/app.e2e-spec.ts.j2 +60 -0
  251. package/templates/kits/nestjs-standard/test/examples.controller.spec.ts.j2 +28 -0
  252. package/templates/kits/nestjs-standard/test/jest-e2e.json.j2 +15 -0
  253. package/templates/kits/nestjs-standard/tsconfig.build.json.j2 +12 -0
  254. package/templates/kits/nestjs-standard/tsconfig.json.j2 +26 -0
  255. package/workspai.config.example.cjs +62 -0
@@ -0,0 +1,61 @@
1
+ """Application factory wiring the FastAPI instance for DDD layers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncIterator, Callable
6
+
7
+ from fastapi import FastAPI
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+
10
+ from src.routing import api_router as root_api_router
11
+
12
+ try:
13
+ # canonical health package
14
+ from src.health import register_health_routes as _core_register_health_routes
15
+ except ImportError: # pragma: no cover - health package not generated yet
16
+
17
+ def _register_health_routes(_: FastAPI) -> None:
18
+ return None
19
+
20
+ else:
21
+
22
+ def _register_health_routes(app: FastAPI) -> None:
23
+ try:
24
+ _core_register_health_routes(app)
25
+ except Exception: # pragma: no cover - defensive best-effort registration
26
+ return None
27
+
28
+
29
+ LifespanHandler = Callable[[FastAPI], AsyncIterator[None]]
30
+
31
+
32
+ def create_app(
33
+ *,
34
+ title: str,
35
+ description: str,
36
+ version: str,
37
+ lifespan: LifespanHandler | None = None,
38
+ ) -> FastAPI:
39
+ """Build the FastAPI application configured with shared middlewares and routers."""
40
+
41
+ app = FastAPI(
42
+ title=title,
43
+ description=description,
44
+ version=version,
45
+ docs_url="/docs",
46
+ redoc_url="/redoc",
47
+ lifespan=lifespan,
48
+ )
49
+
50
+ app.add_middleware(
51
+ CORSMiddleware,
52
+ allow_origins=["*"],
53
+ allow_credentials=True,
54
+ allow_methods=["*"],
55
+ allow_headers=["*"],
56
+ )
57
+
58
+ app.include_router(root_api_router, prefix="/api")
59
+ _register_health_routes(app)
60
+
61
+ return app
@@ -0,0 +1,3 @@
1
+ """Presentation layer packages (HTTP, CLI, messaging, ...)."""
2
+
3
+ __all__ = ["api"]
@@ -0,0 +1,5 @@
1
+ """HTTP-facing presentation layer."""
2
+
3
+ from .router import get_api_router
4
+
5
+ __all__ = ["get_api_router"]
@@ -0,0 +1,19 @@
1
+ """FastAPI dependencies that wire infrastructure to application use-cases."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from functools import lru_cache
6
+
7
+ from src.app.application.interfaces import ServiceContext
8
+ from src.app.infrastructure.repositories import InMemoryNoteRepository, StaticHealthRepository
9
+
10
+
11
+ @lru_cache(maxsize=1)
12
+ def get_service_context() -> ServiceContext:
13
+ """Return a cached service context for request handlers."""
14
+ health_repository = StaticHealthRepository()
15
+ note_repository = InMemoryNoteRepository()
16
+ return ServiceContext(health_provider=health_repository, note_repository=note_repository)
17
+
18
+
19
+ __all__ = ["get_service_context"]
@@ -0,0 +1,10 @@
1
+ """Factory for the root API router."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi import APIRouter
6
+
7
+
8
+ def get_api_router() -> APIRouter:
9
+ """Return an API router ready for versioned sub-routers."""
10
+ return APIRouter()
@@ -0,0 +1,5 @@
1
+ """HTTP routes exposed by the presentation layer."""
2
+
3
+ from . import health, notes
4
+
5
+ __all__ = ["health", "notes"]
@@ -0,0 +1,27 @@
1
+ """Health endpoints bridging the DDD layers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from time import monotonic
6
+
7
+ from fastapi import APIRouter, Depends
8
+
9
+ from src.app.application.interfaces import ServiceContext
10
+ from src.app.application.use_cases import get_service_health
11
+ from src.app.presentation.api.dependencies import get_service_context
12
+
13
+ router = APIRouter(tags=["health"])
14
+
15
+ _START_TIME = monotonic()
16
+
17
+
18
+ @router.get("/", summary="Health check")
19
+ async def heartbeat(
20
+ context: ServiceContext = Depends(get_service_context),
21
+ ) -> dict[str, object]:
22
+ """Return the current service health decal."""
23
+ payload: dict[str, object] = dict(get_service_health(context))
24
+ payload.setdefault("version", "{{ app_version }}")
25
+ payload.setdefault("uptime", monotonic() - _START_TIME)
26
+ payload.setdefault("module", "service")
27
+ return payload
@@ -0,0 +1,50 @@
1
+ """HTTP handlers for the example notes feature."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi import APIRouter, Depends, HTTPException, status
6
+ from pydantic import BaseModel, Field
7
+
8
+ from src.app.application.interfaces import ServiceContext
9
+ from src.app.application.use_cases import create_note, get_note, list_notes
10
+ from src.app.domain.models import NoteDraft
11
+ from src.app.presentation.api.dependencies import get_service_context
12
+
13
+ router = APIRouter(tags=["examples"])
14
+
15
+
16
+ class NotePayload(BaseModel):
17
+ title: str = Field(..., max_length=80)
18
+ body: str = Field(..., max_length=500)
19
+
20
+
21
+ class NoteResponse(NotePayload):
22
+ id: int
23
+
24
+
25
+ @router.post("/notes", response_model=NoteResponse, status_code=status.HTTP_201_CREATED)
26
+ async def create_example_note(
27
+ payload: NotePayload,
28
+ context: ServiceContext = Depends(get_service_context),
29
+ ) -> NoteResponse:
30
+ note = create_note(context, NoteDraft(title=payload.title, body=payload.body))
31
+ return NoteResponse(**note.to_dict())
32
+
33
+
34
+ @router.get("/notes", response_model=list[NoteResponse])
35
+ async def list_example_notes(
36
+ context: ServiceContext = Depends(get_service_context),
37
+ ) -> list[NoteResponse]:
38
+ notes = list_notes(context)
39
+ return [NoteResponse(**note.to_dict()) for note in notes]
40
+
41
+
42
+ @router.get("/notes/{note_id}", response_model=NoteResponse)
43
+ async def get_example_note(
44
+ note_id: int,
45
+ context: ServiceContext = Depends(get_service_context),
46
+ ) -> NoteResponse:
47
+ note = get_note(context, note_id)
48
+ if not note:
49
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Note not found")
50
+ return NoteResponse(**note.to_dict())
@@ -0,0 +1,5 @@
1
+ """Shared utilities across layers."""
2
+
3
+ from .result import Result
4
+
5
+ __all__ = ["Result"]
@@ -0,0 +1,28 @@
1
+ """Basic result type shared across layers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Generic, Optional, TypeVar
7
+
8
+ T = TypeVar("T")
9
+ E = TypeVar("E")
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class Result(Generic[T, E]):
14
+ """Simple functional-style result wrapper."""
15
+
16
+ value: Optional[T] = None
17
+ error: Optional[E] = None
18
+
19
+ @property
20
+ def is_ok(self) -> bool:
21
+ return self.error is None
22
+
23
+ @property
24
+ def is_err(self) -> bool:
25
+ return self.error is not None
26
+
27
+
28
+ __all__ = ["Result"]
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env python3
2
+ """Project CLI wrapper for {{ project_name }} (DDD edition).
3
+
4
+ Prefer a per-project CLI in `.workspai/cli.py` when present. Otherwise
5
+ provide helpful guidance to the developer rather than guessing environment
6
+ details.
7
+ """
8
+
9
+ import importlib.util
10
+ import sys
11
+ import argparse
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+
16
+ def _missing_cli(name: str) -> None:
17
+ print(f"❌ Missing project-local CLI (.workspai/cli.py) - can't run '{name}'")
18
+ print("💡 If you're a new user run: workspai init # create venv + install deps")
19
+ sys.exit(127)
20
+
21
+
22
+ def _load_project_cli_module() -> Optional[object]:
23
+ project_root = Path(__file__).resolve().parents[1]
24
+ cli_file = project_root / ".workspai" / "cli.py"
25
+ if not cli_file.exists():
26
+ legacy_cli_file = project_root / ".rapidkit" / "cli.py"
27
+ if not legacy_cli_file.exists():
28
+ return None
29
+ cli_file = legacy_cli_file
30
+ spec = importlib.util.spec_from_file_location("project_cli", str(cli_file))
31
+ if spec is None or spec.loader is None:
32
+ return None
33
+ module = importlib.util.module_from_spec(spec)
34
+ spec.loader.exec_module(module) # type: ignore
35
+ return module
36
+
37
+
38
+ _mod = _load_project_cli_module()
39
+
40
+
41
+ # Default placeholders -- call `_missing_cli` if the project-local CLI isn't
42
+ # available. These are real functions so linters don't complain.
43
+ def init() -> None:
44
+ _missing_cli("init")
45
+
46
+
47
+ def dev() -> None:
48
+ _missing_cli("dev")
49
+
50
+
51
+ def start() -> None:
52
+ _missing_cli("start")
53
+
54
+
55
+ def build() -> None:
56
+ _missing_cli("build")
57
+
58
+
59
+ def test() -> None:
60
+ _missing_cli("test")
61
+
62
+
63
+ def lint() -> None:
64
+ _missing_cli("lint")
65
+
66
+
67
+ def format() -> None: # type: ignore[misc]
68
+ _missing_cli("format")
69
+
70
+
71
+ def help_cmd() -> None:
72
+ print("Usage: poetry run <command> # (init|dev|start|build|test|lint|format)")
73
+
74
+
75
+ if _mod is not None:
76
+ # prefer project-provided implementations when available
77
+ init = getattr(_mod, "init", init)
78
+ dev = getattr(_mod, "dev", dev)
79
+ start = getattr(_mod, "start", start)
80
+ build = getattr(_mod, "build", build)
81
+ test = getattr(_mod, "test", test)
82
+ lint = getattr(_mod, "lint", lint)
83
+ format = getattr(_mod, "format", format)
84
+ help_cmd = getattr(_mod, "help_cmd", help_cmd)
85
+
86
+
87
+ _dev_impl = dev
88
+ _start_impl = start
89
+
90
+
91
+ def _runtime_kwargs_from_argv(argv: list[str]) -> dict:
92
+ parser = argparse.ArgumentParser(add_help=False)
93
+ parser.add_argument("-p", "--port", type=int, dest="port")
94
+ parser.add_argument("--host", dest="host")
95
+ parser.add_argument("--allow-global-runtime", action="store_true", dest="allow_global_runtime")
96
+ ns, _ = parser.parse_known_args(argv)
97
+
98
+ kwargs = {}
99
+ if ns.port is not None:
100
+ kwargs["port"] = ns.port
101
+ if ns.host is not None:
102
+ kwargs["host"] = ns.host
103
+ if ns.allow_global_runtime:
104
+ kwargs["allow_global_runtime"] = True
105
+ return kwargs
106
+
107
+
108
+ def dev() -> None: # type: ignore[no-redef]
109
+ kwargs = _runtime_kwargs_from_argv(sys.argv[1:])
110
+ try:
111
+ _dev_impl(**kwargs)
112
+ except TypeError:
113
+ _dev_impl()
114
+
115
+
116
+ def start() -> None: # type: ignore[no-redef]
117
+ kwargs = _runtime_kwargs_from_argv(sys.argv[1:])
118
+ try:
119
+ _start_impl(**kwargs)
120
+ except TypeError:
121
+ _start_impl()
122
+
123
+
124
+ def main() -> None:
125
+ if len(sys.argv) < 2:
126
+ help_cmd()
127
+ return
128
+
129
+ command = sys.argv[1]
130
+ commands = {
131
+ "init": init,
132
+ "dev": dev,
133
+ "start": start,
134
+ "build": build,
135
+ "test": test,
136
+ "lint": lint,
137
+ "format": format,
138
+ "help": help_cmd,
139
+ }
140
+
141
+ if command in commands:
142
+ if command in ("dev", "start"):
143
+ parser = argparse.ArgumentParser(prog=f"poetry run {command}", add_help=False)
144
+ parser.add_argument("-p", "--port", type=int, dest="port")
145
+ parser.add_argument("--host", dest="host")
146
+ parser.add_argument("--allow-global-runtime", action="store_true", dest="allow_global_runtime")
147
+ ns, _ = parser.parse_known_args(sys.argv[2:])
148
+
149
+ kwargs = {}
150
+ if ns.port is not None:
151
+ kwargs["port"] = ns.port
152
+ if ns.host is not None:
153
+ kwargs["host"] = ns.host
154
+ if ns.allow_global_runtime:
155
+ kwargs["allow_global_runtime"] = True
156
+
157
+ try:
158
+ commands[command](**kwargs)
159
+ except TypeError:
160
+ commands[command]()
161
+ else:
162
+ commands[command]()
163
+ else:
164
+ print(f"Unknown command: {command}")
165
+ print("Run 'poetry run help' to see available commands.")
166
+ sys.exit(1)
167
+
168
+
169
+ if __name__ == "__main__":
170
+ main()
@@ -0,0 +1,35 @@
1
+ """{{ project_name }} FastAPI application entrypoint (DDD)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncIterator
6
+ from contextlib import asynccontextmanager
7
+
8
+ from fastapi import FastAPI
9
+
10
+ from src.app.main import create_app
11
+
12
+ # <<<inject:imports>>>
13
+
14
+
15
+ @asynccontextmanager
16
+ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
17
+ """Coordinate startup/shutdown hooks contributed by RapidKit Core modules."""
18
+
19
+ _ = app # ensure the app reference stays available for injected hooks
20
+ # <<<inject:startup>>>
21
+ try:
22
+ yield
23
+ finally:
24
+ # <<<inject:shutdown>>>
25
+ pass
26
+
27
+
28
+ app: FastAPI = create_app(
29
+ title="{{ project_name }}",
30
+ description="{{ description }}",
31
+ version="{{ app_version }}",
32
+ lifespan=lifespan,
33
+ )
34
+
35
+ # <<<inject:routes>>>
@@ -0,0 +1,3 @@
1
+ """Module bootstrap namespace."""
2
+
3
+ # <<<inject:module-init>>>
@@ -0,0 +1,13 @@
1
+ """API router assembly bridging DDD presentation routes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from src.app.presentation.api.router import get_api_router
6
+ from .health import router as health_router
7
+ from .notes import router as notes_router
8
+ # <<<inject:router-imports>>>
9
+
10
+ api_router = get_api_router()
11
+ api_router.include_router(health_router, prefix="/health", tags=["health"])
12
+ api_router.include_router(notes_router, prefix="/examples", tags=["examples"])
13
+ # <<<inject:router-mount>>>
@@ -0,0 +1,7 @@
1
+ """Health endpoints re-export for module compatibility."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from src.app.presentation.api.routes.health import router
6
+
7
+ __all__ = ["router"]
@@ -0,0 +1,7 @@
1
+ """Re-export example note routes for compatibility with module injection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from src.app.presentation.api.routes.notes import router
6
+
7
+ __all__ = ["router"]
@@ -0,0 +1 @@
1
+ """Test package placeholder."""
@@ -0,0 +1,22 @@
1
+ """Smoke tests for the generated FastAPI DDD scaffold."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi import FastAPI
6
+ from fastapi.testclient import TestClient
7
+
8
+ from src.app.main import create_app
9
+ from src.main import app
10
+
11
+
12
+ def test_create_app_returns_fastapi_instance() -> None:
13
+ instance = create_app(title="Test", description="Sample", version="1.0.0")
14
+ assert isinstance(instance, FastAPI)
15
+ assert instance.title == "Test"
16
+
17
+
18
+ def test_health_route_returns_ok() -> None:
19
+ client = TestClient(app)
20
+ response = client.get("/api/health/")
21
+ assert response.status_code == 200
22
+ assert response.json()["status"] == "ok"
@@ -0,0 +1,17 @@
1
+ """Health-check regression tests for FastAPI DDD projects."""
2
+
3
+ from fastapi.testclient import TestClient
4
+
5
+ from src.main import app
6
+
7
+ client = TestClient(app)
8
+
9
+
10
+ def test_health_endpoint() -> None:
11
+ response = client.get("/api/health/")
12
+ assert response.status_code == 200
13
+ payload = response.json()
14
+ assert payload["status"] == "ok"
15
+ assert "version" in payload
16
+ assert "uptime" in payload
17
+ assert "module" in payload
@@ -0,0 +1,27 @@
1
+ """Example feature tests exercising the DDD notes handlers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi.testclient import TestClient
6
+
7
+ from src.main import app
8
+
9
+ client = TestClient(app)
10
+
11
+
12
+ def test_create_and_fetch_note() -> None:
13
+ payload = {"title": "architecture note", "body": "Documented via FastAPI DDD kit."}
14
+ create_response = client.post("/api/examples/notes", json=payload)
15
+ assert create_response.status_code == 201
16
+ body = create_response.json()
17
+ note_id = body["id"]
18
+ assert body["title"] == payload["title"]
19
+
20
+ get_response = client.get(f"/api/examples/notes/{note_id}")
21
+ assert get_response.status_code == 200
22
+ assert get_response.json()["id"] == note_id
23
+
24
+
25
+ def test_missing_note_returns_404() -> None:
26
+ response = client.get("/api/examples/notes/999")
27
+ assert response.status_code == 404