Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
7252166
Initial plan
Copilot Mar 5, 2026
fc517b2
Scaffold DAK AI Skill Library infrastructure
Copilot Mar 5, 2026
dd6bca0
Fix BPMN namespace check verbosity and keyword classifier false posit…
Copilot Mar 5, 2026
03effd5
Address code review feedback
Copilot Mar 5, 2026
d03e79f
Fix critical security issue in pr-validate-slash.yml
Copilot Mar 5, 2026
6020568
Replace SmartLLMFacade with direct LiteLLM usage via llm_utils.py
Copilot Mar 6, 2026
e169208
chore: update translation templates (.pot) [2026-03-06 02:35 UTC]
github-actions[bot] Mar 6, 2026
4051f58
Merge branch 'main' into copilot/create-skills-infrastructure-bpmn
litlfred Mar 6, 2026
3f3f4be
Move labels/ to .github/skills/labels/, improve .env.example docs, cl…
Copilot Mar 6, 2026
7d0ac86
chore: update translation templates (.pot) [2026-03-06 13:29 UTC]
github-actions[bot] Mar 6, 2026
efb28e4
Merge upstream main: incorporate .pot translation templates and trans…
Copilot Mar 6, 2026
ac83148
Update .github/skills/common/ig_errors.py
litlfred Mar 6, 2026
e91966d
fix: use --tx for Python runner, -tx for Java JAR in ig_publisher_ifa…
Copilot Mar 6, 2026
c87fd25
fix: address automated review feedback — missing __init__.py, prompt …
Copilot Mar 6, 2026
057c33a
fix: correct format_issues typo in ig_errors.py docstring
Copilot Mar 6, 2026
4dc6239
Merge upstream main: resolve run_ig_publisher.py conflict (take upstr…
Copilot Mar 6, 2026
b7f744d
Update .github/skills/bpmn_author/actions/bpmn_author_action.py
litlfred Mar 23, 2026
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
Comment thread
litlfred marked this conversation as resolved.

# 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.
89 changes: 89 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,89 @@
"""
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")

# 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)",
Comment thread
litlfred marked this conversation as resolved.
dak_bpmn_constraints=dak_bpmn_constraints,
bpmn_xml_schema=bpmn_xml_schema,
actor_context=actor_context,
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