Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# DAK Skill Library — local config. Copy to .env (never commit .env).
#
# LLM features (authoring, error interpretation, classification):
# Leave blank → LLM steps skipped, structural validation still runs.
# Billed to YOUR account, not WHO.
#
# ─── API KEY ────────────────────────────────────────────────────────────
# Get a key from your LLM provider:
# OpenAI: https://platform.openai.com/api-keys → starts with sk-
# Anthropic: https://console.anthropic.com/settings/keys → starts with sk-ant-
# Google AI: https://aistudio.google.com/app/apikey → starts with AI...
# Azure: Azure Portal → your OpenAI resource → Keys
#
# LiteLLM routes to the right provider based on the model name below.
# Leave blank to skip all LLM steps (structural validation still runs).
DAK_LLM_API_KEY=

# ─── MODEL ──────────────────────────────────────────────────────────────
# LiteLLM model identifier. Format: [provider/]model-name
#
# Popular options (as of 2025):
# OpenAI: gpt-4o, gpt-4o-mini, gpt-4-turbo, o1-mini
# Anthropic: claude-sonnet-4-20250514, claude-3-5-haiku-20241022
# Google: gemini/gemini-2.0-flash, gemini/gemini-1.5-pro
# Azure: azure/your-deployment-name
#
# Master list of all supported models and provider prefixes:
# https://docs.litellm.ai/docs/providers
#
# Default: gpt-4o (requires OpenAI key above)
DAK_LLM_MODEL=gpt-4o

# ─── IG PUBLISHER (optional) ───────────────────────────────────────────
# Custom FHIR terminology server. Leave blank for default (tx.fhir.org).
DAK_TX_SERVER=
46 changes: 46 additions & 0 deletions .github/skills/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# DAK Skill Library — Local Development Image
# Mirrors ghbuild.yml CI environment exactly.
# Base: hl7fhir/ig-publisher-base (Jekyll, Ruby, Java 17, Node.js)

FROM hl7fhir/ig-publisher-base:latest

LABEL org.opencontainers.image.title="DAK Skill Library"
LABEL org.opencontainers.image.source="https://github.com/WorldHealthOrganization/smart-base"

# Python packages — identical to ghbuild.yml
# --break-system-packages is required because the base image uses Debian's
# externally-managed Python; a venv is unnecessary inside a disposable container.
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv \
&& ln -sf /usr/bin/python3 /usr/bin/python \
&& pip3 install --break-system-packages \
"GitPython>=3.1.40" \
"PyYAML>=6.0" \
"requests>=2.28.0" \
"lxml" \
"litellm>=1.0.0" \
"pdfplumber" \
"pandas" \
&& rm -rf /var/lib/apt/lists/*

# SUSHI — identical to ghbuild.yml
RUN npm install -g fsh-sushi

# IG Publisher jar — pre-baked so local runs don't need network
# Override: -v /local/publisher.jar:/app/publisher.jar
RUN mkdir -p /app/input-cache \
&& curl -L \
https://github.com/HL7/fhir-ig-publisher/releases/latest/download/publisher.jar \
-o /app/input-cache/publisher.jar

# DAK skill library
COPY . /app/skills/

# Workspace — mount IG repo here: -v $(pwd):/workspace
WORKDIR /workspace

ENV PUBLISHER_JAR=/app/input-cache/publisher.jar
ENV DAK_IG_ROOT=/workspace

ENTRYPOINT ["python3", "/app/skills/cli/dak_skill.py"]
CMD ["--help"]
120 changes: 120 additions & 0 deletions .github/skills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# DAK Skill Library

The DAK Skill Library provides AI-assisted and structural validation tools
for authoring WHO Digital Adaptation Kit (DAK) content.

## Quick Start

### Local Development (Docker)

```bash
# 1. Build the image
docker build -t dak-skill .github/skills/

# 2. Copy environment template
cp .env.example .env
# Edit .env to add your LLM API key (optional — structural validation works without it)

# 3. Run skills
docker compose -f .github/skills/docker-compose.yml run --rm validate
docker compose -f .github/skills/docker-compose.yml run --rm validate-ig
docker compose -f .github/skills/docker-compose.yml run --rm import-bpmn
docker compose -f .github/skills/docker-compose.yml run --rm shell

# Shortcut alias:
alias dak='docker compose -f .github/skills/docker-compose.yml run --rm'
dak validate
dak import-bpmn
```

### CI (GitHub Actions)

Skills run automatically via GitHub Actions workflows:

| Trigger | Workflow | What it does |
|---|---|---|
| Issue opened/edited | `classify-issue.yml` | Auto-labels issues with `content:L1/L2/L3/translation` |
| Label `content:L1` | `skill-l1-review.yml` | L1 guideline review (placeholder) |
| Label `content:L2` | `skill-l2-dak.yml` | L2 DAK content authoring |
| Label `content:L3` | `skill-l3-review.yml` | L3 adaptation review (placeholder) |
| Label `content:translation` | `skill-translation.yml` | Translation management (placeholder) |
| PR comment `/validate` | `pr-validate-slash.yml` | Structural + IG validation |

## One-Time Repository Setup

```
1. Create labels (Issues → Labels → New label):
content:L1 #0075ca "WHO source guideline content"
content:L2 #e4e669 "DAK FHIR assets"
content:L3 #d73a4a "Implementation adaptations"
content:translation #0e8a16 "Translation of any content layer"
(Label definitions also stored in .github/skills/labels/*.json for reference.)

2. Add secret (Settings → Secrets and variables → Actions → New repository secret):
DAK_LLM_API_KEY = sk-...

3. Add variable (Settings → Secrets and variables → Variables → New variable):
DAK_LLM_MODEL = gpt-4o (or gpt-4o-mini to reduce cost)
See .env.example for the full list of supported model identifiers,
or https://docs.litellm.ai/docs/providers for the master list.

4. Build local Docker image (optional, for local development):
docker build -t dak-skill .github/skills/
```

## Security Model

- **API keys MUST NOT appear** in dispatch inputs, issue comments, PR comments, or any user-visible UI
- Two legitimate locations only: **repo secret** (CI) or **local `.env` file** (Docker/local)
- LLM steps skip gracefully when no key present — non-LLM validation always runs
- **Zero WHO infrastructure cost; zero WHO AI cost**

### Graceful Degradation

| Skill | No key | With key |
|---|---|---|
| BPMN structure validation | ✅ runs | ✅ runs |
| Swimlane ↔ ActorDef validation | ✅ runs | ✅ runs |
| IG Publisher build/validate | ✅ runs | ✅ runs |
| Issue classification | keyword fallback | LLM classification |
| LLM BPMN authoring | ⚠️ skipped | ✅ runs |
| LLM error interpretation | ⚠️ skipped | ✅ runs |

## Directory Structure

```
.github/skills/
├── Dockerfile # FROM hl7fhir/ig-publisher-base — mirrors CI
├── docker-compose.yml # Service aliases: validate, author, import, shell
├── README.md # This file
├── skills_registry.yaml # All registered skills
├── cli/
│ └── dak_skill.py # CLI entry point
├── common/
│ ├── llm_utils.py # LLM helpers — thin wrappers around LiteLLM
│ ├── prompt_loader.py # load_prompt() — .md templates with {variable}
│ ├── ig_errors.py # FATAL/ERROR/WARNING/INFORMATION format
│ ├── fsh_utils.py # FSH file utilities
│ ├── ig_publisher_iface.py
│ └── prompts/ # Shared prompt templates
├── bpmn_author/ # Author/edit BPMN
├── bpmn_import/ # Import BPMN → FSH, validate lanes
├── ig_publisher/ # IG Publisher validation and build
├── dak_authoring/ # Issue classification and L2 content review/authoring
│ ├── actions/
│ │ ├── classify_issue_action.py # Keyword + LLM issue classifier
│ │ └── dak_authoring_action.py # L2 content review skill (→ content:L2 label)
│ └── prompts/
├── labels/ # GitHub label definitions (JSON, for reference)
├── l1_review/ # (placeholder v0.2)
├── l3_review/ # (placeholder v0.3)
└── translation/ # (placeholder v0.3)
```

## LLM Provider

LLM features use [LiteLLM](https://github.com/BerriAI/litellm) (MIT License) —
a well-maintained multi-provider library (OpenAI, Anthropic, Google, etc.)
with 20k+ GitHub stars. The `common/llm_utils.py` module adds only DAK-specific
environment variable bridging and JSON-extraction helpers on top of LiteLLM;
there is no custom LLM facade to maintain.
Empty file.
Empty file.
73 changes: 73 additions & 0 deletions .github/skills/bpmn_author/actions/bpmn_author_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
BPMN Author action — creates or edits BPMN files via LLM,
then validates the result structurally.

Environment variables:
DAK_LLM_API_KEY — LLM API key (optional; LLM steps skipped if absent)
DAK_LLM_MODEL — LLM model name (default: gpt-4o)
GITHUB_TOKEN — GitHub API token for issue/PR interaction
ISSUE_NUMBER — GitHub issue number
ISSUE_TITLE — Issue title
ISSUE_BODY — Issue body text
"""

import os
import sys
from pathlib import Path

# Ensure the skills root is on sys.path
_SKILLS_ROOT = Path(__file__).resolve().parent.parent.parent
if str(_SKILLS_ROOT) not in sys.path:
sys.path.insert(0, str(_SKILLS_ROOT))

from common.ig_errors import format_issues, has_errors
from bpmn_author.validators.bpmn_xml_validator import validate_bpmn_xml
from bpmn_author.validators.swimlane_validator import validate_swimlanes


def main() -> None:
api_key = os.environ.get("DAK_LLM_API_KEY", "")
if not api_key:
print("⚠️ DAK_LLM_API_KEY not set — LLM step skipped (structural validation still runs)")
sys.exit(0)

from common.llm_utils import dak_completion
from common.prompt_loader import load_prompt

issue_title = os.environ.get("ISSUE_TITLE", "")
issue_body = os.environ.get("ISSUE_BODY", "")
model = os.environ.get("DAK_LLM_MODEL", "gpt-4o")

# Load additional prompt components required by the create_or_edit_bpmn template
_prompts_dir = _SKILLS_ROOT / "common" / "prompts"
dak_bpmn_constraints = (_prompts_dir / "dak_bpmn_constraints.md").read_text(encoding="utf-8")
bpmn_xml_schema = (_prompts_dir / "bpmn_xml_schema.md").read_text(encoding="utf-8")
actor_context = (_prompts_dir / "actor_context.md").read_text(encoding="utf-8")

prompt = load_prompt(
"bpmn_author", "create_or_edit_bpmn",
user_request=f"{issue_title}\n\n{issue_body}",
current_bpmn="(none — creating new BPMN)",
Comment on lines +47 to +50
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The load_prompt call provides only user_request and current_bpmn, but the create_or_edit_bpmn.md template also contains {dak_bpmn_constraints}, {bpmn_xml_schema}, and {actor_context} placeholders. Because _SafeDict is used (in prompts.py), these unresolved placeholders will appear as literal text (e.g., {dak_bpmn_constraints}) in the prompt sent to the LLM, effectively leaving three important sections empty. The constraints and schema content from common/prompts/dak_bpmn_constraints.md, common/prompts/bpmn_xml_schema.md, and common/prompts/actor_context.md should be loaded and passed as variables to fill these placeholders.

Suggested change
prompt = load_prompt(
"bpmn_author", "create_or_edit_bpmn",
user_request=f"{issue_title}\n\n{issue_body}",
current_bpmn="(none — creating new BPMN)",
# Load additional prompt components required by the create_or_edit_bpmn template
dak_bpmn_constraints_path = _SKILLS_ROOT / "common" / "prompts" / "dak_bpmn_constraints.md"
bpmn_xml_schema_path = _SKILLS_ROOT / "common" / "prompts" / "bpmn_xml_schema.md"
actor_context_path = _SKILLS_ROOT / "common" / "prompts" / "actor_context.md"
with dak_bpmn_constraints_path.open(encoding="utf-8") as f:
dak_bpmn_constraints = f.read()
with bpmn_xml_schema_path.open(encoding="utf-8") as f:
bpmn_xml_schema = f.read()
with actor_context_path.open(encoding="utf-8") as f:
actor_context = f.read()
prompt = load_prompt(
"bpmn_author",
"create_or_edit_bpmn",
user_request=f"{issue_title}\n\n{issue_body}",
current_bpmn="(none — creating new BPMN)",
dak_bpmn_constraints=dak_bpmn_constraints,
bpmn_xml_schema=bpmn_xml_schema,
actor_context=actor_context,

Copilot uses AI. Check for mistakes.
dak_bpmn_constraints=dak_bpmn_constraints,
bpmn_xml_schema=bpmn_xml_schema,
actor_context=actor_context,
)

print(f"🤖 Requesting BPMN from {model}...")
bpmn_xml = dak_completion(prompt, api_key=api_key, model=model)

# Validate the generated BPMN
issues = validate_bpmn_xml(bpmn_xml, filename="generated.bpmn")
issues.extend(validate_swimlanes(bpmn_xml, filename="generated.bpmn"))

print(format_issues(issues))

if has_errors(issues):
print("❌ Generated BPMN has validation errors.")
sys.exit(1)

print("✅ Generated BPMN passed structural validation.")


if __name__ == "__main__":
main()
33 changes: 33 additions & 0 deletions .github/skills/bpmn_author/prompts/create_or_edit_bpmn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Create or Edit BPMN

You are a BPMN 2.0 authoring assistant for WHO Digital Adaptation Kits (DAKs).

## Your Task

{user_request}

## Constraints

{dak_bpmn_constraints}

## BPMN XML Schema

{bpmn_xml_schema}

## Actor Context

{actor_context}

## Current BPMN (if editing)

```xml
{current_bpmn}
```

## Instructions

1. Generate valid BPMN 2.0 XML following the constraints above.
2. Use meaningful lane IDs that can serve as FSH instance identifiers.
3. Ensure every task is assigned to exactly one lane.
4. Include sequence flows connecting all elements.
5. Return ONLY the BPMN XML — no explanation, no markdown fences.
31 changes: 31 additions & 0 deletions .github/skills/bpmn_author/prompts/validate_bpmn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Validate BPMN

Review the following BPMN XML for compliance with WHO DAK constraints.

## BPMN XML

```xml
{bpmn_xml}
```

## Validation Results (structural)

{validation_results}

## Instructions

Summarize the validation findings. For each issue:
1. Explain what is wrong and why it matters for DAK compliance.
2. Suggest a specific fix.

If there are no issues, confirm the BPMN is valid.
Return your analysis as JSON:
```json
{{
"valid": true/false,
"summary": "...",
"issues": [
{{"code": "...", "severity": "...", "message": "...", "fix": "..."}}
]
}}
```
23 changes: 23 additions & 0 deletions .github/skills/bpmn_author/skills.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# bpmn_author skill
name: bpmn_author
version: "0.1.0"
description: Author and edit standard BPMN 2.0 XML for DAK business processes

commands:
- name: create-bpmn
description: Create a new BPMN file from a natural-language description
requires_llm: true
- name: edit-bpmn
description: Edit an existing BPMN file based on instructions
requires_llm: true
- name: validate-bpmn
description: Validate BPMN structure and DAK constraints (no LLM needed)
requires_llm: false

validators:
- bpmn_xml_validator
- swimlane_validator

prompts:
- create_or_edit_bpmn
- validate_bpmn
Empty file.
Loading
Loading