@softspark/ai-toolkit 3.0.2 → 3.1.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 (41) hide show
  1. package/AGENTS.md +13 -0
  2. package/CHANGELOG.md +23 -0
  3. package/README.md +30 -23
  4. package/app/.claude-plugin/plugin.json +1 -1
  5. package/app/skills/cpp-rules/SKILL.md +275 -0
  6. package/app/skills/csharp-rules/SKILL.md +282 -0
  7. package/app/skills/dart-rules/SKILL.md +299 -0
  8. package/app/skills/golang-rules/SKILL.md +262 -0
  9. package/app/skills/java-rules/SKILL.md +273 -0
  10. package/app/skills/kotlin-rules/SKILL.md +271 -0
  11. package/app/skills/medplum-rules/SKILL.md +271 -0
  12. package/app/skills/php-rules/SKILL.md +292 -0
  13. package/app/skills/python-rules/SKILL.md +257 -0
  14. package/app/skills/ruby-rules/SKILL.md +286 -0
  15. package/app/skills/rust-rules/SKILL.md +276 -0
  16. package/app/skills/swift-rules/SKILL.md +293 -0
  17. package/app/skills/typescript-rules/SKILL.md +249 -0
  18. package/benchmarks/ecosystem-doctor-snapshot.json +14 -14
  19. package/kb/history/completed/deep-coverage-v3-20260423.md +3 -3
  20. package/kb/history/completed/ecosystem-deep-sweep-20260423.md +1 -1
  21. package/kb/procedures/release-preparation-sop.md +4 -4
  22. package/kb/procedures/release-verification-sop.md +11 -12
  23. package/kb/reference/architecture-overview.md +1 -1
  24. package/kb/reference/global-install-model.md +29 -6
  25. package/kb/reference/language-rules.md +54 -18
  26. package/kb/reference/mcp-editor-compatibility.md +4 -3
  27. package/kb/reference/mcp-templates.md +3 -2
  28. package/kb/reference/supported-tools-registry.md +10 -8
  29. package/llms-full.txt +133 -58
  30. package/manifest.json +3 -3
  31. package/package.json +10 -3
  32. package/scripts/codex_skill_adapter.py +19 -3
  33. package/scripts/ecosystem_tools.json +7 -7
  34. package/scripts/generate_cline_rules.py +17 -8
  35. package/scripts/generate_codex_skills.py +33 -96
  36. package/scripts/generate_language_rules_skills.py +232 -0
  37. package/scripts/generate_roo_rules.py +11 -3
  38. package/scripts/install.py +6 -1
  39. package/scripts/install_steps/ai_tools.py +154 -51
  40. package/scripts/install_steps/install_state.py +14 -2
  41. package/scripts/mcp_editors.py +7 -0
@@ -0,0 +1,271 @@
1
+ ---
2
+ name: kotlin-rules
3
+ description: "Kotlin coding rules from ai-toolkit: coding-style, frameworks, patterns, security, testing. Triggers: .kt, .kts, build.gradle.kts, Ktor, Jetpack Compose, coroutines, kotlinx. Load when writing, reviewing, or editing Kotlin code."
4
+ effort: medium
5
+ user-invocable: false
6
+ allowed-tools: Read
7
+ ---
8
+
9
+ # Kotlin Rules
10
+
11
+ These rules come from `app/rules/kotlin/` in ai-toolkit. They cover
12
+ the project's standards for coding style, frameworks, patterns,
13
+ security, and testing in Kotlin. Apply them when writing or
14
+ reviewing Kotlin code.
15
+
16
+ # Kotlin Coding Style
17
+
18
+ ## Naming
19
+ - PascalCase: classes, interfaces, objects, type aliases, enum entries.
20
+ - camelCase: functions, properties, local variables, parameters.
21
+ - UPPER_SNAKE: compile-time constants (`const val`), top-level `val` constants.
22
+ - Backing properties: prefix with `_` (`private val _items`, `val items: List<T>`).
23
+ - Package names: lowercase, no underscores (`com.company.project.feature`).
24
+
25
+ ## Null Safety
26
+ - Use nullable types only when nullability is semantically meaningful.
27
+ - Prefer `?.let { }`, `?:` (Elvis), and safe calls over `!!`.
28
+ - Never use `!!` except in tests or when null is truly impossible.
29
+ - Use `requireNotNull()` and `require()` for preconditions at public API boundaries.
30
+ - Use `checkNotNull()` and `check()` for state assertions.
31
+
32
+ ## Data Classes
33
+ - Use `data class` for DTOs, value objects, and state containers.
34
+ - Use `copy()` for immutable updates. Avoid mutable `var` in data classes.
35
+ - Use `sealed class` / `sealed interface` for restricted hierarchies.
36
+ - Use `value class` (inline class) for type-safe wrappers with zero overhead.
37
+ - Use `object` for singletons and namespace-like utility groupings.
38
+
39
+ ## Functions
40
+ - Use expression body (`= expr`) for single-expression functions.
41
+ - Use named arguments for functions with >2 parameters of the same type.
42
+ - Use default parameter values instead of overloaded functions.
43
+ - Use extension functions to add behavior without inheritance.
44
+ - Use `suspend` functions for async operations, not callbacks.
45
+
46
+ ## Collections
47
+ - Prefer `listOf`, `mapOf`, `setOf` (immutable) over `mutableListOf`.
48
+ - Use collection operators: `map`, `filter`, `groupBy`, `associate`.
49
+ - Use `sequence {}` for lazy evaluation on large collections.
50
+ - Prefer `firstOrNull()` over `first()` for safe access.
51
+ - Use destructuring: `val (name, age) = user`.
52
+
53
+ ## Scope Functions
54
+ - `let`: null-safe chaining and local scoping.
55
+ - `apply`: configure object after creation.
56
+ - `also`: side effects (logging, validation) in chains.
57
+ - `run`: compute a result using receiver's context.
58
+ - `with`: multiple operations on an object without chaining.
59
+ - Avoid nesting scope functions more than 1 level deep.
60
+
61
+ ## Formatting
62
+ - Use ktlint or detekt for automated formatting and linting.
63
+ - Use trailing commas in multi-line parameter/argument lists.
64
+ - Max line length: 120 characters (Kotlin convention).
65
+ - Use `when` expression over if-else chains for 3+ branches.
66
+
67
+ # Kotlin Frameworks
68
+
69
+ ## Ktor (Server)
70
+ - Use routing DSL: `routing { get("/users") { call.respond(users) } }`.
71
+ - Use `install()` for plugins: ContentNegotiation, Authentication, CORS.
72
+ - Use `call.receive<T>()` for typed request body parsing with kotlinx.serialization.
73
+ - Use `StatusPages` plugin for centralized error handling.
74
+ - Use `Routing` with nested `route("/api/v1") { }` blocks for URL grouping.
75
+
76
+ ## Ktor (Client)
77
+ - Use `HttpClient` with engine configuration (CIO, OkHttp, Apache).
78
+ - Use `install(ContentNegotiation) { json() }` for JSON serialization.
79
+ - Use `client.get<T>()` with reified type for typed responses.
80
+ - Use `HttpTimeout` plugin for connection and request timeouts.
81
+ - Close `HttpClient` when done or use DI lifecycle management.
82
+
83
+ ## Spring Boot (Kotlin)
84
+ - Use constructor injection (Kotlin classes are `final` by default).
85
+ - Apply `kotlin-spring` plugin for open classes (required for proxying).
86
+ - Use `@ConfigurationProperties` with data classes for typed config.
87
+ - Use `WebFlux` with coroutines: `coRouter { }` and `suspend` handler functions.
88
+ - Use `spring-boot-starter-validation` with `@Valid` on Kotlin data classes.
89
+
90
+ ## Exposed (ORM)
91
+ - Use DSL API for type-safe queries: `Users.select { Users.name eq "Ada" }`.
92
+ - Use DAO API for Active Record-style: `User.find { Users.age greaterEq 18 }`.
93
+ - Wrap database operations in `transaction { }` blocks.
94
+ - Use `SchemaUtils.create(Users)` for schema management in development.
95
+
96
+ ## kotlinx.serialization
97
+ - Use `@Serializable` annotation on data classes for compile-time serialization.
98
+ - Use `@SerialName("field_name")` for JSON field name mapping.
99
+ - Use `Json { ignoreUnknownKeys = true }` for lenient deserialization.
100
+ - Use polymorphic serialization with `sealed class` and `@Polymorphic`.
101
+ - Prefer `kotlinx.serialization` over Jackson for pure Kotlin projects.
102
+
103
+ ## Koin (DI)
104
+ - Define modules: `module { single { UserService(get()) } }`.
105
+ - Use `by inject<T>()` for lazy injection in Android/Ktor.
106
+ - Use `factory { }` for new instance per injection, `single { }` for singleton.
107
+ - Use `checkModules()` in tests to verify DI graph completeness.
108
+
109
+ ## Compose (Multiplatform UI)
110
+ - Use `@Composable` functions for UI components. Keep them stateless.
111
+ - Use `remember { }` and `mutableStateOf()` for local state.
112
+ - Hoist state to callers: pass state down, events up.
113
+ - Use `LaunchedEffect` for side effects tied to composition lifecycle.
114
+ - Use `ViewModel` with `StateFlow` for screen-level state management.
115
+
116
+ # Kotlin Patterns
117
+
118
+ ## Error Handling
119
+ - Use `Result<T>` for operations that can fail without exceptions.
120
+ - Use `runCatching { }` to wrap exception-throwing code into `Result`.
121
+ - Use `sealed class` hierarchies for domain errors: `sealed class AppError`.
122
+ - Prefer `fold()`, `getOrElse()`, `getOrNull()` over `getOrThrow()`.
123
+ - Use `require()` / `check()` for preconditions; they throw `IllegalArgumentException` / `IllegalStateException`.
124
+
125
+ ## Coroutines
126
+ - Use `suspend` functions for sequential async operations.
127
+ - Use `coroutineScope { }` for structured concurrency with parallel work.
128
+ - Use `async { }` + `await()` for concurrent independent operations.
129
+ - Use `supervisorScope { }` when child failures should not cancel siblings.
130
+ - Use `withContext(Dispatchers.IO)` for blocking I/O in coroutine context.
131
+ - Use `flow { }` for cold asynchronous streams. Collect in lifecycle-aware scope.
132
+
133
+ ## Flow Patterns
134
+ - Use `stateIn()` and `shareIn()` to convert cold flows to hot shared state.
135
+ - Use `combine()` to merge multiple flows into derived state.
136
+ - Use `flatMapLatest` for search-as-you-type patterns (cancel previous).
137
+ - Use `catch { }` operator for upstream error handling in flows.
138
+ - Use `flowOn(Dispatchers.IO)` to shift upstream execution context.
139
+
140
+ ## Sealed Hierarchies
141
+ - Use `sealed interface` over `sealed class` when no shared state is needed.
142
+ - Use `when` expressions exhaustively on sealed types (compiler-enforced).
143
+ - Combine sealed types with data classes for typed state machines.
144
+ - Use sealed hierarchies for API responses: `Success<T>`, `Error`, `Loading`.
145
+
146
+ ## Delegation
147
+ - Use `by lazy { }` for thread-safe lazy initialization.
148
+ - Use `by map` for delegated properties backed by a `Map`.
149
+ - Use class delegation (`class Foo : Bar by impl`) to favor composition.
150
+ - Use `observable` / `vetoable` delegates for reactive property changes.
151
+
152
+ ## Builder Patterns
153
+ - Use DSL-style builders with `@DslMarker` annotation to prevent scope leakage.
154
+ - Use trailing lambda syntax for configuration blocks.
155
+ - Use `apply { }` for inline object configuration without a dedicated builder.
156
+ - Use `buildList { }`, `buildMap { }`, `buildString { }` for collection construction.
157
+
158
+ ## Anti-Patterns
159
+ - Overusing `!!`: masks null-safety guarantees. Use safe calls or require.
160
+ - Nesting scope functions: `foo.let { it.also { ... }.run { } }` -- flatten logic.
161
+ - Blocking the main thread: use `withContext(Dispatchers.IO)` for I/O.
162
+ - Using `GlobalScope.launch`: leaks coroutines. Use structured concurrency.
163
+ - Mutable shared state without synchronization: use `Mutex` or `StateFlow`.
164
+
165
+ # Kotlin Security
166
+
167
+ ## Input Validation
168
+ - Validate all inputs at API boundaries using Bean Validation or manual checks.
169
+ - Use `require()` for argument validation: `require(age > 0) { "Age must be positive" }`.
170
+ - Use data class `init` blocks for domain validation on construction.
171
+ - Never trust client-provided IDs. Verify resource ownership server-side.
172
+ - Sanitize strings before using in HTML, SQL, or shell commands.
173
+
174
+ ## Null Safety as Security
175
+ - Kotlin's null safety prevents null pointer exceptions. Do not circumvent with `!!`.
176
+ - Use `?.` and `?:` chains for safe fallback values at boundaries.
177
+ - Treat Java interop as untrusted: platform types can still be null.
178
+ - Use `@Nullable` / `@NotNull` annotations on Java code consumed by Kotlin.
179
+
180
+ ## SQL Injection
181
+ - Use Exposed DSL or JPA with parameterized queries. Never concatenate input.
182
+ - Use `PreparedStatement` if writing raw JDBC.
183
+ - Use `CriteriaBuilder` or Exposed conditions for dynamic query construction.
184
+ - Audit `@Query(nativeQuery = true)` for parameter interpolation risks.
185
+
186
+ ## Serialization
187
+ - Use `kotlinx.serialization` with `@Serializable` for compile-time safety.
188
+ - Use `Json { ignoreUnknownKeys = true }` but validate after deserialization.
189
+ - Never use Java `ObjectInputStream` for deserialization (RCE risk).
190
+ - Restrict polymorphic deserialization to known sealed class subtypes.
191
+
192
+ ## Authentication
193
+ - Use Spring Security or Ktor Authentication plugin. Do not roll your own.
194
+ - Hash passwords with BCrypt or Argon2. Never store plaintext.
195
+ - Use short-lived JWTs (15 min) with refresh token rotation.
196
+ - Validate JWT signature, issuer, audience, and expiration on every request.
197
+
198
+ ## Coroutine Security
199
+ - Use `withTimeout()` to prevent unbounded coroutine execution (DoS vector).
200
+ - Use `Mutex` for critical sections. Do not use `synchronized` in suspend functions.
201
+ - Propagate security context through `CoroutineContext` elements.
202
+ - Cancel coroutine scopes on authentication failure or session expiry.
203
+
204
+ ## Secrets Management
205
+ - Use environment variables or Vault for secrets. Never hardcode.
206
+ - Use `@ConfigurationProperties` with injected secrets, not string literals.
207
+ - Never log request headers containing Authorization tokens.
208
+ - Use separate configuration profiles for dev/staging/prod secrets.
209
+
210
+ ## Dependencies
211
+ - Use Dependabot or Renovate for automated dependency updates.
212
+ - Run OWASP Dependency-Check or Gradle `dependencyCheckAnalyze`.
213
+ - Audit transitive dependencies with `gradle dependencies`.
214
+ - Pin dependency versions. Avoid dynamic versions like `1.+`.
215
+
216
+ ## Logging
217
+ - Use parameterized logging: `logger.info("User {} logged in", userId)`.
218
+ - Never log passwords, tokens, or PII.
219
+ - Sanitize user input before logging to prevent log injection.
220
+ - Use structured logging (JSON) for machine-parseable audit trails.
221
+
222
+ # Kotlin Testing
223
+
224
+ ## Framework
225
+ - Use JUnit 5 as the test runner.
226
+ - Use Kotest for Kotlin-idiomatic BDD-style testing (alternative).
227
+ - Use MockK for mocking (Kotlin-native, supports coroutines).
228
+ - Use Testcontainers for integration tests with external services.
229
+
230
+ ## File Naming
231
+ - Test files: `FooTest.kt` in `src/test/kotlin/` mirroring source package.
232
+ - Integration tests: `FooIT.kt` or use `@Tag("integration")`.
233
+ - Use `@Nested` inner classes to group related test cases.
234
+
235
+ ## Structure
236
+ - Use `@DisplayName` for human-readable test names.
237
+ - Use backtick function names for readable test names: `` `returns 404 when user not found` ``.
238
+ - Use `@BeforeEach` for per-test setup. Avoid shared mutable state.
239
+ - Use `@ParameterizedTest` with `@MethodSource` for table-driven tests.
240
+
241
+ ## MockK
242
+ - Use `mockk<UserRepository>()` to create mocks.
243
+ - Use `every { mock.find(any()) } returns user` for stubbing.
244
+ - Use `coEvery { ... }` and `coVerify { ... }` for coroutine mocking.
245
+ - Use `spyk()` for partial mocks on real objects.
246
+ - Use `slot<T>()` and `captured` to inspect arguments.
247
+ - Clear mocks in `@AfterEach` to prevent state leakage.
248
+
249
+ ## Coroutine Testing
250
+ - Use `runTest { }` from `kotlinx-coroutines-test` for coroutine tests.
251
+ - Use `TestDispatcher` to control coroutine execution timing.
252
+ - Use `advanceUntilIdle()` to complete all pending coroutines.
253
+ - Use `turbine` library for testing `Flow` emissions.
254
+
255
+ ## Assertions
256
+ - Use AssertJ or Kotest assertions for fluent, readable checks.
257
+ - Use `shouldBe`, `shouldThrow`, `shouldContain` (Kotest matchers).
258
+ - Use `assertSoftly { }` to collect multiple assertion failures.
259
+ - Use `assertThrows<FooException> { ... }` for exception testing.
260
+
261
+ ## Test Data
262
+ - Use factory functions for test data: `fun aUser(name: String = "Ada") = User(...)`.
263
+ - Use default parameters for minimal test data setup.
264
+ - Use `copy()` on data classes for variations of base test objects.
265
+ - Use `faker` library for realistic test data generation.
266
+
267
+ ## Spring Integration
268
+ - Use `@SpringBootTest` with `@AutoConfigureMockMvc` for API tests.
269
+ - Use `@WebMvcTest` for controller-only tests (faster).
270
+ - Use `@MockkBean` instead of `@MockBean` for MockK integration.
271
+ - Use `@Transactional` on test classes for automatic rollback.
@@ -0,0 +1,271 @@
1
+ ---
2
+ name: medplum-rules
3
+ description: "Medplum (FHIR healthcare) coding rules from ai-toolkit: coding-style, frameworks, patterns, security, testing. Triggers: medplum.config.mts, medplum.config.ts, FHIR, Medplum, Bot, Subscription, Questionnaire. Load when writing, reviewing, or editing Medplum (FHIR healthcare) code."
4
+ effort: medium
5
+ user-invocable: false
6
+ allowed-tools: Read
7
+ ---
8
+
9
+ # Medplum (FHIR healthcare) Rules
10
+
11
+ These rules come from `app/rules/medplum/` in ai-toolkit. They cover
12
+ the project's standards for coding style, frameworks, patterns,
13
+ security, and testing in Medplum (FHIR healthcare). Apply them when writing or
14
+ reviewing Medplum (FHIR healthcare) code.
15
+
16
+ # Medplum / FHIR Coding Style
17
+
18
+ ## Resource Structure
19
+ - Every FHIR object must include `resourceType` as first field.
20
+ - Use PascalCase for resource types (`Patient`, `ServiceRequest`), camelCase for fields (`birthDate`, `valueQuantity`).
21
+ - Never hardcode resource IDs. Let the server assign them on create.
22
+ - Include `meta.profile` when creating resources that must conform to a StructureDefinition.
23
+
24
+ ## References
25
+ - Use `createReference(resource)` from `@medplum/core` to build Reference objects.
26
+ - Always include `display` on references for human readability.
27
+ - Use `getReferenceString(resource)` for comparisons and logging — returns `ResourceType/id`.
28
+ - Use `parseReference(ref)` to extract resourceType and id from a reference string.
29
+ - Never concatenate strings to build references manually.
30
+
31
+ ## CodeableConcepts & Coding
32
+ - Always include `system`, `code`, and `display` in every Coding element.
33
+ - Use standard terminology URIs: `http://loinc.org`, `http://snomed.info/sct`, `http://hl7.org/fhir/sid/icd-10-cm`.
34
+ - Use `getCodeBySystem(cc, system)` to find codes; `setCodeBySystem(cc, system, code)` to set them.
35
+ - Prefer `CodeableConcept` over plain `Coding` when the FHIR spec allows both — it supports multiple codings and free text.
36
+
37
+ ## Identifiers
38
+ - Use `identifier` arrays with `system` + `value` for external IDs (MRN, NPI, SSN).
39
+ - Use `getIdentifier(resource, system)` and `setIdentifier(resource, system, value)` helpers.
40
+ - Identifier systems must be absolute URIs (e.g., `http://hl7.org/fhir/sid/us-npi`).
41
+ - Use `createResourceIfNoneExist(resource, 'identifier=system|value')` for idempotent creates.
42
+
43
+ ## Extensions
44
+ - Use the `extension` array with `url` and typed `value[x]` fields.
45
+ - Prefer official HL7/US Core extensions over custom ones where they exist.
46
+ - Use `getExtension(resource, url)` and `getExtensionValue(resource, url)` helpers.
47
+
48
+ ## Bundles
49
+ - Use `urn:uuid:<uuid>` for internal references between entries in a transaction Bundle.
50
+ - Every Bundle entry must have `request.method` (`POST`, `PUT`, `DELETE`) and `request.url`.
51
+ - Include `fullUrl` on entries that are referenced by other entries.
52
+ - Use conditional references (`Practitioner?identifier=npi|123`) for existing resources.
53
+
54
+ ## HIPAA-Aware Coding
55
+ - Identifiers like SSN, MRN, and insurance IDs are PHI — never log raw values to console or external services.
56
+ - Use a safe logging utility (e.g., `safeLog()`) for any output that might contain patient data. Never `console.log` raw FHIR resources.
57
+ - Reference `display` strings may contain patient names — treat as PHI in logs and error messages.
58
+ - Every new data access path or admin operation must include corresponding `AuditEvent` creation. No exceptions.
59
+ - When audit logging fails (Medplum unreachable), write to a fallback store — audit events must never be silently dropped.
60
+ - See `security.md` rules for full HIPAA, access policy, and PHI handling requirements.
61
+
62
+ ## Formatting
63
+ - Use `formatHumanName()`, `formatAddress()`, `formatDate()`, `formatQuantity()` for display strings.
64
+ - Use `getDisplayString(resource)` as a universal fallback for any resource's display name.
65
+ - Never manually concatenate name parts — FHIR names have `given[]`, `family`, `prefix[]`, `suffix[]`.
66
+
67
+ # Medplum Frameworks
68
+
69
+ ## @medplum/core — SDK Client
70
+ - Use `MedplumClient` for all FHIR operations. Never use raw `fetch` against Medplum endpoints.
71
+ - Use `medplum.createResource()`, `readResource()`, `updateResource()`, `deleteResource()` for CRUD.
72
+ - Use `medplum.searchResources()` for typed arrays. Use `medplum.searchOne()` when expecting a single result.
73
+ - Use `medplum.executeBatch()` for transaction Bundles — groups multiple operations atomically.
74
+ - Use `medplum.upsertResource(resource, query)` for atomic create-or-update.
75
+ - Use `medplum.createResourceIfNoneExist(resource, query)` for idempotent creation.
76
+ - Configure `autoBatchTime` on MedplumClient to auto-batch concurrent GET requests. Use `Promise.all()` instead of sequential `await` to benefit from batching.
77
+
78
+ ## @medplum/fhirtypes — Type Safety
79
+ - Import FHIR types directly: `import { Patient, Observation } from '@medplum/fhirtypes'`.
80
+ - Use TypeScript types for all FHIR resources — never use `any` for resource data.
81
+ - Cast `event.input` in bot handlers: `const patient = event.input as Patient`.
82
+ - Use optional chaining for nested FHIR fields: `patient.name?.[0]?.given?.[0]`.
83
+
84
+ ## @medplum/react — UI Components
85
+ - Wrap app with `<MedplumProvider client={medplum}>` at the root.
86
+ - Use `useMedplum()` hook to access the MedplumClient instance in components.
87
+ - Use `useMedplumContext()` for client + profile + loading state together.
88
+ - Use `<ResourceForm>` for auto-generated CRUD forms, `<ResourceTable>` for display.
89
+ - Use `<SearchControl>` for searchable/filterable resource lists.
90
+ - Use `<QuestionnaireForm>` to render FHIR Questionnaires and capture responses.
91
+ - Use `useSubscription(criteria)` for real-time WebSocket data in React components.
92
+ - Requires Mantine 7+ and PostCSS with Mantine preset. Import `@mantine/core/styles.css`.
93
+
94
+ ## Bot Development
95
+ - Export a single `handler` function: `export async function handler(medplum: MedplumClient, event: BotEvent)`.
96
+ - Access trigger resource via `event.input`. Access secrets via `event.secrets`.
97
+ - Use `event.contentType` to determine input format (`application/fhir+json`, `text/plain`, `x-application/hl7-v2+er7`).
98
+ - Deploy bots via CLI for CI/CD: `medplum bot deploy <bot-name>`.
99
+ - Apply AccessPolicies to bots — restrict to minimum required resource types.
100
+ - Use Subscriptions with `channel.type: 'rest-hook'` and `channel.endpoint: 'Bot/<ID>'` for event-driven execution.
101
+
102
+ ## GraphQL
103
+ - Append `List` to resource type for searches: `PatientList(name: "Eve")`.
104
+ - Use snake_case for search parameters in GraphQL (not kebab-case): `address_city`, not `address-city`.
105
+ - Use inline fragments for reference resolution: `... on Observation { valueQuantity { value } }`.
106
+ - Use `_reference` for reverse lookups: `EncounterList(_reference: patient)`.
107
+
108
+ ## CLI (@medplum/cli)
109
+ - Use `medplum login` for auth, `medplum get`/`medplum post` for FHIR operations.
110
+ - Use `medplum bot deploy` for bot deployment in CI/CD pipelines.
111
+ - Use `medplum bulk export` for bulk data operations.
112
+
113
+ # Medplum / FHIR Patterns
114
+
115
+ ## Bundle Transactions
116
+ - Use `type: 'transaction'` for atomic multi-resource operations — all-or-nothing.
117
+ - Use `type: 'batch'` when operations are independent and partial failure is acceptable.
118
+ - Use `urn:uuid:<uuid>` in `fullUrl` for forward references between entries.
119
+ - Reference other entries via `{ reference: 'urn:uuid:<uuid>' }`.
120
+ - Use `ifNoneExist` on POST entries for conditional creation (idempotent).
121
+ - Use `ifMatch: 'W/"versionId"'` on PUT entries for optimistic concurrency.
122
+ - Use conditional references for existing resources: `Practitioner?identifier=http://hl7.org/fhir/sid/us-npi|123`.
123
+ - For large bundles (>50MB), use `Prefer: respond-async` header.
124
+
125
+ ## Search Patterns
126
+ - Use `_include=ResourceType:searchParam` to fetch referenced resources in one call.
127
+ - Use `_revinclude=ResourceType:searchParam` to fetch resources referencing your results.
128
+ - Use `:iterate` modifier for multi-hop traversal: `_include:iterate=Patient:general-practitioner`.
129
+ - Use `_count` + `_offset` for pagination; use `searchResourcePages()` for async iteration.
130
+ - Use `:contains` modifier for substring search on string params: `name:contains=eve`.
131
+ - Use `:not` modifier to exclude: `status:not=completed`.
132
+ - Use comma-separated values for OR: `status=active,on-hold`.
133
+ - Use multiple parameters for AND: `name=Simpson&birthdate=1940-03-29`.
134
+ - Prefer `searchResources()` over `search()` — returns typed array, not raw Bundle.
135
+
136
+ ## Subscription & Bot Workflows
137
+ - Create a `Subscription` resource with `criteria` (FHIR search query) and `channel.type: 'rest-hook'`.
138
+ - Point `channel.endpoint` to `Bot/<BOT_ID>` for automated processing.
139
+ - Use `subscribeToCriteria()` client-side for WebSocket real-time updates.
140
+ - Never subscribe to `AuditEvent` changes — prevents notification spirals.
141
+ - Use cron-based bots for scheduled tasks (e.g., daily reports, batch processing).
142
+
143
+ ## Access Policies
144
+ - Define `AccessPolicy.resource[]` with `resourceType` and optional `criteria`, `readonly`, `hiddenFields`, `readonlyFields`.
145
+ - Use `%profile` variable to scope data to the current user: `Observation?performer=%profile`.
146
+ - Use `%patient` variable for patient-portal access: `Observation?subject=%patient`.
147
+ - Use compartment-based access for patient-scoped isolation.
148
+ - Use `writeConstraint` with FHIRPath for state machine enforcement (e.g., prevent status rollback).
149
+ - Apply least privilege: start with no access, add specific resource types.
150
+
151
+ ## Conditional Operations
152
+ - Use `createResourceIfNoneExist(resource, query)` for idempotent creates keyed on identifier.
153
+ - Use `upsertResource(resource, query)` for atomic create-or-update in a single request.
154
+ - Use `If-None-Exist` header on POST for server-side conditional creation.
155
+
156
+ ## Patient Deduplication
157
+ - Match on `identifier` systems (MRN, SSN, insurance ID) for deterministic matching.
158
+ - Use probabilistic matching on name + birthdate + address for fuzzy matches.
159
+ - Use Patient `link` field with `type: 'replaced-by'` for merge workflows.
160
+ - Prefer `createResourceIfNoneExist()` at ingestion to prevent duplicates.
161
+
162
+ ## Questionnaire Workflows
163
+ - Create `Questionnaire` resources for form definitions. Use `linkId` for question identification.
164
+ - Use `QuestionnaireResponse` for captured answers. Link to Questionnaire via `questionnaire` field.
165
+ - Use `getQuestionnaireAnswers(response)` to extract answers as a map keyed by `linkId`.
166
+ - Automate post-submission processing with a Bot subscribed to `QuestionnaireResponse` creation.
167
+ - Use SDC (Structured Data Capture) extensions for advanced rendering and extraction.
168
+
169
+ # Medplum / FHIR Security
170
+
171
+ ## Authentication
172
+ - Use client credentials flow (`startClientLogin`) for backend services and integrations.
173
+ - Use authorization code flow (`startLogin` + `processCode`) for user-facing web apps.
174
+ - Never store tokens in `localStorage` in production — use secure HTTP-only cookies or server-side sessions.
175
+ - Use `refreshIfExpired()` before critical operations. Set `gracePeriod` to refresh proactively.
176
+ - Use `setBasicAuth(clientId, clientSecret)` only for server-side code, never in browser.
177
+ - Rotate client secrets via `$rotate-client-secret` operation periodically.
178
+
179
+ ## Access Policies
180
+ - Every non-admin user must have an AccessPolicy. Never leave users with default full access.
181
+ - Scope to specific resource types: list only the types the user needs.
182
+ - Use `readonly: true` or explicit `interaction` arrays to restrict write access.
183
+ - Use `criteria` with FHIR search syntax to filter visible resources (e.g., `Patient?organization=Organization/123`).
184
+ - Use `hiddenFields` to prevent sensitive fields from being returned (e.g., SSN).
185
+ - Use `readonlyFields` to allow viewing but prevent modification of specific fields.
186
+ - Use `writeConstraint` FHIRPath expressions for business rules (e.g., prevent status rollback on finalized resources).
187
+ - Test access policies by logging in as a test user with the policy applied.
188
+
189
+ ## HIPAA & Audit Logging
190
+ - Medplum automatically creates AuditEvent resources for all FHIR operations.
191
+ - Never log PHI (patient names, identifiers, health data) to application console or external services.
192
+ - Use structured audit references: reference the Patient and the accessing Practitioner in audit records.
193
+ - For custom audit trails, create AuditEvent resources with `type`, `agent`, `entity`, and `outcome`.
194
+ - Ensure audit events are never silently dropped — if Medplum is unreachable, write to a fallback store.
195
+
196
+ ## PHI Handling
197
+ - Never include PHI in URLs, query parameters, or HTTP headers.
198
+ - Use POST-based search for queries containing sensitive criteria.
199
+ - Use `Binary` resources with `securityContext` for sensitive file attachments.
200
+ - Encrypt data at rest and in transit (TLS 1.2+). Medplum hosted handles this automatically.
201
+ - Apply data retention policies — use `$expunge` operation for permanent deletion when required.
202
+
203
+ ## SMART Scopes
204
+ - Use `patient/*.read` style scopes for patient-facing apps.
205
+ - Use `user/*.read` style scopes for practitioner-facing apps.
206
+ - Validate scopes server-side on every request — do not trust client-side scope claims.
207
+ - Use `launch/patient` context for apps launched within a patient context.
208
+ - Define minimal scopes: request only the resource types and operations needed.
209
+
210
+ ## Multi-Tenant Isolation
211
+ - Use Medplum Projects for tenant isolation — each project is a separate data silo.
212
+ - Never share AccessPolicies across projects/tenants.
213
+ - Validate `meta.project` on operations when building multi-tenant middleware.
214
+ - Use separate ClientApplications per tenant for backend integrations.
215
+
216
+ ## Secrets Management
217
+ - Use Bot secrets (`event.secrets`) for API keys, connection strings, and credentials.
218
+ - Never hardcode secrets in bot source code or resource data.
219
+ - Use Medplum project-level secrets storage — accessible only by project admins.
220
+ - Rotate secrets on a regular schedule and after any suspected compromise.
221
+
222
+ # Medplum / FHIR Testing
223
+
224
+ ## MockClient
225
+ - Use `MockClient` from `@medplum/mock` for unit tests — it simulates the full MedplumClient API in memory.
226
+ - Pre-populate test data with `mockClient.createResource()` before running test assertions.
227
+ - MockClient supports `search`, `searchResources`, `searchOne`, `readResource`, `updateResource`, `deleteResource`.
228
+ - MockClient does not require network access — tests run fast and offline.
229
+ - Use `new MockClient()` per test to ensure isolation between test cases.
230
+
231
+ ## Bot Unit Testing
232
+ - Test the handler function directly: `await handler(mockClient, mockEvent)`.
233
+ - Create mock `BotEvent` objects with `input`, `contentType`, `secrets`, and `bot` fields.
234
+ - Verify resource creation: call `mockClient.searchResources()` after handler execution.
235
+ - Test error paths: pass invalid input resources and assert the handler throws or returns errors.
236
+ - Test different content types: `application/fhir+json`, `text/plain`, `x-application/hl7-v2+er7`.
237
+ - Mock `event.secrets` for bots that depend on external API keys.
238
+
239
+ ## Resource Validation
240
+ - Use `validateResource(resource)` from `@medplum/core` to check resources against StructureDefinitions.
241
+ - Test that required fields produce `OperationOutcome` errors when missing.
242
+ - Test custom profiles: create a `StructureDefinition` resource, then validate resources against it.
243
+ - Use the `$validate` operation for server-side validation in integration tests.
244
+ - Test the Data Absent Reason extension when required fields may legitimately be empty.
245
+
246
+ ## Search Testing
247
+ - Test search parameter behavior: exact match vs prefix match vs substring (`name`, `name:exact`, `name:contains`).
248
+ - Verify `_include` returns related resources in the Bundle.
249
+ - Test pagination with `_count` and `_offset` parameters.
250
+ - Test token search with and without system namespace: `identifier=value` vs `identifier=system|value`.
251
+ - Test date range searches with comparison prefixes: `ge`, `le`, `gt`, `lt`.
252
+
253
+ ## Integration Testing
254
+ - Use Medplum Docker image (`medplum/medplum-server`) for local integration tests.
255
+ - Test full workflows end-to-end: create patient → create observation → search → verify.
256
+ - Verify Bundle transactions are atomic: intentionally fail one entry and confirm rollback.
257
+ - Test access policies by authenticating as users with different policies.
258
+ - Test Subscription triggers: create a Subscription, modify a matching resource, verify Bot execution.
259
+
260
+ ## Test Data Factories
261
+ - Create typed factory functions: `createTestPatient(overrides?)`, `createTestObservation(overrides?)`.
262
+ - Use realistic but synthetic data — never use real patient data in tests.
263
+ - Include only minimal required fields by default. Let tests add specific fields via overrides.
264
+ - Use `generateId()` from `@medplum/core` for unique test identifiers.
265
+ - Use standard test identifier systems: `http://example.com/test-mrn` to avoid collision with real systems.
266
+
267
+ ## Assertions
268
+ - Assert on `OperationOutcome` for error responses: check `issue[].severity`, `issue[].code`, `issue[].expression`.
269
+ - Use `isOk(outcome)` and `isNotFound(outcome)` from `@medplum/core` for status checks.
270
+ - Use `deepEquals(a, b)` for resource comparison (ignores `meta.versionId` and `meta.lastUpdated`).
271
+ - Assert reference integrity: verify `subject.reference` matches expected `Patient/id` format.