Move all three skills into skills/; add root README; fix paths

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VasanthandClaude Sonnet 5.5 committed 2026-10-16 17:04:38 +05:30
1 parent 312742cfef
commit d95e5d452d
84 files changed
+97 -31

No files matched your search

+1
View File
@@ -4,3 +4,4 @@ node_modules/
.venv/ .venv/
.claude/worktrees/ .claude/worktrees/
rpa-run-tab/logs/ rpa-run-tab/logs/
skills/rpa-run-tab/logs/
+1 -1
View File
@@ -1,6 +1,6 @@
# RPA SKILLS # RPA SKILLS
Shared Claude skills for building RPA dashboards: `rpa-dashboard-no-ai`, `rpa-dashboard-ai`, and `rpa-run-tab` (Tebra-style Run tab). See `PLAN.md`. Shared Claude skills for RPA bots, all in `skills/`: `rpa-run-tab` (Tebra-style Run tab), `rpa-dashboard-no-ai` and `rpa-dashboard-ai`. See `README.md` and `PLAN.md`.
## Agent skills ## Agent skills
+7 -7
View File
@@ -73,12 +73,12 @@ Build both in `D:\RPA SKILLS\<skill-name>\`. The user installs them into Claude
## Status: what already exists ## Status: what already exists
``` ```
rpa-dashboard-no-ai/ skills/rpa-dashboard-no-ai/
templates/dashboard.html DONE: generic page, works in Qt (QWebChannel) or web (polling) templates/dashboard.html DONE: generic page, works in Qt (QWebChannel) or web (polling)
templates/contract.md DONE: event/JSON contract + Tebra lessons templates/contract.md DONE: event/JSON contract + Tebra lessons
templates/bridge.py DONE: minimal PyQt6 host + subprocess log tailing templates/bridge.py DONE: minimal PyQt6 host + subprocess log tailing
templates/reference/ DONE: tebra_dashboard.html, tebra_bridge_part1/2.py (raw extracts) templates/reference/ DONE: tebra_dashboard.html, tebra_bridge_part1/2.py (raw extracts)
rpa-dashboard-ai/ skills/rpa-dashboard-ai/
templates/reference/web/ DONE: Superbill index.css, DESIGN.md, bands.ts, theme.tsx, types.ts, api.ts, templates/reference/web/ DONE: Superbill index.css, DESIGN.md, bands.ts, theme.tsx, types.ts, api.ts,
components (BrandHeader, Sidebar, StatsBar, Donut, SplitPane, components (BrandHeader, Sidebar, StatsBar, Donut, SplitPane,
RunPanel, ReviewPane, RoiTab, PdfPane, Tooltip, icons) RunPanel, ReviewPane, RoiTab, PdfPane, Tooltip, icons)
@@ -86,24 +86,24 @@ rpa-dashboard-ai/
``` ```
## TODO for the implement skill ## TODO for the implement skill
1. **Fix the lint error in `rpa-dashboard-no-ai/templates/bridge.py:15`.** `STEPS = {{STEPS_PY}}` is not valid Python, so the lint hook flags it (F821). Change it to `STEPS = ["{{STEP_1}}", "{{STEP_2}}"]` or similar. 1. **Fix the lint error in `skills/rpa-dashboard-no-ai/templates/bridge.py:15`.** `STEPS = {{STEPS_PY}}` is not valid Python, so the lint hook flags it (F821). Change it to `STEPS = ["{{STEP_1}}", "{{STEP_2}}"]` or similar.
2. **Write `rpa-dashboard-no-ai/SKILL.md`.** 2. **Write `skills/rpa-dashboard-no-ai/SKILL.md`.**
- Frontmatter `name` and `description`. The trigger should cover "RPA dashboard", "automation dashboard", "bot dashboard", and "without AI". - Frontmatter `name` and `description`. The trigger should cover "RPA dashboard", "automation dashboard", "bot dashboard", and "without AI".
- The workflow above. - The workflow above.
- Point to `templates/*`. - Point to `templates/*`.
3. **Write `rpa-dashboard-ai/SKILL.md`.** 3. **Write `skills/rpa-dashboard-ai/SKILL.md`.**
- Trigger: "AI RPA dashboard", "extraction dashboard", "LLM/OCR review dashboard". - Trigger: "AI RPA dashboard", "extraction dashboard", "LLM/OCR review dashboard".
- The workflow above. - The workflow above.
- Web path: React + Vite + Tailwind v4, adapted from `reference/web`. - Web path: React + Vite + Tailwind v4, adapted from `reference/web`.
- Desktop path: copy the no-AI `dashboard.html`, then add the AI sections. - Desktop path: copy the no-AI `dashboard.html`, then add the AI sections.
4. **Write `templates/themes.md` in both skills.** Content is in the Theme presets section above. 4. **Write `templates/themes.md` in both skills.** Content is in the Theme presets section above.
5. **Write `rpa-dashboard-ai/templates/contract.md`.** It extends the no-AI contract with: 5. **Write `skills/rpa-dashboard-ai/templates/contract.md`.** It extends the no-AI contract with:
- `Run{job_id,status:pending|queued|running|done|failed,model,pages_done,pages_total,summary:{green,amber,red,unscored}}` - `Run{job_id,status:pending|queued|running|done|failed,model,pages_done,pages_total,summary:{green,amber,red,unscored}}`
- `ReviewRow{row_id,field,value,band,score,reason,verified,corrected_value}` - `ReviewRow{row_id,field,value,band,score,reason,verified,corrected_value}`
- `RoiRun{model,input_tokens,output_tokens,thinking_tokens,total_cost}` - `RoiRun{model,input_tokens,output_tokens,thinking_tokens,total_cost}`
- Endpoints: `PATCH /review/{row_id}`, `POST /runs/{id}/approve` (server gate), `GET /roi`. - Endpoints: `PATCH /review/{row_id}`, `POST /runs/{id}/approve` (server gate), `GET /roi`.
- Polling: 1.5–2s only while a run is busy. - Polling: 1.5–2s only while a run is busy.
6. **Write `rpa-dashboard-ai/templates/ai-sections.html`.** Plain HTML/CSS/JS versions of the band legend, the review split pane and the ROI tiles, for the desktop path. 6. **Write `skills/rpa-dashboard-ai/templates/ai-sections.html`.** Plain HTML/CSS/JS versions of the band legend, the review split pane and the ROI tiles, for the desktop path.
7. **Strip project-specific bits from the AI reference files.** Remove ECW/Tebra project names, SPB_* tables and patient/ticket wording, or mark them as such. **Never** ship real patient data (PHI), internal IPs or keys. The reference copies have already been checked for these, but check again after editing. 7. **Strip project-specific bits from the AI reference files.** Remove ECW/Tebra project names, SPB_* tables and patient/ticket wording, or mark them as such. **Never** ship real patient data (PHI), internal IPs or keys. The reference copies have already been checked for these, but check again after editing.
8. **Verify.** Open `dashboard.html` in a browser using sample data. Check light, dark and narrow width. Load each SKILL.md and make sure every path it references exists. 8. **Verify.** Open `dashboard.html` in a browser using sample data. Check light, dark and narrow width. Load each SKILL.md and make sure every path it references exists.
+30
View File
@@ -0,0 +1,30 @@
# RPA SKILLS
Claude skills for building the dashboard of an RPA bot. All three live in [`skills/`](skills/).
| Skill | Use it when | What it builds |
|---|---|---|
| [`rpa-run-tab`](skills/rpa-run-tab/README.md) | You have a PyQt6 desktop bot and only need a Run tab, in about 5 minutes. | The Tebra-style Run tab: trigger button, Excel input, progress, checkpoints, live log, dark mode. |
| [`rpa-dashboard-no-ai`](skills/rpa-dashboard-no-ai/SKILL.md) | The bot has no AI step. | A full dashboard: run panel, overview, report, optional sections (checkpoints, admin, login). Desktop (PyQt) or web. |
| [`rpa-dashboard-ai`](skills/rpa-dashboard-ai/SKILL.md) | The bot uses an LLM or OCR. | Everything in the no-AI skill, plus confidence bands, review-and-approve (the server refuses approval until fields are verified), and a cost/ROI page. |
## Install
Copy the skill folders you need from `skills/` into `~/.claude/skills/`.
`rpa-dashboard-ai` reuses files from `rpa-dashboard-no-ai`, so install the two dashboard skills together, side by side.
## Use
In your bot's project, ask Claude for what you want, for example *"add the run tab"* or *"build an RPA dashboard"*.
If the project already has a frontend, the dashboard skills report it and ask whether to keep its look or restyle it.
## Repo layout
| Path | What it is |
|---|---|
| `skills/` | The three skills |
| `tests/` | Template tests, page checks (Playwright) and skill evals. See [`tests/README.md`](tests/README.md). |
| `PLAN.md`, `GLOSSARY.md`, `docs/` | Background, domain terms and decisions |
| `.scratch/` | Spec and tickets |
Run all tests: `uv run --with pytest --with playwright pytest tests`
@@ -8,4 +8,4 @@ The bot runs as a **separate process** with no Qt. The Run tab starts it with `-
**Why:** this comes from Tebra Auto Post. UI automation inside a Qt worker thread was unreliable (bug-094). A windowed PyInstaller child's stdout pipe is block-buffered, so log lines arrived in late bursts; reading the log file fixed that. Tagging lines saves each project from writing a table of phrase→friendly-text rules. **Why:** this comes from Tebra Auto Post. UI automation inside a Qt worker thread was unreliable (bug-094). A windowed PyInstaller child's stdout pipe is block-buffered, so log lines arrived in late bursts; reading the log file fixed that. Tagging lines saves each project from writing a table of phrase→friendly-text rules.
**Conflict:** `rpa-dashboard-no-ai/templates/bot_runner.py` reads `stdout=PIPE`, which is the approach Tebra dropped. Fix it there, or point it at the Run tab skill's runner. **Conflict:** `skills/rpa-dashboard-no-ai/templates/bot_runner.py` reads `stdout=PIPE`, which is the approach Tebra dropped. Fix it there, or point it at the Run tab skill's runner.
File renamed without changes.
File renamed without changes.
+35
View File
@@ -0,0 +1,35 @@
# rpa-run-tab
A Claude skill that adds a **Tebra-style Run tab** to any desktop RPA bot (PyQt6) in about 5 minutes.
## What you get
- **Trigger button** with three states: Upload a file / Trigger automation / Running...
- **Excel input card:** drop zone, sheet tabs, a 200-row preview with passwords masked, and a Sample input download
- **Progress bar** with an elapsed timer, a step list and a friendly live log
- **Checkpoint boxes** with time limits. A stuck checkpoint is killed and shown in red.
- **Endings:** green Completed, amber "N of M failed", red Failed or Stuck
- **Dark mode** and a close-while-running guard. The bot runs in its own process.
## Install
Copy this folder to `~/.claude/skills/rpa-run-tab/`.
## Use
In your bot project, ask Claude: *"add the run tab"*. The skill:
1. Scans your bot. It never runs it.
2. Asks three things: **colour** (Tebra Sky, TVL Teal, or your hex), **checkpoints** (names and time limits), and **Excel input or none**.
3. Builds `run_tab/` and adds a few `rt.checkpoint()` / `rt.ui()` lines to your bot.
4. Checks the result with a fake bot (`python run_tab/run_tab.py --selfcheck`).
Start it with `python run_tab/run_tab.py`.
## Files
| File | What it is |
|---|---|
| `SKILL.md` | Steps Claude follows |
| `templates/run_tab.html` | The page (layout fixed, colours themable) |
| `templates/run_tab.py` | PyQt6 window and bridge, plus the CONFIG block and `--selfcheck` |
| `templates/runner.py` | Starts the bot, reads its log, handles stuck checkpoints and the ending |
| `templates/rt.py` | Helpers the bot calls |
| `templates/fake_bot.py` | Fake bot used by the self-check |
| `templates/palette.py` | Colour presets or a custom hex, with a contrast check |
| `docs/adr/` | Why it works this way |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1 -1
View File
@@ -72,4 +72,4 @@ uv run --with pytest --with playwright pytest tests
passwords, checkpoints driving steps/boxes/progress, green/amber/red endings, the no-input passwords, checkpoints driving steps/boxes/progress, green/amber/red endings, the no-input
mode, the dark toggle and no horizontal scroll, each in light, dark and narrow mode. The mode, the dark toggle and no horizontal scroll, each in light, dark and narrow mode. The
Python side covers line parsing, endings, the sample workbook and `palette.py --write`. Python side covers line parsing, endings, the sample workbook and `palette.py --write`.
The end-to-end fake-bot check lives in the skill itself: `python rpa-run-tab/templates/run_tab.py --selfcheck`. The end-to-end fake-bot check lives in the skill itself: `python skills/rpa-run-tab/templates/run_tab.py --selfcheck`.
+1 -1
View File
@@ -202,7 +202,7 @@ def main(fixture):
shutil.copy(pages[0], results / f"{fixture}.dashboard.html") shutil.copy(pages[0], results / f"{fixture}.dashboard.html")
checks["no placeholders left"] = "{{" not in html checks["no placeholders left"] = "{{" not in html
if cfg.get("brand"): if cfg.get("brand"):
spec = importlib.util.spec_from_file_location("palette", ROOT / "rpa-dashboard-no-ai/templates/palette.py") spec = importlib.util.spec_from_file_location("palette", ROOT / "skills/rpa-dashboard-no-ai/templates/palette.py")
palette = importlib.util.module_from_spec(spec) palette = importlib.util.module_from_spec(spec)
spec.loader.exec_module(palette) spec.loader.exec_module(palette)
t = palette.derive(cfg["brand"]) t = palette.derive(cfg["brand"])
+1 -1
View File
@@ -16,7 +16,7 @@ import pytest
from page_check_common import FAKE_BRIDGE, MODES, TEST_FILL, WIRED, open_page from page_check_common import FAKE_BRIDGE, MODES, TEST_FILL, WIRED, open_page
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
TEMPLATE = ROOT / "rpa-dashboard-no-ai" / "templates" / "dashboard.html" TEMPLATE = ROOT / "skills" / "rpa-dashboard-no-ai" / "templates" / "dashboard.html"
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
+1 -1
View File
@@ -23,7 +23,7 @@ import pytest
from page_check_common import MODES, TEST_FILL, load_stub, open_page from page_check_common import MODES, TEST_FILL, load_stub, open_page
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
TEMPLATE = ROOT / "rpa-dashboard-ai" / "templates" / "web" TEMPLATE = ROOT / "skills" / "rpa-dashboard-ai" / "templates" / "web"
TEST_FILL = {**TEST_FILL, "APP_NAME": "Test Extractor", "API_BASE": "/api"} TEST_FILL = {**TEST_FILL, "APP_NAME": "Test Extractor", "API_BASE": "/api"}
RECORD_ACTIONS = """ RECORD_ACTIONS = """
window.__calls = []; window.__calls = [];
+2 -2
View File
@@ -22,8 +22,8 @@ import pytest
from page_check_common import FAKE_BRIDGE, MODES, TEST_FILL, WIRED, load_stub, open_page from page_check_common import FAKE_BRIDGE, MODES, TEST_FILL, WIRED, load_stub, open_page
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
NO_AI = ROOT / "rpa-dashboard-no-ai" / "templates" / "dashboard.html" NO_AI = ROOT / "skills" / "rpa-dashboard-no-ai" / "templates" / "dashboard.html"
AI = ROOT / "rpa-dashboard-ai" / "templates" AI = ROOT / "skills" / "rpa-dashboard-ai" / "templates"
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
+1 -1
View File
@@ -9,7 +9,7 @@ import pytest
from page_check_common import FAKE_BRIDGE, MODES, open_page from page_check_common import FAKE_BRIDGE, MODES, open_page
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
TEMPLATE = ROOT / "rpa-run-tab" / "templates" / "run_tab.html" TEMPLATE = ROOT / "skills" / "rpa-run-tab" / "templates" / "run_tab.html"
CFG = {"title": "Test Bot", "sub": "Page check", "input": True, "inputHint": "Test workbook", CFG = {"title": "Test Bot", "sub": "Page check", "input": True, "inputHint": "Test workbook",
"sample": True, "darkKey": "test_dark", "sample": True, "darkKey": "test_dark",
"checkpoints": [{"name": "Sign in", "max": 300}, {"name": "Process rows", "max": 0}, "checkpoints": [{"name": "Sign in", "max": 300}, {"name": "Process rows", "max": 0},
+1 -1
View File
@@ -3,7 +3,7 @@ import importlib.util
import re import re
from pathlib import Path from pathlib import Path
BANDS = Path(__file__).resolve().parents[1] / "rpa-dashboard-ai" / "templates" / "web" / "bands.py" BANDS = Path(__file__).resolve().parents[1] / "skills" / "rpa-dashboard-ai" / "templates" / "web" / "bands.py"
spec = importlib.util.spec_from_file_location("bands", BANDS) spec = importlib.util.spec_from_file_location("bands", BANDS)
bands = importlib.util.module_from_spec(spec) bands = importlib.util.module_from_spec(spec)
spec.loader.exec_module(bands) spec.loader.exec_module(bands)
+1 -1
View File
@@ -7,7 +7,7 @@ from pathlib import Path
import pytest import pytest
RUNNER = Path(__file__).resolve().parents[1] / "rpa-dashboard-no-ai" / "templates" / "bot_runner.py" RUNNER = Path(__file__).resolve().parents[1] / "skills" / "rpa-dashboard-no-ai" / "templates" / "bot_runner.py"
@pytest.fixture @pytest.fixture
+1 -1
View File
@@ -5,7 +5,7 @@ import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
DETECT = Path(__file__).resolve().parents[1] / "rpa-dashboard-no-ai" / "templates" / "detect_frontend.py" DETECT = Path(__file__).resolve().parents[1] / "skills" / "rpa-dashboard-no-ai" / "templates" / "detect_frontend.py"
spec = importlib.util.spec_from_file_location("detect_frontend", DETECT) spec = importlib.util.spec_from_file_location("detect_frontend", DETECT)
detect_frontend = importlib.util.module_from_spec(spec) detect_frontend = importlib.util.module_from_spec(spec)
spec.loader.exec_module(detect_frontend) spec.loader.exec_module(detect_frontend)
+1 -1
View File
@@ -4,7 +4,7 @@ import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
PALETTE = Path(__file__).resolve().parents[1] / "rpa-dashboard-no-ai" / "templates" / "palette.py" PALETTE = Path(__file__).resolve().parents[1] / "skills" / "rpa-dashboard-no-ai" / "templates" / "palette.py"
spec = importlib.util.spec_from_file_location("palette", PALETTE) spec = importlib.util.spec_from_file_location("palette", PALETTE)
palette = importlib.util.module_from_spec(spec) palette = importlib.util.module_from_spec(spec)
spec.loader.exec_module(palette) spec.loader.exec_module(palette)
+1 -1
View File
@@ -5,7 +5,7 @@ from pathlib import Path
import pytest import pytest
SRC = Path(__file__).resolve().parents[1] / "rpa-dashboard-ai" / "templates" / "web" / "pricing.py" SRC = Path(__file__).resolve().parents[1] / "skills" / "rpa-dashboard-ai" / "templates" / "web" / "pricing.py"
spec = importlib.util.spec_from_file_location("pricing", SRC) spec = importlib.util.spec_from_file_location("pricing", SRC)
pricing = importlib.util.module_from_spec(spec) pricing = importlib.util.module_from_spec(spec)
spec.loader.exec_module(pricing) spec.loader.exec_module(pricing)
+2 -2
View File
@@ -6,8 +6,8 @@ from pathlib import Path
import pytest import pytest
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
SKILLS = [ROOT / "rpa-dashboard-ai", ROOT / "rpa-dashboard-no-ai"] SKILLS = [ROOT / "skills" / "rpa-dashboard-ai", ROOT / "skills" / "rpa-dashboard-no-ai"]
AI_REFERENCE = ROOT / "rpa-dashboard-ai" / "templates" / "reference" AI_REFERENCE = ROOT / "skills" / "rpa-dashboard-ai" / "templates" / "reference"
FORBIDDEN = { FORBIDDEN = {
"internal IP": r"\b(10\.\d{1,3}|192\.168|172\.(1[6-9]|2\d|3[01]))\.\d{1,3}\.\d{1,3}\b", "internal IP": r"\b(10\.\d{1,3}|192\.168|172\.(1[6-9]|2\d|3[01]))\.\d{1,3}\.\d{1,3}\b",
"Google API key": r"AIza[0-9A-Za-z_-]{30,}", "Google API key": r"AIza[0-9A-Za-z_-]{30,}",
+1 -1
View File
@@ -4,7 +4,7 @@ import sys
from pathlib import Path from pathlib import Path
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
T = ROOT / "rpa-run-tab" / "templates" T = ROOT / "skills" / "rpa-run-tab" / "templates"
sys.path.insert(0, str(T)) sys.path.insert(0, str(T))
import runner # noqa: E402 import runner # noqa: E402
+1 -1
View File
@@ -10,7 +10,7 @@ from pathlib import Path
import pytest import pytest
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
TEMPLATES = ROOT / "rpa-dashboard-no-ai" / "templates" TEMPLATES = ROOT / "skills" / "rpa-dashboard-no-ai" / "templates"
spec = importlib.util.spec_from_file_location("sections", TEMPLATES / "sections.py") spec = importlib.util.spec_from_file_location("sections", TEMPLATES / "sections.py")
sections = importlib.util.module_from_spec(spec) sections = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sections) spec.loader.exec_module(sections)
+6 -6
View File
@@ -10,9 +10,9 @@ ROOT = Path(__file__).resolve().parents[1]
HOOK_RULES = "E9,F63,F7,F82,F811" # same rule set as the PostToolUse lint hook HOOK_RULES = "E9,F63,F7,F82,F811" # same rule set as the PostToolUse lint hook
@pytest.mark.parametrize("template", ["rpa-dashboard-no-ai/templates/bridge.py", @pytest.mark.parametrize("template", ["skills/rpa-dashboard-no-ai/templates/bridge.py",
"rpa-dashboard-no-ai/templates/bot_runner.py", "skills/rpa-dashboard-no-ai/templates/bot_runner.py",
"rpa-dashboard-ai/templates/desktop/ai_bridge.py"]) "skills/rpa-dashboard-ai/templates/desktop/ai_bridge.py"])
def test_template_passes_lint_hook_unfilled(template): def test_template_passes_lint_hook_unfilled(template):
ruff = shutil.which("ruff") or pytest.skip("ruff not on PATH") ruff = shutil.which("ruff") or pytest.skip("ruff not on PATH")
r = subprocess.run([ruff, "check", "--select", HOOK_RULES, "--no-cache", str(ROOT / template)], r = subprocess.run([ruff, "check", "--select", HOOK_RULES, "--no-cache", str(ROOT / template)],
@@ -21,7 +21,7 @@ def test_template_passes_lint_hook_unfilled(template):
def test_tebra_sky_preset_is_the_template_default(): def test_tebra_sky_preset_is_the_template_default():
templates = ROOT / "rpa-dashboard-no-ai" / "templates" templates = ROOT / "skills" / "rpa-dashboard-no-ai" / "templates"
themes = (templates / "themes.md").read_text(encoding="utf-8") themes = (templates / "themes.md").read_text(encoding="utf-8")
sky = themes.split("## 1. Tebra Sky")[1].split("```css\n")[1].split("```")[0] sky = themes.split("## 1. Tebra Sky")[1].split("```css\n")[1].split("```")[0]
assert sky in (templates / "dashboard.html").read_text(encoding="utf-8") assert sky in (templates / "dashboard.html").read_text(encoding="utf-8")
@@ -29,13 +29,13 @@ def test_tebra_sky_preset_is_the_template_default():
@pytest.mark.parametrize("skill", ["rpa-dashboard-no-ai", "rpa-dashboard-ai"]) @pytest.mark.parametrize("skill", ["rpa-dashboard-no-ai", "rpa-dashboard-ai"])
def test_skill_description_fits_the_1024_char_limit(skill): def test_skill_description_fits_the_1024_char_limit(skill):
text = (ROOT / skill / "SKILL.md").read_text(encoding="utf-8") text = (ROOT / "skills" / skill / "SKILL.md").read_text(encoding="utf-8")
description = re.search(r"^description: (.+)$", text, re.MULTILINE)[1] description = re.search(r"^description: (.+)$", text, re.MULTILINE)[1]
assert len(description) <= 1024, len(description) assert len(description) <= 1024, len(description)
def test_ai_skill_references_files_that_exist(): def test_ai_skill_references_files_that_exist():
skill = ROOT / "rpa-dashboard-ai" skill = ROOT / "skills" / "rpa-dashboard-ai"
text = (skill / "SKILL.md").read_text(encoding="utf-8") text = (skill / "SKILL.md").read_text(encoding="utf-8")
paths = re.findall(r"`((?:templates/|<no-ai>)[\w./-]+)`", text) paths = re.findall(r"`((?:templates/|<no-ai>)[\w./-]+)`", text)
assert len(paths) > 5 assert len(paths) > 5