Skip to content

Commit fbf2e76

Browse files
Add truncate-aidbox script and make skills compatible with Haiku model
Co-authored-by: Nikita Sintsev <nikita.sintsev@health-samurai.io>
1 parent 614f8ac commit fbf2e76

8 files changed

Lines changed: 427 additions & 52 deletions

File tree

.claude/skills/hl7v2-to-fhir-pipeline/SKILL.md

Lines changed: 86 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,69 +5,114 @@ description: Build a new HL7v2→FHIR message converter or extend an existing on
55

66
# HL7v2 → FHIR Converter Pipeline
77

8-
Extend the HL7v2→FHIR conversion module per the user's request. You are the orchestrator; sub-agents and the `/plan` and `/work` skills do the heavy lifting.
8+
Build or extend an HL7v2→FHIR converter. Four phases: **Setup → Requirements → Plan → Execute**.
99

10-
If the request isn't about a new message converter, a new field mapping, or adapting existing conversion to new messages — return an error.
10+
**One invocation = one phase.** Detect which phase is next from ticket state, run it, commit, stop. Do NOT re-invoke this skill — the user does that.
1111

12-
## Step 0 — Is it already supported?
12+
If the request is not about a converter (new message, new field mapping, extending conversion), stop and say so.
1313

14-
Before creating a ticket, if the user supplies one or more example messages, run each through `message-lookup`:
14+
---
15+
16+
## Step A — Detect which phase to run
17+
18+
The arg from the user names a message type or ticket slug (e.g. `ADT_A03`, `adt-a03-discharge`).
19+
20+
**A.1 — Find or choose the ticket folder.**
21+
22+
Search for an existing folder:
23+
24+
```sh
25+
ls ai/tickets/converter-skill-tickets/ 2>/dev/null | grep -i <keyword>
26+
```
27+
28+
`<keyword>` = a lowercase fragment of the arg (e.g. `a03` for `ADT_A03`).
29+
30+
- Match found → `TICKET_DIR=ai/tickets/converter-skill-tickets/<match>`. Go to A.2.
31+
- No match → this is new work. `SLUG` = kebab-case descriptive name (e.g. `adt-a03-discharge`). `TICKET_DIR=ai/tickets/converter-skill-tickets/<SLUG>`. Run **Phase 1**.
32+
33+
**A.2 — Inspect ticket.md to pick the phase.**
1534

1635
```sh
17-
bun scripts/check-message-support.ts <example-file>
36+
grep -c "^# Requirements" $TICKET_DIR/ticket.md
37+
grep -c "^# Implementation Plan" $TICKET_DIR/ticket.md
38+
grep -c "^- \[ \]" $TICKET_DIR/ticket.md
1839
```
1940

20-
- All examples report `supported — message converts cleanly` → tell the user "this is already supported" and stop. Do not open a ticket.
21-
- All examples report `NOT supported — no converter registered` → proceed to Step 1; this is a new-converter case.
22-
- Examples report `routed but data fails conversion` or `supported with caveats` → the converter exists but has gaps. Ask the user whether they want to extend the existing converter (this pipeline) or fix a specific error (`check-errors`).
41+
| `# Requirements` | `# Implementation Plan` | Unchecked `- [ ]` | Run |
42+
|---|---|---|---|
43+
| 0 | 0 || Phase 2 |
44+
| ≥1 | 0 || Phase 3 |
45+
| ≥1 | ≥1 | ≥1 | Phase 4 |
46+
| ≥1 | ≥1 | 0 | Ask user what they want next. Stop. |
47+
48+
---
49+
50+
## Rules (apply to every phase)
2351

24-
If the user supplies no example messages, skip Step 0.
52+
- You talk to the user. Sub-agents, `/plan`, `/work` run silently — their output returns to you.
53+
- Commit after each phase (ticket edits + new files).
54+
- **De-identify example messages before saving them.** Change names, dates, and numeric IDs in any user-supplied message before writing it to `examples/` or a fixture. Never paste raw PHI.
2555

26-
## Rules
56+
---
2757

28-
- You are the only thing that talks to the user. Sub-agents and `/plan`/`/work` delegates do not.
29-
- If a sub-agent needs user input, resume it with the user's answer — don't re-prompt the sub-agent from scratch.
30-
- Between steps, commit the ticket changes and move on. Don't summarize sub-agent output unless the user asks.
31-
- **Never use user-provided example messages unchanged.** De-identify them (change names, dates, numeric IDs) before writing them into fixtures, docs, or the ticket.
58+
## Phase 1 — Setup (new ticket)
59+
60+
1. Create the ticket folder: `mkdir -p $TICKET_DIR/examples`.
61+
2. If the user supplied example messages, first pre-flight them:
62+
```sh
63+
bun scripts/check-message-support.ts <file>
64+
```
65+
If **all** report `supported — message converts cleanly`, delete the folder you just made and stop — no ticket needed. Tell the user it already works.
66+
3. Write `$TICKET_DIR/ticket.md` with a `# Goal` section (2-4 sentences: what + why).
67+
4. Populate `$TICKET_DIR/examples/`:
68+
- User supplied messages → de-identify, one message per file.
69+
- User has no examples → spawn an agent:
70+
```
71+
Agent({
72+
description: "Generate HL7v2 examples",
73+
subagent_type: "general-purpose",
74+
prompt: "Generate 5 example <message-type> messages in $TICKET_DIR/examples/. 3 conforming to HL7v2 2.5, 2 conforming to HL7v2 2.8.2. Use the hl7v2-info skill. One message per file."
75+
})
76+
```
77+
5. Commit (`git add $TICKET_DIR && git commit`).
78+
6. Tell the user: "Ticket created at `$TICKET_DIR`. Invoke the skill again to run Phase 2 (requirements)." Stop.
3279
33-
## Pipeline
80+
---
3481
35-
### Step 1Ticket setup
82+
## Phase 2Requirements
3683
37-
- Create `ai/tickets/converter-skill-tickets/<ticket-name>/` (unless the user pointed to an existing folder).
38-
- Create `ticket.md` with a `# Goal` section describing the request.
39-
- Create an `examples/` subdirectory. Ask the user to put real example messages there. If they say they have none, spawn a sub-agent with this prompt to generate them:
84+
Spawn the requirements agent. The prompt lives at `.claude/skills/hl7v2-to-fhir-pipeline/requirements-prompt.md` — read it, substitute `<the-ticket-name>` with the ticket slug, pass as prompt:
4085
41-
```
42-
Generate 5 example <message-type> messages in ai/tickets/converter-skill-tickets/<ticket-name>/examples/:
43-
- 3 conforming to HL7v2 2.5
44-
- 2 conforming to HL7v2 2.8.2
45-
Use the hl7v2-info skill. Each example in its own file.
46-
```
86+
```
87+
Agent({
88+
description: "Write requirements for <slug>",
89+
subagent_type: "general-purpose",
90+
prompt: <contents of requirements-prompt.md with slug substituted>
91+
})
92+
```
4793
48-
- Record the `examples/` path in the ticket. Commit.
94+
When the agent returns, commit the ticket changes. Tell the user: "Requirements added to `$TICKET_DIR/ticket.md`. Invoke the skill again to run Phase 3 (plan)." Stop.
4995
50-
### Step 2 — Requirements
96+
---
5197
52-
Spawn a sub-agent with the prompt in `requirements-prompt.md`. It writes a `# Requirements` section into `ticket.md`. Commit.
98+
## Phase 3 — Plan
5399
54-
### Step 3 — Plan
100+
Invoke `/plan` via the Skill tool:
55101
56-
Call `/plan`. Point it at `ticket.md`. It explores the codebase, discusses the approach with the user, and appends `# Implementation Plan` to the ticket — leaving `# Requirements` intact. Commit.
102+
```
103+
Skill({ skill: "plan", args: "$TICKET_DIR/ticket.md" })
104+
```
57105
58-
### Step 4 — Execute
106+
`/plan` talks with the user and appends `# Implementation Plan` to `ticket.md`. When it returns, commit. Tell the user: "Plan ready. Invoke the skill again to run Phase 4 (execute)." Stop.
59107
60-
Call `/work`. It executes the plan one task at a time, with an independent review after each task.
108+
---
61109
62-
When `/work` reports the final task complete and the user approves, the pipeline is done. Tell the user they can test the functionality in the UI.
110+
## Phase 4 — Execute
63111
64-
## Resumption
112+
Invoke `/work`:
65113
66-
If the user points to an existing ticket folder, detect current state and jump in:
114+
```
115+
Skill({ skill: "work", args: "$TICKET_DIR/ticket.md" })
116+
```
67117
68-
| State of `ticket.md` | Resume at |
69-
|---|---|
70-
| No `# Requirements` | Step 2 |
71-
| Has `# Requirements`, no `# Implementation Plan` | Step 3 |
72-
| Has `# Implementation Plan` with unchecked tasks | Step 4 |
73-
| All tasks checked | Ask the user what they want next |
118+
`/work` runs tasks one at a time with review between each. When all tasks are checked off and the user has approved, tell them: "Converter ready. Test via the UI at http://localhost:3000." Stop.

.claude/skills/plan/SKILL.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ If the ticket already has upstream sections (e.g. `# Requirements` from a prior
1616
## Process
1717

1818
1. If the request is ambiguous, ask only the questions that genuinely block exploration. Many questions are better deferred to step 3.
19-
2. Explore the codebase — spawn an Explore agent for unfamiliar areas. Read `CLAUDE.md`, find related patterns, understand constraints.
19+
2. Explore the codebase. Read `CLAUDE.md` directly. For unfamiliar areas, spawn an Explore agent via the **Agent tool**:
20+
```
21+
Agent({
22+
description: "Explore <area>",
23+
subagent_type: "Explore",
24+
prompt: "Find <what you need>. Focus: <files or patterns>. Report under 200 words."
25+
})
26+
```
2027
3. Present findings and propose the implementation shape. Confirm with the user before writing tasks.
2128
4. Write the plan using the structure below.
2229

.claude/skills/work/SKILL.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,22 @@ Execute a plan file at `ai/tickets/YYYY-MM-DD-*.md` produced by `/plan`. The fil
1717

1818
If the task is the last one and the user approves, move the plan file to `ai/tickets/completed/`.
1919

20-
## Review agent prompt
20+
## Review agent spawn
21+
22+
Use the **Agent tool** (not the Skill tool — this is a sub-agent running `ai-review`, not you invoking it yourself). Substitute `[N]` and `[plan-file-path]`:
2123

2224
```
23-
Use skill ai-review to review implementation of Task [N] from [plan-file-path]. Think hard. The changes are uncommitted. Return your review output; do not change any files.
25+
Agent({
26+
description: "Review Task [N]",
27+
subagent_type: "general-purpose",
28+
prompt: "Use skill ai-review to review implementation of Task [N] from [plan-file-path]. Think hard. The changes are uncommitted. Return your review output; do not change any files."
29+
})
2430
```
2531

26-
Replace `[N]` and `[plan-file-path]`. If the user specified codex for reviews, spawn it with:
32+
If the user specified codex for reviews, instead run via the **Bash tool**:
2733

28-
```
29-
codex exec --model gpt-5.3-codex --sandbox workspace-write --full-auto <prompt>
34+
```sh
35+
codex exec --model gpt-5.3-codex --sandbox workspace-write --full-auto "<prompt>"
3036
```
3137

3238
## Rules

CLAUDE.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,6 @@ Referenced constantly when diagnosing errors. Full details: `docs/developer-guid
9898
- `sending_error` — FHIR bundle submission to Aidbox failed; auto-retried 3 times
9999
- `deferred` — manually set via `POST /defer/:id` when resolution needs external input; eligible for retry via `POST /mark-for-retry/:id`
100100

101-
## Known Sender Issues & Preprocessors
102-
103-
**swap-in1-date-fields** — Handles reversed IN1-12/IN1-13 (Plan Effective Date / Plan Expiration Date). Some senders send start >= end; preprocessor swaps them only when detected. Logs warning. Applied to ADT-A01 and ADT-A08. Masks sender error — escalate if persistent.
104-
105101
## US Core demographic extension runtime note
106102

107103
If `profileConformance.implementationGuides` enables US Core (`hl7.fhir.us.core`), PID-10/PID-22 mapping adds `us-core-race` / `us-core-ethnicity` on Patient. Aidbox must have the US Core package loaded and CodeSystem `urn:oid:2.16.840.1.113883.6.238` available (seeded in `init-bundle.json`), or Patient writes fail with terminology-binding errors.

init-bundle.json

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,88 @@
472472
}
473473
}
474474
},
475+
{
476+
"request": {
477+
"method": "PUT",
478+
"url": "StructureDefinition/coverage-period-as-sent-start"
479+
},
480+
"resource": {
481+
"resourceType": "StructureDefinition",
482+
"id": "coverage-period-as-sent-start",
483+
"url": "http://example.org/coverage-period-as-sent-start",
484+
"name": "CoveragePeriodAsSentStart",
485+
"title": "Coverage Period As-Sent Start",
486+
"status": "active",
487+
"kind": "complex-type",
488+
"abstract": false,
489+
"context": [{ "type": "element", "expression": "Period" }],
490+
"type": "Extension",
491+
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Extension",
492+
"derivation": "constraint",
493+
"differential": {
494+
"element": [
495+
{
496+
"id": "Extension",
497+
"path": "Extension",
498+
"short": "Original as-sent Coverage.period.start before autoswap",
499+
"definition": "Set when the IN1 Coverage converter detected reversed IN1-12/IN1-13 dates and autoswapped them to satisfy FHIR invariant per-1. Records the start value as originally received from the sender."
500+
},
501+
{
502+
"id": "Extension.url",
503+
"path": "Extension.url",
504+
"fixedUri": "http://example.org/coverage-period-as-sent-start"
505+
},
506+
{
507+
"id": "Extension.value[x]",
508+
"path": "Extension.value[x]",
509+
"min": 1,
510+
"type": [{ "code": "date" }]
511+
}
512+
]
513+
}
514+
}
515+
},
516+
{
517+
"request": {
518+
"method": "PUT",
519+
"url": "StructureDefinition/coverage-period-as-sent-end"
520+
},
521+
"resource": {
522+
"resourceType": "StructureDefinition",
523+
"id": "coverage-period-as-sent-end",
524+
"url": "http://example.org/coverage-period-as-sent-end",
525+
"name": "CoveragePeriodAsSentEnd",
526+
"title": "Coverage Period As-Sent End",
527+
"status": "active",
528+
"kind": "complex-type",
529+
"abstract": false,
530+
"context": [{ "type": "element", "expression": "Period" }],
531+
"type": "Extension",
532+
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Extension",
533+
"derivation": "constraint",
534+
"differential": {
535+
"element": [
536+
{
537+
"id": "Extension",
538+
"path": "Extension",
539+
"short": "Original as-sent Coverage.period.end before autoswap",
540+
"definition": "Set when the IN1 Coverage converter detected reversed IN1-12/IN1-13 dates and autoswapped them to satisfy FHIR invariant per-1. Records the end value as originally received from the sender."
541+
},
542+
{
543+
"id": "Extension.url",
544+
"path": "Extension.url",
545+
"fixedUri": "http://example.org/coverage-period-as-sent-end"
546+
},
547+
{
548+
"id": "Extension.value[x]",
549+
"path": "Extension.value[x]",
550+
"min": 1,
551+
"type": [{ "code": "date" }]
552+
}
553+
]
554+
}
555+
}
556+
},
475557
{
476558
"request": {
477559
"method": "PUT",

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"test:unit": "bun test",
2626
"test:integration": "bun test --test-root ./test/integration --preload ./test/integration/preload.ts --max-concurrency=1",
2727
"test:smoke": "bun test --test-root ./test/integration --preload ./test/integration/preload.ts --max-concurrency=1 --test-name-pattern \"smoke: \"",
28-
"reset-integration-aidbox": "bun scripts/reset-integration-aidbox.ts"
28+
"reset-integration-aidbox": "bun scripts/reset-integration-aidbox.ts",
29+
"truncate-aidbox": "bun scripts/truncate-aidbox.ts"
2930
},
3031
"type": "module",
3132
"dependencies": {

0 commit comments

Comments
 (0)