ftl/skills/content-reader/SKILL.md
AsciiDoc reader agent for the FTL lab validator. Reads a showroom .adoc module file, extracts executable code blocks (role="execute"), classifies each step by automation type, and outputs a structured task report for the solve-writer and validate-writer agents.
npx skillsauth add rhpds/rhdp-skills-marketplace ftl:content-readerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reads a Showroom .adoc module file and produces a structured task report classifying every student step. This feeds directly into ftl:solve-writer and ftl:validate-writer.
Self-contained. No ECC, no external tools required.
MODULE_FILE — absolute path to the .adoc fileAGV_COMMON — path to common.yaml (or "none")LAB_TYPE — ocp-tenant | ocp-dedicated | vm-rhelCLUSTER_CONTEXT (optional) — namespace patterns, service URLs from live clusterRead the full .adoc file. Extract:
Before classifying steps, read ALL image:: references in the .adoc file. These screenshots show what the student sees at each step — critical for UI-step classification and for generating resilient intent descriptions.
image::genaistudio-mcpservers.png[MCP servers page]
image::authorize-mcp.png[Authorize MCP servers]
For each image referenced:
content/modules/ROOT/assets/images/<name>.pngStore as vision context per step:
IMAGE: genaistudio-mcpservers.png
Vision analysis:
- Page: RHOAI Gen AI Studio → AI asset endpoints → MCP servers tab
- Table shows: "Kubernetes-MCP-Server" and "Slack-MCP-Server", both Active
- Both rows have checkboxes, both checked
- Toolbar button reads EXACTLY: "▶ Try in Playground (2)"
- Button is in the top toolbar row between filter controls and column selector
This vision analysis drives intent descriptions (not selector guesses) and helps the self-healing loop when UI changes.
AsciiDoc executable blocks have role="execute" — these are commands the student runs in the terminal:
[source,bash,role="execute"]
----
oc login --server={openshift_api_url}
----
[source,bash,role="execute",subs="attributes"]
----
oc project {user}-coolstore
----
For each block, capture:
bash, python, yaml, etc.----{attr} with known values (from AgV ocp4_workload_showroom_content_* or userdata keys like {user}, {guid})== or === heading above itEvery section heading (==, ===) + its prose content that does NOT have role="execute" blocks — these may contain UI steps.
Look for _attributes.adoc, vars.adoc, or attribute definitions at the top of the file:
:user: user1
:openshift_api_url: https://api.cluster...
Use these to substitute {attr} placeholders in code blocks.
For each extracted block or prose section, assign a classification. Apply the priority ladder strictly:
| Classification | Signal | Automate with |
|---|---|---|
| k8s | kubectl/oc command creating/updating a k8s resource | kubernetes.core.k8s |
| k8s-check | kubectl/oc command reading a k8s resource | kubernetes.core.k8s_info |
| oc-cli | oc command with no direct k8s equivalent (login, project, new-app, etc.) | ansible.builtin.shell + oc |
| api | curl to REST API, Python HTTP call | ansible.builtin.uri |
| tcp-check | Port/connectivity check | ansible.builtin.wait_for |
| exec-into-pod | Command that must run INSIDE a pod (NetworkPolicy, localhost URLs) | kubernetes.core.k8s_exec |
| shell-workspace | Command inside DevSpaces/workspace pod | kubernetes.core.k8s_exec into workspace pod |
| ui-playwright | Browser interaction with NO api/oc equivalent | Playwright script |
| skip | Informational only / visual verification / pure AI interaction | No automation — document reason |
When a step is described in prose (no role="execute") or the code doesn't map to a known command:
Is there an oc CLI equivalent?
YES → classify as oc-cli
NO → Is there a REST API endpoint?
YES → classify as api
NO → Is this a k8s resource operation?
YES → classify as k8s
NO → Is this a browser interaction that Playwright CAN reproduce?
YES → classify as ui-playwright (only if reproducible deterministically)
NO → classify as skip
skip examples that cannot be automated:
ui-playwright examples (only these qualify):
Output as a structured list. This is consumed verbatim by ftl:solve-writer and ftl:validate-writer.
MODULE: module-02-developer-lightspeed
FILE: /path/to/04-module-02-developer-lightspeed.adoc
LAB_TYPE: ocp-tenant
ATTRIBUTE_SUBSTITUTIONS:
user: llmuser-{guid}
devworkspace_namespace: user-{guid}-devworkspace
mta_namespace: user-{guid}-mta
EXECUTABLE_BLOCKS:
- id: block-01
section: "Exercise 1: Open DevSpaces"
language: bash
role: execute
raw: "oc login --server={openshift_api_url} --username={user} ..."
substituted: "oc login --server={{ openshift_api_url }} --username={{ user }} ..."
classification: oc-cli
ansible_module: ansible.builtin.shell (oc)
notes: "Login must happen inside workspace pod — exec into devworkspace pod"
reference_images: []
- id: block-02
section: "Exercise 2: Configure LiteLLM"
language: yaml
role: execute
raw: |
models:
OpenAI: &active
environment:
OPENAI_API_KEY: {litellm_virtual_key}
substituted: |
models:
OpenAI: &active
environment:
OPENAI_API_KEY: {{ litellm_virtual_key }}
classification: shell-workspace
ansible_module: kubernetes.core.k8s_exec
notes: "Write to /projects/kai-coolstore/settings/provider-settings.yaml — check if exists first"
PROSE_STEPS:
- id: prose-01
section: "Exercise 2: Review AI Suggestions"
description: "Student reviews AI-generated code fixes and clicks Accept or Reject for each"
classification: skip
reason: "AI code review acceptance is a visual/interactive step with no deterministic API equivalent"
reference_images: []
intent: null # skip — no automation possible
- id: prose-02
section: "Exercise 3: Open MTA Analysis Results"
description: "Student opens MTA web console and reviews issue list"
classification: skip
reason: "Visual review of analysis results — validator checks that analysis completed, not that results were reviewed"
VALIDATION_TARGETS:
- "DevSpaces workspace pod running in {{ student_user }}-devworkspace"
- "provider-settings.yaml exists at /projects/kai-coolstore/settings/provider-settings.yaml"
- "LiteLLM endpoint reachable with virtual key"
SKIP_SUMMARY:
- prose-01: "AI code acceptance — manual review required"
- prose-02: "Visual analysis review — not automatable"
STATS:
total_blocks: 8
automatable: 5
ui_playwright: 0
skip: 3
coverage: 62%
For every ui-playwright classified step, generate an intent description — not a CSS selector, but a semantic description of what the student needs to do. This drives the self-healing loop when UI changes.
ui-playwright step intent format:
action: "Click | Fill | Select | Navigate"
target: "<what to interact with — human-readable>"
context: "<which page/section this happens on>"
visual_confirmation: "<what the UI looks like after success — from screenshot>"
exact_labels_visible: ["▶ Try in Playground (2)", "MCP servers", ...]
Example (from genaistudio-mcpservers.png vision analysis):
intent:
action: Click
target: "The button that opens the playground for the selected MCP servers"
context: "RHOAI Gen AI Studio → AI asset endpoints → MCP servers tab"
visual_confirmation: "Page navigates to playground with chat interface visible"
exact_labels_visible:
- "▶ Try in Playground (2)" ← exact text from screenshot
- "MCP servers" ← tab label
When UI version changes:
Before handing off to solve-writer, flag anything uncertain:
⚠️ AMBIGUITIES (resolve before solve-writer runs):
1. block-04: Command uses {mta_hub_url} but no matching attribute found in .adoc.
Check common.yaml for ocp4_workload_tenant_mta_url or equivalent.
→ Defaulting to: http://mta-hub.{{ student_user }}-mta.svc.cluster.local:8080
2. prose-03: "Click the Configure GenAI Settings button" — is there an API equivalent?
The MTA extension stores config at /projects/kai-coolstore/settings/provider-settings.yaml
→ Classifying as shell-workspace (write the file directly)
List ambiguities clearly. If resolution is clear from context (AgV catalog or known patterns), resolve it. If not, flag for human input.
Return ONLY the structured task report + ambiguities. No prose explanation. The orchestrator passes this directly to solve-writer and validate-writer.
tools
Writes validate.yml playbooks using the validation_check Ansible plugin. Takes the content-reader task report and solve-writer actions as input, producing checks that verify student progress without manual steps or navigation instructions.
tools
Writes solve.yml playbooks from the structured task report produced by ftl:content-reader. Uses the automation priority ladder (k8s_exec → k8s → uri → wait_for → Playwright) to generate Ansible tasks that replicate what the student does in the lab.
development
Pushes solve.yml and validate.yml to a live RHDP showroom, restarts the pod, and runs the full test cycle (fresh validate → solve → validate again → idempotency check). Reports pass/fail per task with full output for debugging.
testing
This skill should be used when the user asks to "verify my workshop content", "review my lab module", "check my Showroom content", "validate my AsciiDoc module", "quality check my demo", "review my workshop for Red Hat standards", or "run a content review on my lab".