Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,9 @@ validate-jtbd:
@echo ""
@files=$$(find . \
-type d \( -name .git -o -name .claude \) -prune -o \
-type f -path "*/skills/*.md" -print | sort); \
-type f -path "*/skills/*/SKILL.md" -print | sort); \
if [ -z "$$files" ]; then \
echo "$(YELLOW)No JTBD files found under */skills/*.md$(NC)"; \
echo "$(YELLOW)No JTBD files found under */skills/*/SKILL.md$(NC)"; \
exit 0; \
fi; \
passed=0; failed=0; \
Expand Down Expand Up @@ -506,7 +506,7 @@ pre-commit-hook:
jtbd_ok=true; \
jtbd_files=$$(find . \
-type d \( -name .git -o -name .agents -o -name .claude -o -name portal \) -prune -o \
-type f -path "*/skills/*.md" -print | sort); \
-type f -path "*/skills/*/SKILL.md" -print | sort); \
if [ -n "$$jtbd_files" ]; then \
for file in $$jtbd_files; do \
if ! python3 scripts/build/validate_jtbd.py "$$file" . > /dev/null 2>&1; then \
Expand Down
14 changes: 3 additions & 11 deletions scripts/build/validate_jtbd.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,17 +317,10 @@ def validate(self) -> bool:
else:
print(f" ✅ Steps are numbered sequentially (1-{len(step_headers)})")

# Step-based skill: at least 1 YAML step required
if len(steps) == 0:
self.errors.append(
"No YAML step blocks found! "
"Jobs must have at least 1 step defined in a YAML code block with 'api' and 'operationId' fields."
)
print(f" ❌ {self.errors[-1]}")
self.print_summary()
return False

print(" ✅ At least 1 YAML step is defined")
print(" ℹ️ No YAML step blocks found")
else:
print(" ✅ At least 1 YAML step is defined")

# Check that number of headers matches number of YAML blocks
if step_headers and steps:
Expand Down Expand Up @@ -418,7 +411,6 @@ def main():
print(" ✓ At least 1 step header is defined (## Step 1:, ## Step 2:, etc.)")
print(" ✓ Step headers are numbered sequentially")
print(" ✓ Step header count matches YAML block count")
print(" ✓ At least 1 job step is defined (YAML block)")
print(" ✓ Each step has a valid YAML code block with required fields")
print(" ✓ API URN points to an existing folder")
print(" ✓ OperationId exists in the referenced API spec")
Expand Down
21 changes: 16 additions & 5 deletions scripts/portal_generator/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,25 @@ def discover_skills(repo_root: Path) -> Tuple[Dict[str, List[Dict]], Dict[str, L

print("🔍 Scanning for skills...")

for skill_dir in sorted(skills_dir.iterdir()):
if not skill_dir.is_dir():
# Collect SKILL.md files at skills/<slug>/SKILL.md and
# skills/<category>/<slug>/SKILL.md (one level of nesting).
skill_files: List[Path] = []
for entry in sorted(skills_dir.iterdir()):
if not entry.is_dir():
continue

skill_file = skill_dir / 'SKILL.md'
if not skill_file.exists():
direct = entry / 'SKILL.md'
if direct.exists():
skill_files.append(direct)
continue
for nested in sorted(entry.iterdir()):
if not nested.is_dir():
continue
nested_skill = nested / 'SKILL.md'
if nested_skill.exists():
skill_files.append(nested_skill)

for skill_file in skill_files:
skill_dir = skill_file.parent
skill_data = parse_skill(skill_file)
if not skill_data:
continue
Expand Down
10 changes: 10 additions & 0 deletions scripts/portal_generator/parsers/skill_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,15 @@ def parse_skill(skill_path: Path) -> Dict[str, Any]:
else:
display_md = post.content

# Fallback rendering: when a skill uses none of the recognized structured
# headings, render its full markdown body so the page isn't empty.
has_structured_content = bool(
step_details or overview or prerequisites or starting_point
or completion_checklist or what_youve_built or next_steps
or tips or troubleshooting or related_jobs or additional_resources
)
raw_body_html = '' if has_structured_content else _md.render(post.content)

# Tags from frontmatter: accept a YAML list or a comma-separated string.
raw_tags = post.get('tags')
tag_names: List[str] = []
Expand Down Expand Up @@ -256,6 +265,7 @@ def parse_skill(skill_path: Path) -> Dict[str, Any]:
'troubleshooting_html': _md.render(troubleshooting) if troubleshooting else '',
'related_jobs_list': _extract_related_jobs(related_jobs) if related_jobs else [],
'additional_resources_html': _md.render(additional_resources) if additional_resources else '',
'raw_body_html': raw_body_html,
}

except Exception as e:
Expand Down
Loading
Loading