@softspark/ai-toolkit 2.7.2 → 2.8.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 +28 -0
- package/README.md +5 -4
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/hooks/commit-quality.sh +11 -8
- package/app/hooks/quality-check.sh +5 -1
- package/app/hooks/session-start.sh +7 -3
- package/app/rules/medplum/coding-style.md +56 -0
- package/app/rules/medplum/frameworks.md +51 -0
- package/app/rules/medplum/patterns.md +61 -0
- package/app/rules/medplum/security.md +58 -0
- package/app/rules/medplum/testing.md +56 -0
- package/bin/ai-toolkit.js +1 -1
- package/kb/reference/language-rules.md +8 -4
- package/kb/reference/medplum-docs-map.md +535 -0
- package/llms-full.txt +549 -4
- package/llms.txt +1 -0
- package/manifest.json +5 -1
- package/package.json +3 -4
- package/scripts/add_rule.py +1 -1
- package/scripts/audit_skills.py +178 -6
- package/scripts/config_resolver.py +8 -2
- package/scripts/hook_sources.py +30 -3
- package/scripts/inject_hook_cli.py +1 -1
- package/scripts/install_steps/ai_tools.py +10 -2
- package/scripts/install_steps/hooks.py +1 -1
- package/scripts/install_steps/markers.py +2 -2
- package/scripts/rule_sources.py +30 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,34 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v2.8.0 — Security Hardening & GHAS Integration (2026-04-18)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`scripts/audit_skills.py --sarif`** — emits SARIF 2.1.0 JSON compatible with GitHub Advanced Security Code Scanning. Severity maps HIGH→error / WARN→warning / INFO→note. Enables the GitHub Security tab to ingest audit findings directly.
|
|
14
|
+
- **`scripts/audit_skills.py --permissions`** — per-skill tool-permission report (human + `--json` forms). Aggregates skills by tool usage (e.g. "50 skills use Bash"), flags broad Bash+Write+Edit access, prints full skill/invocable/tools table. Security review can now answer "show me every skill that can Bash" in one command.
|
|
15
|
+
- **Checksum pinning for URL-sourced rules and hooks** — `rule_sources.py` and `hook_sources.py` now persist `sha256` of the fetched payload in `sources.json`. Subsequent refreshes log `CHECKSUM CHANGED` when the upstream payload changes. Setting `AI_TOOLKIT_STRICT_PIN=1` turns the mismatch into a hard failure (exit 2) so CI can reject silent upstream tampering.
|
|
16
|
+
- **`SECRET_PLACEHOLDER_PREFIXES`** allowlist in `audit_skills.py` — WARN-level hardcoded-secret patterns now skip values starting with `REPLACE_`, `CHANGEME_`, `CHANGE_ME`, `YOUR_`, `EXAMPLE_`, `PLACEHOLDER_`, `${`, `{{`, `$ENV_`, `$(`, `<`, `xxx`, `XXX`. Fewer false positives on docs and `.env.example` fixtures.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **npm publish workflow (`.github/workflows/publish.yml`)** now runs with `--provenance` and `id-token: write`. Published tarballs carry a cryptographic provenance attestation visible on npmjs.com and verifiable via `npm audit signatures`.
|
|
20
|
+
- **`scripts/config_resolver.py:_extract_tarball`** passes `filter="data"` to `tarfile.extract` on Python 3.12+. Defense in depth on top of existing path-traversal, symlink, and absolute-path rejection. Future-proofs against the 3.14 default-filter change.
|
|
21
|
+
- **`app/hooks/session-start.sh`** sanitises `VERSION_MSG` with `LC_ALL=C tr -d '"'"'"'\\`$'` before interpolating into `osascript` / `powershell.exe` notification commands. Closes a latent command-injection footgun.
|
|
22
|
+
- **`scripts/install_steps/ai_tools.py`** and **`hooks.py`** — generator and merge-hooks `subprocess.run` calls now have `timeout=120`. A stuck generator produces a clear error instead of hanging `ai-toolkit install` indefinitely.
|
|
23
|
+
- **`app/hooks/commit-quality.sh`** — extracted commit message via a small Python regex instead of fragile shell `grep -oE` chain. Handles commit messages containing mixed `"` and `'` correctly.
|
|
24
|
+
- **`app/hooks/quality-check.sh`** — normalised `|| true` handling across all languages (Python ruff was previously the only one propagating exit status). All language checks are now consistently advisory on the Stop hook.
|
|
25
|
+
- **`bin/ai-toolkit.js:handleAddRule`** — simplified HTTPS/HTTP detection (single `startsWith('https://')` after the `http://` reject).
|
|
26
|
+
- **`package.json` description** shortened from 400+ to 241 characters — stops mid-sentence truncation in npm search. Surfaces the new SARIF + provenance differentiators.
|
|
27
|
+
- **`package.json` `engines`** — removed non-standard `bats` entry (npm ignores unknown engines and warned on install). Bats requirement is documented in `CLAUDE.md` Commands section.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## v2.7.3 — Regenerate llms after Medplum Merge (2026-04-17)
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- **`llms.txt` + `llms-full.txt` stale after PR #7 (Medplum/FHIR rules)** — contributor did not re-run `npm run generate:all` before merge, so the machine-readable catalogs did not list `kb/reference/medplum-docs-map.md`, and `kb/reference/language-rules.md` still advertised `13 languages / 68 rule files` instead of `14 languages / 73 rule files`. Validator passes regardless (it does not diff generated text), so the drift slipped past CI. Regenerated from source; added Medplum row to the languages table.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
10
38
|
## v2.7.2 — Doc & Model-ID Consistency (2026-04-17)
|
|
11
39
|
|
|
12
40
|
### Changed
|
package/README.md
CHANGED
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
## What's New in v2.
|
|
13
|
+
## What's New in v2.8.0
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
-
|
|
17
|
-
-
|
|
15
|
+
- **SARIF + permissions audit** — `audit_skills.py --sarif` emits SARIF 2.1.0 for GitHub Advanced Security Code Scanning; `--permissions` prints per-skill tool usage (e.g. "50 skills use Bash") and flags broad Bash+Write+Edit access.
|
|
16
|
+
- **Signed npm provenance** — the publish workflow now runs with `--provenance`; published tarballs carry a cryptographic build-origin attestation verifiable via `npm audit signatures`.
|
|
17
|
+
- **Checksum-pinned URL sources** — `sources.json` now persists `sha256` of every URL-sourced rule/hook and warns on upstream content change. `AI_TOOLKIT_STRICT_PIN=1` turns mismatches into a hard CI failure.
|
|
18
|
+
- **Security hardening** — `tarfile.extract` uses `filter="data"` on Python 3.12+; `session-start.sh` sanitises `VERSION_MSG` before `osascript`/`powershell.exe`; install-time `subprocess.run` calls now time out at 120 s.
|
|
18
19
|
|
|
19
20
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
20
21
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-toolkit",
|
|
3
3
|
"description": "Professional-grade Claude Code toolkit with persona presets, skill security auditor, expanded lifecycle hooks, experimental opt-in plugin packs, benchmark harvesting, and multi-tool support.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.8.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SoftSpark",
|
|
7
7
|
"url": "https://github.com/softspark"
|
|
@@ -14,14 +14,17 @@ fi
|
|
|
14
14
|
# Only check commands that contain "git commit"
|
|
15
15
|
printf '%s' "$COMMAND" | grep -q 'git commit' || exit 0
|
|
16
16
|
|
|
17
|
-
# Extract commit message from -m flag
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
# Extract commit message from -m flag. Delegated to Python for correct
|
|
18
|
+
# quote handling — the previous shell-regex approach broke on commit messages
|
|
19
|
+
# containing both " and '.
|
|
20
|
+
MSG=$(printf '%s' "$COMMAND" | python3 -c '
|
|
21
|
+
import re, sys
|
|
22
|
+
cmd = sys.stdin.read()
|
|
23
|
+
# Match -m followed by a quoted string (either " or ").
|
|
24
|
+
m = re.search(r"""-m\s+("((?:[^"\\]|\\.)*)"|'"'"'((?:[^'"'"'\\]|\\.)*)'"'"')""", cmd)
|
|
25
|
+
if m:
|
|
26
|
+
print(m.group(2) if m.group(2) is not None else m.group(3))
|
|
27
|
+
' 2>/dev/null)
|
|
25
28
|
|
|
26
29
|
# No message found (might be --amend or interactive) — skip
|
|
27
30
|
if [ -z "$MSG" ]; then
|
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
# shellcheck source=_profile-check.sh
|
|
8
8
|
source "$(dirname "$0")/_profile-check.sh"
|
|
9
9
|
|
|
10
|
+
# All lint/typecheck invocations are advisory. They run on the Stop hook and
|
|
11
|
+
# must not block Claude from returning a response, hence the trailing `|| true`.
|
|
12
|
+
# The first 15 lines of output are surfaced to the user; further lines are
|
|
13
|
+
# truncated to keep the context lean.
|
|
10
14
|
if [ -f pyproject.toml ] || [ -f setup.py ]; then
|
|
11
|
-
ruff check . 2>&1 | head -15
|
|
15
|
+
ruff check . 2>&1 | head -15 || true
|
|
12
16
|
elif [ -f package.json ] && [ -f tsconfig.json ]; then
|
|
13
17
|
npx tsc --noEmit 2>&1 | head -15 || true
|
|
14
18
|
elif [ -f composer.json ] && [ -f vendor/bin/phpstan ]; then
|
|
@@ -14,13 +14,17 @@ TOOLKIT_DIR="$(npm root -g 2>/dev/null)/@softspark/ai-toolkit"
|
|
|
14
14
|
VERSION_MSG=$(python3 "$TOOLKIT_DIR/scripts/version_check.py" 2>/dev/null)
|
|
15
15
|
if [ -n "$VERSION_MSG" ]; then
|
|
16
16
|
echo "$VERSION_MSG"
|
|
17
|
+
# Strip shell/AppleScript/PowerShell metacharacters before interpolating into
|
|
18
|
+
# notification commands. VERSION_MSG is version_check.py output which should
|
|
19
|
+
# be plain ASCII, but sanitize anyway as defense in depth.
|
|
20
|
+
VERSION_MSG_SAFE=$(printf '%s' "$VERSION_MSG" | LC_ALL=C tr -d '"'"'"'\\`$')
|
|
17
21
|
# Desktop notification so user sees update before typing
|
|
18
22
|
if command -v osascript >/dev/null 2>&1; then
|
|
19
|
-
osascript -e "display notification \"$
|
|
23
|
+
osascript -e "display notification \"$VERSION_MSG_SAFE\" with title \"ai-toolkit\"" 2>/dev/null &
|
|
20
24
|
elif command -v notify-send >/dev/null 2>&1; then
|
|
21
|
-
notify-send "ai-toolkit" "$
|
|
25
|
+
notify-send "ai-toolkit" "$VERSION_MSG_SAFE" 2>/dev/null &
|
|
22
26
|
elif command -v powershell.exe >/dev/null 2>&1; then
|
|
23
|
-
powershell.exe -Command "[void](New-Object -ComObject WScript.Shell).Popup('$
|
|
27
|
+
powershell.exe -Command "[void](New-Object -ComObject WScript.Shell).Popup('$VERSION_MSG_SAFE',5,'ai-toolkit',64)" 2>/dev/null &
|
|
24
28
|
fi
|
|
25
29
|
fi
|
|
26
30
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
language: medplum
|
|
3
|
+
category: coding-style
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Medplum / FHIR Coding Style
|
|
8
|
+
|
|
9
|
+
## Resource Structure
|
|
10
|
+
- Every FHIR object must include `resourceType` as first field.
|
|
11
|
+
- Use PascalCase for resource types (`Patient`, `ServiceRequest`), camelCase for fields (`birthDate`, `valueQuantity`).
|
|
12
|
+
- Never hardcode resource IDs. Let the server assign them on create.
|
|
13
|
+
- Include `meta.profile` when creating resources that must conform to a StructureDefinition.
|
|
14
|
+
|
|
15
|
+
## References
|
|
16
|
+
- Use `createReference(resource)` from `@medplum/core` to build Reference objects.
|
|
17
|
+
- Always include `display` on references for human readability.
|
|
18
|
+
- Use `getReferenceString(resource)` for comparisons and logging — returns `ResourceType/id`.
|
|
19
|
+
- Use `parseReference(ref)` to extract resourceType and id from a reference string.
|
|
20
|
+
- Never concatenate strings to build references manually.
|
|
21
|
+
|
|
22
|
+
## CodeableConcepts & Coding
|
|
23
|
+
- Always include `system`, `code`, and `display` in every Coding element.
|
|
24
|
+
- Use standard terminology URIs: `http://loinc.org`, `http://snomed.info/sct`, `http://hl7.org/fhir/sid/icd-10-cm`.
|
|
25
|
+
- Use `getCodeBySystem(cc, system)` to find codes; `setCodeBySystem(cc, system, code)` to set them.
|
|
26
|
+
- Prefer `CodeableConcept` over plain `Coding` when the FHIR spec allows both — it supports multiple codings and free text.
|
|
27
|
+
|
|
28
|
+
## Identifiers
|
|
29
|
+
- Use `identifier` arrays with `system` + `value` for external IDs (MRN, NPI, SSN).
|
|
30
|
+
- Use `getIdentifier(resource, system)` and `setIdentifier(resource, system, value)` helpers.
|
|
31
|
+
- Identifier systems must be absolute URIs (e.g., `http://hl7.org/fhir/sid/us-npi`).
|
|
32
|
+
- Use `createResourceIfNoneExist(resource, 'identifier=system|value')` for idempotent creates.
|
|
33
|
+
|
|
34
|
+
## Extensions
|
|
35
|
+
- Use the `extension` array with `url` and typed `value[x]` fields.
|
|
36
|
+
- Prefer official HL7/US Core extensions over custom ones where they exist.
|
|
37
|
+
- Use `getExtension(resource, url)` and `getExtensionValue(resource, url)` helpers.
|
|
38
|
+
|
|
39
|
+
## Bundles
|
|
40
|
+
- Use `urn:uuid:<uuid>` for internal references between entries in a transaction Bundle.
|
|
41
|
+
- Every Bundle entry must have `request.method` (`POST`, `PUT`, `DELETE`) and `request.url`.
|
|
42
|
+
- Include `fullUrl` on entries that are referenced by other entries.
|
|
43
|
+
- Use conditional references (`Practitioner?identifier=npi|123`) for existing resources.
|
|
44
|
+
|
|
45
|
+
## HIPAA-Aware Coding
|
|
46
|
+
- Identifiers like SSN, MRN, and insurance IDs are PHI — never log raw values to console or external services.
|
|
47
|
+
- Use a safe logging utility (e.g., `safeLog()`) for any output that might contain patient data. Never `console.log` raw FHIR resources.
|
|
48
|
+
- Reference `display` strings may contain patient names — treat as PHI in logs and error messages.
|
|
49
|
+
- Every new data access path or admin operation must include corresponding `AuditEvent` creation. No exceptions.
|
|
50
|
+
- When audit logging fails (Medplum unreachable), write to a fallback store — audit events must never be silently dropped.
|
|
51
|
+
- See `security.md` rules for full HIPAA, access policy, and PHI handling requirements.
|
|
52
|
+
|
|
53
|
+
## Formatting
|
|
54
|
+
- Use `formatHumanName()`, `formatAddress()`, `formatDate()`, `formatQuantity()` for display strings.
|
|
55
|
+
- Use `getDisplayString(resource)` as a universal fallback for any resource's display name.
|
|
56
|
+
- Never manually concatenate name parts — FHIR names have `given[]`, `family`, `prefix[]`, `suffix[]`.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
language: medplum
|
|
3
|
+
category: frameworks
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Medplum Frameworks
|
|
8
|
+
|
|
9
|
+
## @medplum/core — SDK Client
|
|
10
|
+
- Use `MedplumClient` for all FHIR operations. Never use raw `fetch` against Medplum endpoints.
|
|
11
|
+
- Use `medplum.createResource()`, `readResource()`, `updateResource()`, `deleteResource()` for CRUD.
|
|
12
|
+
- Use `medplum.searchResources()` for typed arrays. Use `medplum.searchOne()` when expecting a single result.
|
|
13
|
+
- Use `medplum.executeBatch()` for transaction Bundles — groups multiple operations atomically.
|
|
14
|
+
- Use `medplum.upsertResource(resource, query)` for atomic create-or-update.
|
|
15
|
+
- Use `medplum.createResourceIfNoneExist(resource, query)` for idempotent creation.
|
|
16
|
+
- Configure `autoBatchTime` on MedplumClient to auto-batch concurrent GET requests. Use `Promise.all()` instead of sequential `await` to benefit from batching.
|
|
17
|
+
|
|
18
|
+
## @medplum/fhirtypes — Type Safety
|
|
19
|
+
- Import FHIR types directly: `import { Patient, Observation } from '@medplum/fhirtypes'`.
|
|
20
|
+
- Use TypeScript types for all FHIR resources — never use `any` for resource data.
|
|
21
|
+
- Cast `event.input` in bot handlers: `const patient = event.input as Patient`.
|
|
22
|
+
- Use optional chaining for nested FHIR fields: `patient.name?.[0]?.given?.[0]`.
|
|
23
|
+
|
|
24
|
+
## @medplum/react — UI Components
|
|
25
|
+
- Wrap app with `<MedplumProvider client={medplum}>` at the root.
|
|
26
|
+
- Use `useMedplum()` hook to access the MedplumClient instance in components.
|
|
27
|
+
- Use `useMedplumContext()` for client + profile + loading state together.
|
|
28
|
+
- Use `<ResourceForm>` for auto-generated CRUD forms, `<ResourceTable>` for display.
|
|
29
|
+
- Use `<SearchControl>` for searchable/filterable resource lists.
|
|
30
|
+
- Use `<QuestionnaireForm>` to render FHIR Questionnaires and capture responses.
|
|
31
|
+
- Use `useSubscription(criteria)` for real-time WebSocket data in React components.
|
|
32
|
+
- Requires Mantine 7+ and PostCSS with Mantine preset. Import `@mantine/core/styles.css`.
|
|
33
|
+
|
|
34
|
+
## Bot Development
|
|
35
|
+
- Export a single `handler` function: `export async function handler(medplum: MedplumClient, event: BotEvent)`.
|
|
36
|
+
- Access trigger resource via `event.input`. Access secrets via `event.secrets`.
|
|
37
|
+
- Use `event.contentType` to determine input format (`application/fhir+json`, `text/plain`, `x-application/hl7-v2+er7`).
|
|
38
|
+
- Deploy bots via CLI for CI/CD: `medplum bot deploy <bot-name>`.
|
|
39
|
+
- Apply AccessPolicies to bots — restrict to minimum required resource types.
|
|
40
|
+
- Use Subscriptions with `channel.type: 'rest-hook'` and `channel.endpoint: 'Bot/<ID>'` for event-driven execution.
|
|
41
|
+
|
|
42
|
+
## GraphQL
|
|
43
|
+
- Append `List` to resource type for searches: `PatientList(name: "Eve")`.
|
|
44
|
+
- Use snake_case for search parameters in GraphQL (not kebab-case): `address_city`, not `address-city`.
|
|
45
|
+
- Use inline fragments for reference resolution: `... on Observation { valueQuantity { value } }`.
|
|
46
|
+
- Use `_reference` for reverse lookups: `EncounterList(_reference: patient)`.
|
|
47
|
+
|
|
48
|
+
## CLI (@medplum/cli)
|
|
49
|
+
- Use `medplum login` for auth, `medplum get`/`medplum post` for FHIR operations.
|
|
50
|
+
- Use `medplum bot deploy` for bot deployment in CI/CD pipelines.
|
|
51
|
+
- Use `medplum bulk export` for bulk data operations.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
language: medplum
|
|
3
|
+
category: patterns
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Medplum / FHIR Patterns
|
|
8
|
+
|
|
9
|
+
## Bundle Transactions
|
|
10
|
+
- Use `type: 'transaction'` for atomic multi-resource operations — all-or-nothing.
|
|
11
|
+
- Use `type: 'batch'` when operations are independent and partial failure is acceptable.
|
|
12
|
+
- Use `urn:uuid:<uuid>` in `fullUrl` for forward references between entries.
|
|
13
|
+
- Reference other entries via `{ reference: 'urn:uuid:<uuid>' }`.
|
|
14
|
+
- Use `ifNoneExist` on POST entries for conditional creation (idempotent).
|
|
15
|
+
- Use `ifMatch: 'W/"versionId"'` on PUT entries for optimistic concurrency.
|
|
16
|
+
- Use conditional references for existing resources: `Practitioner?identifier=http://hl7.org/fhir/sid/us-npi|123`.
|
|
17
|
+
- For large bundles (>50MB), use `Prefer: respond-async` header.
|
|
18
|
+
|
|
19
|
+
## Search Patterns
|
|
20
|
+
- Use `_include=ResourceType:searchParam` to fetch referenced resources in one call.
|
|
21
|
+
- Use `_revinclude=ResourceType:searchParam` to fetch resources referencing your results.
|
|
22
|
+
- Use `:iterate` modifier for multi-hop traversal: `_include:iterate=Patient:general-practitioner`.
|
|
23
|
+
- Use `_count` + `_offset` for pagination; use `searchResourcePages()` for async iteration.
|
|
24
|
+
- Use `:contains` modifier for substring search on string params: `name:contains=eve`.
|
|
25
|
+
- Use `:not` modifier to exclude: `status:not=completed`.
|
|
26
|
+
- Use comma-separated values for OR: `status=active,on-hold`.
|
|
27
|
+
- Use multiple parameters for AND: `name=Simpson&birthdate=1940-03-29`.
|
|
28
|
+
- Prefer `searchResources()` over `search()` — returns typed array, not raw Bundle.
|
|
29
|
+
|
|
30
|
+
## Subscription & Bot Workflows
|
|
31
|
+
- Create a `Subscription` resource with `criteria` (FHIR search query) and `channel.type: 'rest-hook'`.
|
|
32
|
+
- Point `channel.endpoint` to `Bot/<BOT_ID>` for automated processing.
|
|
33
|
+
- Use `subscribeToCriteria()` client-side for WebSocket real-time updates.
|
|
34
|
+
- Never subscribe to `AuditEvent` changes — prevents notification spirals.
|
|
35
|
+
- Use cron-based bots for scheduled tasks (e.g., daily reports, batch processing).
|
|
36
|
+
|
|
37
|
+
## Access Policies
|
|
38
|
+
- Define `AccessPolicy.resource[]` with `resourceType` and optional `criteria`, `readonly`, `hiddenFields`, `readonlyFields`.
|
|
39
|
+
- Use `%profile` variable to scope data to the current user: `Observation?performer=%profile`.
|
|
40
|
+
- Use `%patient` variable for patient-portal access: `Observation?subject=%patient`.
|
|
41
|
+
- Use compartment-based access for patient-scoped isolation.
|
|
42
|
+
- Use `writeConstraint` with FHIRPath for state machine enforcement (e.g., prevent status rollback).
|
|
43
|
+
- Apply least privilege: start with no access, add specific resource types.
|
|
44
|
+
|
|
45
|
+
## Conditional Operations
|
|
46
|
+
- Use `createResourceIfNoneExist(resource, query)` for idempotent creates keyed on identifier.
|
|
47
|
+
- Use `upsertResource(resource, query)` for atomic create-or-update in a single request.
|
|
48
|
+
- Use `If-None-Exist` header on POST for server-side conditional creation.
|
|
49
|
+
|
|
50
|
+
## Patient Deduplication
|
|
51
|
+
- Match on `identifier` systems (MRN, SSN, insurance ID) for deterministic matching.
|
|
52
|
+
- Use probabilistic matching on name + birthdate + address for fuzzy matches.
|
|
53
|
+
- Use Patient `link` field with `type: 'replaced-by'` for merge workflows.
|
|
54
|
+
- Prefer `createResourceIfNoneExist()` at ingestion to prevent duplicates.
|
|
55
|
+
|
|
56
|
+
## Questionnaire Workflows
|
|
57
|
+
- Create `Questionnaire` resources for form definitions. Use `linkId` for question identification.
|
|
58
|
+
- Use `QuestionnaireResponse` for captured answers. Link to Questionnaire via `questionnaire` field.
|
|
59
|
+
- Use `getQuestionnaireAnswers(response)` to extract answers as a map keyed by `linkId`.
|
|
60
|
+
- Automate post-submission processing with a Bot subscribed to `QuestionnaireResponse` creation.
|
|
61
|
+
- Use SDC (Structured Data Capture) extensions for advanced rendering and extraction.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
language: medplum
|
|
3
|
+
category: security
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Medplum / FHIR Security
|
|
8
|
+
|
|
9
|
+
## Authentication
|
|
10
|
+
- Use client credentials flow (`startClientLogin`) for backend services and integrations.
|
|
11
|
+
- Use authorization code flow (`startLogin` + `processCode`) for user-facing web apps.
|
|
12
|
+
- Never store tokens in `localStorage` in production — use secure HTTP-only cookies or server-side sessions.
|
|
13
|
+
- Use `refreshIfExpired()` before critical operations. Set `gracePeriod` to refresh proactively.
|
|
14
|
+
- Use `setBasicAuth(clientId, clientSecret)` only for server-side code, never in browser.
|
|
15
|
+
- Rotate client secrets via `$rotate-client-secret` operation periodically.
|
|
16
|
+
|
|
17
|
+
## Access Policies
|
|
18
|
+
- Every non-admin user must have an AccessPolicy. Never leave users with default full access.
|
|
19
|
+
- Scope to specific resource types: list only the types the user needs.
|
|
20
|
+
- Use `readonly: true` or explicit `interaction` arrays to restrict write access.
|
|
21
|
+
- Use `criteria` with FHIR search syntax to filter visible resources (e.g., `Patient?organization=Organization/123`).
|
|
22
|
+
- Use `hiddenFields` to prevent sensitive fields from being returned (e.g., SSN).
|
|
23
|
+
- Use `readonlyFields` to allow viewing but prevent modification of specific fields.
|
|
24
|
+
- Use `writeConstraint` FHIRPath expressions for business rules (e.g., prevent status rollback on finalized resources).
|
|
25
|
+
- Test access policies by logging in as a test user with the policy applied.
|
|
26
|
+
|
|
27
|
+
## HIPAA & Audit Logging
|
|
28
|
+
- Medplum automatically creates AuditEvent resources for all FHIR operations.
|
|
29
|
+
- Never log PHI (patient names, identifiers, health data) to application console or external services.
|
|
30
|
+
- Use structured audit references: reference the Patient and the accessing Practitioner in audit records.
|
|
31
|
+
- For custom audit trails, create AuditEvent resources with `type`, `agent`, `entity`, and `outcome`.
|
|
32
|
+
- Ensure audit events are never silently dropped — if Medplum is unreachable, write to a fallback store.
|
|
33
|
+
|
|
34
|
+
## PHI Handling
|
|
35
|
+
- Never include PHI in URLs, query parameters, or HTTP headers.
|
|
36
|
+
- Use POST-based search for queries containing sensitive criteria.
|
|
37
|
+
- Use `Binary` resources with `securityContext` for sensitive file attachments.
|
|
38
|
+
- Encrypt data at rest and in transit (TLS 1.2+). Medplum hosted handles this automatically.
|
|
39
|
+
- Apply data retention policies — use `$expunge` operation for permanent deletion when required.
|
|
40
|
+
|
|
41
|
+
## SMART Scopes
|
|
42
|
+
- Use `patient/*.read` style scopes for patient-facing apps.
|
|
43
|
+
- Use `user/*.read` style scopes for practitioner-facing apps.
|
|
44
|
+
- Validate scopes server-side on every request — do not trust client-side scope claims.
|
|
45
|
+
- Use `launch/patient` context for apps launched within a patient context.
|
|
46
|
+
- Define minimal scopes: request only the resource types and operations needed.
|
|
47
|
+
|
|
48
|
+
## Multi-Tenant Isolation
|
|
49
|
+
- Use Medplum Projects for tenant isolation — each project is a separate data silo.
|
|
50
|
+
- Never share AccessPolicies across projects/tenants.
|
|
51
|
+
- Validate `meta.project` on operations when building multi-tenant middleware.
|
|
52
|
+
- Use separate ClientApplications per tenant for backend integrations.
|
|
53
|
+
|
|
54
|
+
## Secrets Management
|
|
55
|
+
- Use Bot secrets (`event.secrets`) for API keys, connection strings, and credentials.
|
|
56
|
+
- Never hardcode secrets in bot source code or resource data.
|
|
57
|
+
- Use Medplum project-level secrets storage — accessible only by project admins.
|
|
58
|
+
- Rotate secrets on a regular schedule and after any suspected compromise.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
language: medplum
|
|
3
|
+
category: testing
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Medplum / FHIR Testing
|
|
8
|
+
|
|
9
|
+
## MockClient
|
|
10
|
+
- Use `MockClient` from `@medplum/mock` for unit tests — it simulates the full MedplumClient API in memory.
|
|
11
|
+
- Pre-populate test data with `mockClient.createResource()` before running test assertions.
|
|
12
|
+
- MockClient supports `search`, `searchResources`, `searchOne`, `readResource`, `updateResource`, `deleteResource`.
|
|
13
|
+
- MockClient does not require network access — tests run fast and offline.
|
|
14
|
+
- Use `new MockClient()` per test to ensure isolation between test cases.
|
|
15
|
+
|
|
16
|
+
## Bot Unit Testing
|
|
17
|
+
- Test the handler function directly: `await handler(mockClient, mockEvent)`.
|
|
18
|
+
- Create mock `BotEvent` objects with `input`, `contentType`, `secrets`, and `bot` fields.
|
|
19
|
+
- Verify resource creation: call `mockClient.searchResources()` after handler execution.
|
|
20
|
+
- Test error paths: pass invalid input resources and assert the handler throws or returns errors.
|
|
21
|
+
- Test different content types: `application/fhir+json`, `text/plain`, `x-application/hl7-v2+er7`.
|
|
22
|
+
- Mock `event.secrets` for bots that depend on external API keys.
|
|
23
|
+
|
|
24
|
+
## Resource Validation
|
|
25
|
+
- Use `validateResource(resource)` from `@medplum/core` to check resources against StructureDefinitions.
|
|
26
|
+
- Test that required fields produce `OperationOutcome` errors when missing.
|
|
27
|
+
- Test custom profiles: create a `StructureDefinition` resource, then validate resources against it.
|
|
28
|
+
- Use the `$validate` operation for server-side validation in integration tests.
|
|
29
|
+
- Test the Data Absent Reason extension when required fields may legitimately be empty.
|
|
30
|
+
|
|
31
|
+
## Search Testing
|
|
32
|
+
- Test search parameter behavior: exact match vs prefix match vs substring (`name`, `name:exact`, `name:contains`).
|
|
33
|
+
- Verify `_include` returns related resources in the Bundle.
|
|
34
|
+
- Test pagination with `_count` and `_offset` parameters.
|
|
35
|
+
- Test token search with and without system namespace: `identifier=value` vs `identifier=system|value`.
|
|
36
|
+
- Test date range searches with comparison prefixes: `ge`, `le`, `gt`, `lt`.
|
|
37
|
+
|
|
38
|
+
## Integration Testing
|
|
39
|
+
- Use Medplum Docker image (`medplum/medplum-server`) for local integration tests.
|
|
40
|
+
- Test full workflows end-to-end: create patient → create observation → search → verify.
|
|
41
|
+
- Verify Bundle transactions are atomic: intentionally fail one entry and confirm rollback.
|
|
42
|
+
- Test access policies by authenticating as users with different policies.
|
|
43
|
+
- Test Subscription triggers: create a Subscription, modify a matching resource, verify Bot execution.
|
|
44
|
+
|
|
45
|
+
## Test Data Factories
|
|
46
|
+
- Create typed factory functions: `createTestPatient(overrides?)`, `createTestObservation(overrides?)`.
|
|
47
|
+
- Use realistic but synthetic data — never use real patient data in tests.
|
|
48
|
+
- Include only minimal required fields by default. Let tests add specific fields via overrides.
|
|
49
|
+
- Use `generateId()` from `@medplum/core` for unique test identifiers.
|
|
50
|
+
- Use standard test identifier systems: `http://example.com/test-mrn` to avoid collision with real systems.
|
|
51
|
+
|
|
52
|
+
## Assertions
|
|
53
|
+
- Assert on `OperationOutcome` for error responses: check `issue[].severity`, `issue[].code`, `issue[].expression`.
|
|
54
|
+
- Use `isOk(outcome)` and `isNotFound(outcome)` from `@medplum/core` for status checks.
|
|
55
|
+
- Use `deepEquals(a, b)` for resource comparison (ignores `meta.versionId` and `meta.lastUpdated`).
|
|
56
|
+
- Assert reference integrity: verify `subject.reference` matches expected `Patient/id` format.
|
package/bin/ai-toolkit.js
CHANGED
|
@@ -368,11 +368,11 @@ function handleAddRule(args) {
|
|
|
368
368
|
process.exit(1);
|
|
369
369
|
}
|
|
370
370
|
// Pass URLs through directly (don't resolve as filesystem path)
|
|
371
|
-
const isUrl = ruleFile.startsWith('https://') || ruleFile.startsWith('http://');
|
|
372
371
|
if (ruleFile.startsWith('http://')) {
|
|
373
372
|
console.error('Error: only HTTPS URLs are supported. Use https:// for security.');
|
|
374
373
|
process.exit(1);
|
|
375
374
|
}
|
|
375
|
+
const isUrl = ruleFile.startsWith('https://');
|
|
376
376
|
const absRuleFile = isUrl ? ruleFile : path.resolve(CWD, ruleFile);
|
|
377
377
|
const ruleName = args[1];
|
|
378
378
|
run(scriptPath('add_rule.py'), ruleName ? [absRuleFile, ruleName] : [absRuleFile]);
|
|
@@ -6,14 +6,14 @@ tags: [rules, languages, coding-style, testing, patterns, security]
|
|
|
6
6
|
version: "1.0.0"
|
|
7
7
|
created: "2026-04-07"
|
|
8
8
|
last_updated: "2026-04-07"
|
|
9
|
-
description: "Reference for the language-specific rules system:
|
|
9
|
+
description: "Reference for the language-specific rules system: 14 languages, 5 categories per language, auto-detection."
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Language Rules System
|
|
13
13
|
|
|
14
14
|
## Overview
|
|
15
15
|
|
|
16
|
-
ai-toolkit ships language-specific rule files covering
|
|
16
|
+
ai-toolkit ships language-specific rule files covering 14 languages/platforms plus a common set (see README.md for current count). Rules are plain Markdown files injected into `CLAUDE.md` via `ai-toolkit install --local`. They provide coding-style, testing, patterns, frameworks, and security guidance specific to each language.
|
|
17
17
|
|
|
18
18
|
Rules are distinct from skills: rules are injected as static text into `CLAUDE.md` and are always visible to Claude, whereas skills are loaded contextually by agents.
|
|
19
19
|
|
|
@@ -48,10 +48,11 @@ app/rules/
|
|
|
48
48
|
├── csharp/
|
|
49
49
|
├── php/
|
|
50
50
|
├── cpp/
|
|
51
|
-
|
|
51
|
+
├── ruby/
|
|
52
|
+
└── medplum/
|
|
52
53
|
```
|
|
53
54
|
|
|
54
|
-
**Total:
|
|
55
|
+
**Total: 14 directories × 5 files each + 3 standalone = 73 rule files** (see README.md for canonical count)
|
|
55
56
|
|
|
56
57
|
## Supported Languages
|
|
57
58
|
|
|
@@ -70,6 +71,7 @@ app/rules/
|
|
|
70
71
|
| PHP | `rules/php/` | `composer.json` |
|
|
71
72
|
| C++ | `rules/cpp/` | `CMakeLists.txt`, `Makefile`, `*.cpp` |
|
|
72
73
|
| Ruby | `rules/ruby/` | `Gemfile`, `*.gemspec` |
|
|
74
|
+
| Medplum | `rules/medplum/` | `medplum.config.mts`, `medplum.config.ts` |
|
|
73
75
|
|
|
74
76
|
## Rule Categories
|
|
75
77
|
|
|
@@ -107,6 +109,7 @@ Scans for configuration files defined in each module's `auto_detect` list in `ma
|
|
|
107
109
|
10. `Package.swift` → Swift
|
|
108
110
|
11. `*.csproj` or `*.sln` → C#
|
|
109
111
|
12. `CMakeLists.txt` or `Makefile` → C++
|
|
112
|
+
13. `medplum.config.mts` or `medplum.config.ts` → Medplum
|
|
110
113
|
|
|
111
114
|
### Phase 2: Source file extensions (actual code presence)
|
|
112
115
|
|
|
@@ -163,6 +166,7 @@ Language rules are tracked as modules in `manifest.json`:
|
|
|
163
166
|
| `rules-php` | PHP-specific rules |
|
|
164
167
|
| `rules-cpp` | C++-specific rules |
|
|
165
168
|
| `rules-ruby` | Ruby-specific rules |
|
|
169
|
+
| `rules-medplum` | Medplum/FHIR healthcare platform rules |
|
|
166
170
|
|
|
167
171
|
## Rules vs Skills
|
|
168
172
|
|