name: zbeam-howto-spec description: "Generates HowTo JSON-LD spec from any page with a howToProceed block. Run after content-writer or page-updater. Produces a Copilot-ready SchemaFactory spec."
Z-Beam HowTo Spec Generator
Reads the howToProceed block from any Z-Beam page and produces two outputs:
- A validated HowTo JSON-LD spec (human-readable, for review)
- A Copilot implementation directive for
SchemaFactory
This skill is reusable across all page types — application pages, location pages,
and any future page type that carries a howToProceed block. It does not hardcode domain
assumptions; it reads whatever structure is present and validates it.
Input required: slug of any page with a howToProceed block.
Output: data/specs/howto-spec-[slug]-[YYYY-MM-DD].md
When to run
Run after zbeam-content-writer or zbeam-page-updater completes on any page. Specifically:
- After any application page is written or updated (application pages require
howToProceed) - After any location page that has a
howToProceedblock - When Copilot needs to implement or update
SchemaFactoryHowTo generation
Step 1: Read the page
import yaml, glob, json, os
from datetime import date
# Resolve page path — check applications, locations, then materials
slug = '[slug]'
candidates = [
f'frontmatter/applications/{slug}.yaml',
f'frontmatter/locations/{slug}.yaml',
f'frontmatter/materials/{slug}.yaml',
]
page_path = next((p for p in candidates if os.path.exists(p)), None)
if not page_path:
print(f'ERROR: No YAML found for slug {slug}')
exit(1)
page = yaml.safe_load(open(page_path))
content_type = page.get('contentType', 'unknown')
howto = page.get('howToProceed')
if not howto:
print(f'SKIP: {slug} has no howToProceed block — nothing to spec')
exit(0)
print(f'Page: {slug} (contentType: {content_type})')
print(f'howToProceed title: {howto.get("title", "[missing]")}')
print(f'Steps found: {len(howto.get("steps", []))}')
Step 2: Validate the howToProceed block
Before generating the spec, validate the block can produce valid HowTo schema:
issues = []
steps = howto.get('steps', [])
# Title required
if not howto.get('title'):
issues.append('FAIL: howToProceed.title missing')
# Steps: must be exactly 3
if len(steps) != 3:
issues.append(f'FAIL: {len(steps)} steps found — HowTo requires exactly 3')
# Each step must be an object with name + text
for i, step in enumerate(steps):
if isinstance(step, str):
issues.append(f'FAIL: steps[{i}] is a plain string — must be object with name + text. '
f'Run zbeam-content-writer to rewrite the howToProceed block.')
elif isinstance(step, dict):
if not step.get('name'):
issues.append(f'FAIL: steps[{i}].name missing')
if not step.get('text'):
issues.append(f'FAIL: steps[{i}].text missing')
text_words = len(step.get('text', '').split())
if text_words < 20:
issues.append(f'WARN: steps[{i}].text is {text_words} words — HowTo steps under 20 words '
f'are unlikely to generate rich results')
if issues:
print('Validation issues:')
for issue in issues:
print(f' {issue}')
if any(i.startswith('FAIL') for i in issues):
print('\nBlocking failures found — fix howToProceed block before generating spec.')
exit(1)
else:
print('howToProceed block is valid — proceeding to spec generation.')
Step 3: Generate the HowTo JSON-LD spec
# Build the HowTo JSON-LD object
page_url = page.get('fullPath', f'/{content_type}s/{slug}')
base_url = 'https://www.z-beam.com'
howto_jsonld = {
"@context": "https://schema.org",
"@type": "HowTo",
"name": howto.get('title', f'How to Proceed with {page.get("name", slug)}'),
"description": howto.get('description', ''),
"url": f"{base_url}{page_url}",
"step": []
}
for i, step in enumerate(steps):
howto_jsonld["step"].append({
"@type": "HowToStep",
"position": i + 1,
"name": step.get('name', f'Step {i + 1}'),
"text": step.get('text', '')
})
# Optional: add totalTime if the page has duration data
# (skip for now — requires explicit duration field not yet in schema)
print(json.dumps(howto_jsonld, indent=2))
Step 4: Assess HowTo rich result eligibility
Google's criteria for HowTo rich results (as of 2026):
- Step count: 2+ steps (we require 3 — compliant)
- Each step: must have
nameandtext(enforced in Step 2) textper step: should be instructional, not promotional- Total: page must be publicly crawlable (not blocked by robots.txt)
Scan each step text for promotional language that disqualifies rich results:
promotional_signals = [
'best in class', 'industry-leading', 'trusted by', 'guaranteed',
'contact us today', 'call now', 'click here', 'learn more'
]
for i, step in enumerate(steps):
text = step.get('text', '').lower()
flagged = [p for p in promotional_signals if p in text]
if flagged:
print(f'WARN: steps[{i}] contains promotional language that may disqualify rich results: {flagged}')
print(f' Rewrite to be instructional (what to do + outcome) rather than promotional.')
Step 5: Write the Copilot SchemaFactory spec
Write a spec document Copilot can implement directly — no ambiguity, no guesswork:
os.makedirs('data/specs', exist_ok=True)
spec_path = f'data/specs/howto-spec-{slug}-{date.today()}.md'
spec_content = f"""# HowTo Schema Spec — {slug}
Generated: {date.today()}
Content type: {content_type}
Page URL: {base_url}{page_url}
## Task for Copilot
Implement HowTo JSON-LD generation in `SchemaFactory` for pages that have a
`howToProceed` block. This is a **new generator**, not a modification to an existing one.
### Files to modify
1. `app/utils/schema/SchemaFactory.ts` (or equivalent schema generation file)
2. Possibly `app/utils/schema/generators/HowToGenerator.ts` (new file, preferred)
### Implementation spec
**Trigger condition**: generate HowTo JSON-LD when `page.howToProceed` exists AND
`page.howToProceed.steps` is a non-empty array of objects with `name` and `text`.
Do NOT generate HowTo if `steps` is an array of plain strings — those are legacy format
and should not produce schema until the page is updated to the name+text format.
**Schema shape**:
```json
{json.dumps(howto_jsonld, indent=2)}
What to check before emitting:
function canEmitHowTo(page: PageData): boolean {{
const howto = page.howToProceed;
if (!howto?.steps?.length) return false;
// Only emit if steps are objects (name+text), not plain strings
return howto.steps.every(
(step) => typeof step === 'object' && step.name && step.text
);
}}
Where to inject the JSON-LD:
Follow the existing pattern for other schema generators — add the HowTo block to the
<script type="application/ld+json"> output for the page. The HowTo script tag is
separate from the existing BusinessSchema / ServiceSchema tags (not merged into one tag).
FAQPage and Dataset schema removal: Both FAQPage and Dataset schema types have been
deprecated from this project. Remove their generators from SchemaFactory entirely.
faq.items[]content remains in YAML and on the page — readers and AI citation systems still consume it. Only the JSON-LD generation is removed.- HowTo schema (from
howToProceed) replaces FAQPage as the primary structured data signal on application pages. - Dataset schema is also removed — do not add Dataset generators elsewhere.
// REMOVE these generators from SchemaFactory:
// - FAQPageSchema / FAQPage generator
// - DatasetSchema / Dataset generator
// ADD this generator:
// - HowToSchema / HowTo generator (spec above)
Testing
After implementation, verify with:
npm run validate:rich-results # existing rich results validator
Check that the HowTo JSON-LD appears in the page source for any application page with a
valid howToProceed block, and does NOT appear for pages without one or with string-only steps.
Definition of done
- HowTo JSON-LD emitted for
{slug}at{base_url}{page_url} - HowTo not emitted for pages with string-only steps
- HowTo and FAQPage coexist without errors
-
validate:rich-resultspasses - No TypeScript errors (
npm run type-check) """
with open(spec_path, 'w') as f: f.write(spec_content)
print(f'Spec written to: {spec_path}')
---
## Step 6: Output summary
Report:
- Spec path
- HowTo step count and whether all pass rich result eligibility
- Validation issues found (if any)
- Next step: send spec to Copilot for SchemaFactory implementation
---
## What this skill does NOT do
- Does not modify the YAML page
- Does not implement SchemaFactory — it writes the spec for Copilot to implement
- Does not modify YAML page content — faq.items remain in the YAML file even though the JSON-LD FAQPage generator is deprecated
- Does not run if `howToProceed` is missing — use `zbeam-content-writer` first
- Does not handle plain-string steps — requires the name+text format enforced by the writer