Workshop Series
- Workshop 1: Prompt-First Product Design for a Tagalog Learning App
- Workshop 2: Educational-First Dev Tips for a Tagalog Learning App
- Workshop 3: Deep-Dive Development Flow for a Tagalog Learning App
- Workshop 4: Localize a Tagalog Learning App to Chinese Variants
- Workshop 5: Grammar and Pronunciation Enrichment Pipeline
- Workshop 6: Unique and Reviewable Extra Examples
Audience: professional developers who want an end-to-end AI-assisted engineering flow
Duration: 2 hours
Primary AWS AI service: Kiro
Project output: a deterministic static-site generator that expands sentence banks into Tagalog learning cards, renders HTML, validates output, and packages a release artifact.
Workshop Summary

This workshop walks developers through a complete file-first build pipeline for a Tagalog learning site. Participants use Kiro to define steering, model article data, expand sentence banks, generate helper content, render static HTML, validate outputs, and package releases. The focus is repeatable engineering flow, where structured data and checks keep generated learning materials predictable, traceable, and shippable for reviewers later.
Workshop objective
This workshop focuses on development flow. Developers build a file-first content pipeline: article metadata, sentence banks, context expansion, grammar helpers, pronunciation helpers, HTML rendering, validation checks, and release packaging. Kiro is used for steering, specs, task planning, implementation support, test generation, hooks, documentation, and release review.
2-hour agenda
| Time | Module | Developer outcome |
|---|---|---|
| 0–10 min | Kiro workspace | Steering and spec folders prepared |
| 10–25 min | Content contract | Article and sentence-bank schema designed |
| 25–45 min | Deterministic expansion | 10 base phrases expand into 40 cards |
| 45–65 min | Helper functions | Grammar and pronunciation helpers added |
| 65–90 min | Static rendering | HTML pages generated from structured data |
| 90–105 min | Validation | Card count, summary, and forbidden-reference checks added |
| 105–115 min | Packaging | Zip artifact created |
| 115–120 min | Review | Kiro-assisted release checklist produced |
Step 1 — Create Kiro steering for a file-first generator
Kiro prompt sample
Create steering docs for a file-first static-site generator.
The product is a Tagalog learning app for AWS Manila Community Day.
Use Python for deterministic generation, HTML output, CSS, and zip packaging.
The source of truth is structured data, not hand-authored pages.
Every generated page must be validated before release.
System design decision
- File-first architecture: The app does not need a runtime backend for the workshop. The technical goal is to show how small source data can become a large, consistent learning site. File-first generation makes every build step visible, inspectable, and easy to explain.
- Deterministic generation over random output: AI can assist with drafts and code, but the published build should be deterministic. Given the same sentence banks and contexts, the generator should produce the same cards and files. This makes review, testing, and debugging practical.
- Validation as a product feature: A generated site can look complete while hiding missing cards or broken mobile rules. Build validation is part of the system design, not an afterthought. The release artifact should include evidence that required conditions were checked.
Code sample — .kiro/steering/static-generator.md
# Static Generator Guidance
This project generates a Tagalog learning site from structured Python data.
Do not hand-author final pages when a renderer can generate them.
## Rules
- Article metadata controls navigation and file names.
- Sentence banks are compact source data.
- Context expansion creates repeated learning cards deterministically.
- HTML output must escape generated text.
- Every article must have exactly 40 cards.
- Release checks must run after generation and before packaging.
- Generated language content requires native-speaker review before production use.
Code explanation
- Business logic: The steering file defines the generator philosophy: structured data, deterministic output, and release checks.
- Code logic: Kiro uses this Markdown guidance when generating Python functions, validation checks, and release tasks.
- Expected result: Kiro suggestions should favor reusable renderers and checks instead of manual HTML duplication.
Step 2 — Generate a Kiro spec for the pipeline
Kiro prompt sample
Create a spec named tagalog-static-generator.
Feature: generate article pages from metadata and sentence banks.
Include requirements, design, data flow, and implementation tasks.
Acceptance criteria: each article has 40 cards, HTML escapes content, mobile CSS exists, and release zip includes generated pages.
System design decision
- Spec describes the pipeline, not only UI: The main engineering value is the transformation from source data to release artifact. The spec should cover data, expansion, rendering, validation, and packaging so developers understand the whole system.
- Acceptance criteria become release checks: “Exactly 40 cards” and “mobile CSS exists” are not subjective. They can be counted and tested. This makes the release process transparent and workshop-friendly.
- Traceable tasks: Kiro tasks break the pipeline into manageable implementation units. Professional developers can assign, review, or automate each task independently in a real team.
Code sample — .kiro/specs/tagalog-static-generator/design.md
# Design
## Data flow
1. Article metadata defines page identity, slug, title, category, and summary.
2. Sentence banks define compact phrase tuples.
3. Context arrays expand each tuple into multiple learning cards.
4. Helper functions attach grammar, examples, and pronunciation.
5. Renderer creates escaped static HTML.
6. Validator checks card counts, mobile CSS marker, summary word counts, and forbidden internal references.
7. Packager writes a zip artifact.
## Key modules
- `content.py`: source article metadata and sentence banks
- `expand.py`: deterministic card expansion
- `helpers.py`: grammar, examples, pronunciation
- `render.py`: HTML rendering
- `validate.py`: release checks
- `build.py`: orchestrates generation and packaging
Code explanation
- Business logic: The design shows how a cultural learning goal becomes a repeatable build system.
- Code logic: Each module has a single responsibility, which makes Kiro-generated implementation easier to review.
- Expected result: Developers can implement the generator in small steps and validate each stage.
Step 3 — Model article metadata and sentence banks
Kiro prompt sample
Create Python source data for a static Tagalog learning site.
Use article metadata with id, slug, title, category, and 20-word summary.
Create a compact sentence bank with English, natural Tagalog, polite Tagalog, friendly Filipino-English, playful Filipino-English, and tone.
System design decision
- Metadata as routing contract: Article metadata controls file names, navigation, page titles, and summaries. Treating metadata as a contract prevents page drift and makes the site easy to regenerate.
- Compact tuple source data: Sentence banks store the smallest reviewed phrase unit. The generator expands context around stable base phrases rather than inventing entirely new output for every card.
- Tone variants in source data: Each base phrase includes natural, polite, friendly, and playful versions. This guarantees the renderer can always show side-by-side tone comparisons without guessing.
Code sample — content.py
ARTICLES = [
{
"id": 1,
"slug": "community-greetings-introductions",
"title": "Community Day: Greetings and Respectful Introductions",
"category": "Community Day",
"summary": "Practice polite greetings names beginner phrases and warm introductions for meeting speakers volunteers students and builders in Manila with confidence today"
}
]
SENTENCE_BANKS = {
1: [
(
"Hello, I am learning Tagalog.",
"Kumusta, nag-aaral ako ng Tagalog.",
"Kumusta po, nag-aaral po ako ng Tagalog.",
"Hello po, learning Tagalog ako.",
"Kumusta, learning Tagalog na ako, all right.",
"friendly beginner introduction"
),
(
"May I ask a question?",
"Puwede ba akong magtanong?",
"Puwede po ba akong magtanong?",
"Can I ask po?",
"Question time na ako, all right?",
"polite workshop request"
)
]
}
CONTEXTS = [
("at morning registration", "sa morning registration"),
("before a workshop starts", "bago magsimula ang workshop"),
("while meeting a volunteer", "habang may nakikilalang volunteer"),
("after a session", "pagkatapos ng session")
]
Code explanation
- Business logic: The data defines what the site teaches and how pages are identified.
- Code logic:
ARTICLESstores page metadata.SENTENCE_BANKSmaps article IDs to phrase tuples.CONTEXTSprovides deterministic expansion situations. - Expected result: Other modules can generate pages and cards without hardcoding content inside render functions.
Step 4 — Expand base phrases into cards
Kiro prompt sample
Create a Python function that expands each base phrase into context-aware cards.
Each card must have number, background, English, natural, polite, friendly, playful, tone, and context fields.
Return exactly 40 cards per article by repeating contexts if needed and trimming overflow.
System design decision
- Controlled expansion: The generator expands context, not meaning. Keeping the base sentence stable helps learners memorize phrasing while still seeing event situations.
- Exact count for predictable pages: The article contract says each page has 40 cards. Exact counts make layout, review workload, and release validation predictable.
- No randomness in published build: Random generation would make review difficult. Deterministic loops ensure that a bug report can be reproduced from the same input data.
Code sample — expand.py
from itertools import cycle
from content import CONTEXTS
def expand_cards(base_sentences, article_id, target_count=40):
cards = []
context_cycle = cycle(CONTEXTS)
while len(cards) < target_count:
for sentence in base_sentences:
if len(cards) >= target_count:
break
english, natural, polite, friendly, playful, tone = sentence
context_en, context_tl = next(context_cycle)
cards.append({
"num": len(cards) + 1,
"background": f"Use this sentence {context_en}. It supports respectful event communication.",
"english": english,
"natural": natural,
"polite": polite,
"friendly": friendly,
"playful": playful,
"tone": tone,
"context_en": context_en,
"context_tl": context_tl,
"article_id": article_id
})
return cards
Code explanation
- Business logic: A small sentence bank becomes a full article deck while preserving the reviewed phrase text.
- Code logic: The function cycles through contexts and base sentences until it reaches
target_count. Each card receives deterministic numbering. - Expected result: Calling
expand_cards(SENTENCE_BANKS[1], 1)returns exactly 40 card dictionaries.
Step 5 — Add grammar and pronunciation helpers
Kiro prompt sample
Create helper functions for grammar breakdown and pronunciation.
If English mentions thank, explain Salamat and po.
If English asks where, explain Saan and ang.
If English asks permission, explain Puwede, ba, and magtanong.
Return beginner-friendly explanations.
System design decision
- Rule-based augmentation: Grammar notes should be consistent and reviewable. Simple rules are easier to inspect than opaque runtime generation.
- Beginner-first explanations: Developers should avoid academic grammar overload. The helper returns short explanations that fit a mobile card.
- Helpers separate enrichment from rendering: Renderers should display content, not decide grammar. Helper functions keep enrichment logic testable and reusable.
Code sample — helpers.py
def grammar_breakdown(card):
english = card["english"].lower()
natural = card["natural"].lower()
if "thank" in english or "salamat" in natural:
return [
("Salamat", "thank you"),
("po", "politeness marker"),
("sa", "for, in, at, or to depending on context")
]
if "where" in english or natural.startswith("saan"):
return [
("Saan", "where"),
("po", "polite marker for respectful questions"),
("ang", "focus marker before the place or thing")
]
if "may i" in english or "question" in english:
return [
("Puwede", "may or can"),
("ba", "question marker"),
("magtanong", "to ask")
]
return [
("po", "polite marker used for respect"),
("ako", "I or me"),
("kayo", "polite or plural you")
]
def pronunciation_guide(tagalog):
if tagalog.startswith("Kumusta"):
return "koo-MOOS-tah. Keep the greeting warm and clear."
if tagalog.startswith("Puwede"):
return "PWEH-deh poh bah AH-kong mag-tah-NONG. Make the question gentle."
if tagalog.startswith("Saan"):
return "SAH-ahn poh. Keep the question short and polite."
return "Read vowels clearly: a as ah, e as eh, i as ee, o as oh, u as oo."
Code explanation
- Business logic: Helpers add teaching value beyond translation.
- Code logic:
grammar_breakdownchooses beginner notes based on English and Tagalog text.pronunciation_guidereturns phrase-specific guidance or a fallback. - Expected result: Each generated card can display grammar and pronunciation without manually writing those sections for every card.
Step 6 — Render static HTML safely
Kiro prompt sample
Create a Python HTML renderer for article pages.
Escape all generated text.
Render title, summary, category, 40 sentence cards, grammar breakdown, pronunciation, and mobile CSS.
System design decision
- HTML escaping is mandatory: Generated content can include punctuation, apostrophes, or unexpected characters. Escaping protects page structure and prevents accidental markup injection.
- One renderer for every article: A single renderer prevents layout drift. If the card design changes, developers update one function and regenerate the site.
- CSS embedded for workshop simplicity: External CSS is cleaner for large sites, but embedding a small style block keeps the workshop artifact portable and easy to inspect.
Code sample — render.py
import html
from helpers import grammar_breakdown, pronunciation_guide
def render_card(card):
grammar_items = "".join(
f"<li><strong>{html.escape(term)}:</strong> {html.escape(meaning)}</li>"
for term, meaning in grammar_breakdown(card)
)
return f"""
<article class="sentence-card">
<h2>Sentence {card['num']}</h2>
<p><strong>Background:</strong> {html.escape(card['background'])}</p>
<p><strong>English:</strong> {html.escape(card['english'])}</p>
<p><strong>Natural Tagalog:</strong> <span lang="tl">{html.escape(card['natural'])}</span></p>
<p><strong>Polite Tagalog:</strong> <span lang="tl">{html.escape(card['polite'])}</span></p>
<p><strong>Friendly Filipino-English:</strong> {html.escape(card['friendly'])}</p>
<p><strong>Playful Filipino-English:</strong> {html.escape(card['playful'])} <span class="badge">Informal</span></p>
<p><strong>Tone:</strong> {html.escape(card['tone'])}</p>
<h3>Grammar breakdown</h3>
<ul>{grammar_items}</ul>
<h3>Pronunciation</h3>
<p>{html.escape(pronunciation_guide(card['polite']))}</p>
</article>
"""
def render_article(article, cards):
body = "\n".join(render_card(card) for card in cards)
return f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{html.escape(article['title'])}</title>
<style>
body {{ font-family: system-ui, sans-serif; margin: 0; padding: 1rem; line-height: 1.6; }}
main {{ max-width: 960px; margin: auto; }}
.sentence-card {{ border: 1px solid #d0d7de; border-radius: 16px; padding: 1rem; margin: 1rem 0; }}
.badge {{ background: #fff3cd; color: #7a4d00; padding: .15rem .5rem; border-radius: 999px; font-size: .75rem; }}
@media (max-width: 760px) {{ body {{ padding: .75rem; }} .sentence-card {{ padding: .85rem; }} }}
</style>
</head>
<body>
<main>
<p>{html.escape(article['category'])}</p>
<h1>{html.escape(article['title'])}</h1>
<p>{html.escape(article['summary'])}</p>
<p><strong>Review note:</strong> Generated language content requires native-speaker review before production use.</p>
{body}
</main>
</body>
</html>"""
Code explanation
- Business logic: The renderer turns structured learning cards into a browser-ready lesson page.
- Code logic:
html.escapeprotects all dynamic text.render_cardhandles card markup, whilerender_articlewraps cards in a full HTML document. - Expected result: A generated article page contains exactly the cards passed into the renderer and remains readable on mobile.
Step 7 — Validate and package the release
Kiro prompt sample
Create Python build code that generates article HTML, validates each page, prints JSON checks, and writes a zip package.
Checks: each page has 40 sentence-card elements, mobile CSS exists, summaries have 20 words, and forbidden internal references are absent.
System design decision
- Build orchestration as code: A professional workshop should end with a repeatable build, not a manually copied demo.
build.pydocuments the release process in executable form. - Validation after rendering: Checks run against generated HTML because that is what learners will open. Validating only source data can miss renderer defects.
- Zip artifact for sharing: A zip file is easy to distribute after a community workshop. Packaging the exact generated files prevents ambiguity about what should be deployed or reviewed.
Code sample — build.py
import json
import re
import zipfile
from pathlib import Path
from content import ARTICLES, SENTENCE_BANKS
from expand import expand_cards
from render import render_article
OUT_DIR = Path("dist")
ZIP_NAME = "tagalog-learning-static-site.zip"
def word_count(text):
return len(re.findall(r"\b\w+\b", text))
def validate_page(path, article):
text = path.read_text(encoding="utf-8")
return {
"file": path.name,
"sentence_cards": text.count('class="sentence-card"'),
"has_mobile_css": "@media (max-width: 760px)" in text,
"summary_words": word_count(article["summary"]),
"has_forbidden_internal_reference": bool(re.search(r"internal prompt|private instruction", text, re.I))
}
def build():
OUT_DIR.mkdir(exist_ok=True)
checks = []
for article in ARTICLES:
cards = expand_cards(SENTENCE_BANKS[article["id"]], article["id"])
html = render_article(article, cards)
output_path = OUT_DIR / f"article-{article['id']}-{article['slug']}.html"
output_path.write_text(html, encoding="utf-8")
checks.append(validate_page(output_path, article))
with zipfile.ZipFile(ZIP_NAME, "w", zipfile.ZIP_DEFLATED) as package:
for html_file in sorted(OUT_DIR.glob("*.html")):
package.write(html_file, arcname=html_file.name)
print(json.dumps(checks, indent=2))
failures = [
check for check in checks
if check["sentence_cards"] != 40
or not check["has_mobile_css"]
or check["summary_words"] != 20
or check["has_forbidden_internal_reference"]
]
if failures:
raise SystemExit("Release validation failed")
if __name__ == "__main__":
build()
Code explanation
- Business logic: The build script creates a release-ready static site and proves that the expected learning structure exists.
- Code logic: It expands cards, renders HTML, validates generated pages, writes a zip, prints JSON checks, and fails the build if any rule is violated.
- Expected result: Running
python build.pycreatesdist/*.html, createstagalog-learning-static-site.zip, prints checks, and exits successfully only when the release is valid.
Step 8 — Add a Kiro release hook and final review
Kiro prompt sample
Create a Kiro hook for release hygiene.
When build.py or content.py changes, run python build.py, summarize validation output, and suggest fixes for failed card counts, mobile CSS, summary word count, or forbidden references.
System design decision
- Automation near the change: Build validation should happen when source content or build logic changes, not only at the end of a release. A Kiro hook keeps feedback close to developer action.
- Explain failures, do not only fail: Developers learn faster when the tool explains why a release check failed and suggests a minimal fix. This is especially useful in a workshop where participants may be new to Kiro or the codebase.
- Release checklist as documentation: The hook becomes living documentation for what “done” means. It preserves team expectations for future contributors.
Kiro hook sample — .kiro/hooks/release-validation.md
# Hook: Release validation
Trigger: when `build.py`, `content.py`, `expand.py`, `helpers.py`, or `render.py` changes.
Action:
1. Run `python build.py`.
2. Read the JSON validation output.
3. If a page has fewer or more than 40 cards, explain which article failed.
4. If mobile CSS is missing, suggest restoring the media query in `render.py`.
5. If summary word count is not 20, suggest a corrected 20-word summary.
6. If forbidden internal references appear, identify the generated file and suggest removal.
Hook explanation
- Business logic: The hook protects the generated learning site from release drift.
- Code logic: It reacts to generator file changes, runs the build, interprets JSON checks, and suggests targeted fixes.
- Expected result: Developers get fast release feedback before sharing the static site.
Completion checklist
- Kiro steering describes file-first generator rules.
- Kiro spec describes data flow and tasks.
- Article metadata and sentence banks exist.
- Expansion returns exactly 40 cards.
- Grammar and pronunciation helpers work.
- Renderer escapes generated text.
- Build script validates generated HTML.
- Zip package is created.
- Kiro release hook guidance exists.
Optional AWS extension after the workshop
- Upload the zip contents to Amazon S3 static website hosting.
- Put Amazon CloudFront in front of the static site.
- Store source JSON in S3 with versioning enabled.
- Use Kiro CLI in CI/CD to run generator checks during pull requests.
Additional Hands-on Developer Labs
These labs are unique to Workshop 3 — Deep-Dive Development Flow. They extend the Python static-site generator with deterministic build operations, provenance, release manifests, reviewer bundles, and pipeline tests. The focus is generator engineering, not React app behavior.
Hands-on Lab A — Add a build manifest for release provenance
Developer action
- Ask Kiro to create a release manifest format.
- Generate
dist/manifest.jsonafter each build. - Include article count, generated files, card counts, build timestamp, and validation summary.
- Add a validator check that fails if the manifest is missing expected files.
Kiro prompt sample
Add a build manifest to the Python static generator.
After rendering pages, write dist/manifest.json with generated file names, article IDs, card counts, validation checks, and build timestamp.
The manifest should be deterministic except for timestamp.
Add validation that every generated HTML page appears in the manifest.
System design decision
- Release provenance matters: A zip artifact is easier to review when it contains a machine-readable description of what was generated.
- Manifest complements validation: Validation says whether the build passed; the manifest records what was built.
- Reviewer-friendly artifact: Reviewers can inspect file names and counts without opening every HTML page.
Code sample — manifest.py
from datetime import datetime, timezone
import json
from pathlib import Path
def write_manifest(output_dir: Path, articles: list[dict], checks: list[dict]) -> Path:
html_files = sorted(path.name for path in output_dir.glob("*.html"))
manifest = {
"generatedAt": datetime.now(timezone.utc).isoformat(),
"articleCount": len(articles),
"files": html_files,
"checks": checks,
"cardCounts": {
check["file"]: check["sentence_cards"]
for check in checks
}
}
manifest_path = output_dir / "manifest.json"
manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
return manifest_path
Code explanation
- Business logic: The manifest documents the generated release for reviewers and future maintainers.
- Code logic: The function scans generated HTML files, records validation output, and writes JSON into the output directory.
- Expected result: Every build produces
dist/manifest.jsonalongside the generated pages.
Hands-on Lab B — Add deterministic card IDs and slug collision checks
Developer action
- Ask Kiro to replace numeric-only card identity with stable IDs.
- Generate card IDs from article ID, base sentence index, and context index.
- Add a slug collision check for article metadata.
- Fail the build when duplicate card IDs or duplicate slugs appear.
Kiro prompt sample
Create deterministic IDs for generated cards.
Use article id, source sentence index, context index, and sequence number.
Add release validation for duplicate card ids and duplicate article slugs.
Return clear error messages that identify the conflicting records.
System design decision
- Stable IDs support review comments: Native-speaker reviewers need a stable reference when they comment on a generated card.
- Collision checks prevent overwritten pages: Duplicate slugs can cause one article to overwrite another in
dist. - Determinism supports regression testing: IDs should not change unless source data or expansion logic changes.
Code sample — identity.py
import re
def safe_slug(value: str) -> str:
text = value.lower().strip()
text = re.sub(r"[^a-z0-9]+", "-", text)
return text.strip("-")
def card_id(article_id: int, sentence_index: int, context_index: int, sequence: int) -> str:
return f"a{article_id:03d}-s{sentence_index:02d}-c{context_index:02d}-n{sequence:03d}"
def assert_unique(values: list[str], label: str) -> None:
seen: set[str] = set()
duplicates: set[str] = set()
for value in values:
if value in seen:
duplicates.add(value)
seen.add(value)
if duplicates:
joined = ", ".join(sorted(duplicates))
raise ValueError(f"Duplicate {label}: {joined}")
Code explanation
- Business logic: IDs and slugs become stable review and routing contracts.
- Code logic:
card_idproduces deterministic IDs;assert_uniquefails fast on duplicates. - Expected result: Reviewers can refer to card IDs, and the generator refuses ambiguous article output.
Hands-on Lab C — Create an incremental build cache for faster iteration
Developer action
- Ask Kiro to compute a content hash for each article’s source data.
- Store hashes in
.build-cache.json. - Skip rendering unchanged articles unless
--forceis provided. - Print which articles were built, skipped, or failed validation.
Kiro prompt sample
Add incremental build behavior to the static generator.
Hash article metadata, sentence bank, and contexts for each article.
Store hashes in .build-cache.json.
Skip unchanged articles unless --force is used.
Always run validation on existing output before packaging.
System design decision
- Fast feedback for content-heavy builds: Large generated sites should not re-render every page when one article changes.
- Cache source inputs, not output timestamps: Hashing source data makes rebuild decisions deterministic and portable.
- Validation still runs: Skipping rendering should not skip release checks, because existing output may still be invalid or missing.
Code sample — cache.py
import hashlib
import json
from pathlib import Path
from typing import Any
CACHE_FILE = Path(".build-cache.json")
def stable_hash(value: Any) -> str:
payload = json.dumps(value, sort_keys=True, ensure_ascii=False)
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def read_cache() -> dict[str, str]:
if not CACHE_FILE.exists():
return {}
return json.loads(CACHE_FILE.read_text(encoding="utf-8"))
def write_cache(cache: dict[str, str]) -> None:
CACHE_FILE.write_text(json.dumps(cache, indent=2), encoding="utf-8")
def should_build(cache: dict[str, str], article_id: int, source_hash: str, force: bool = False) -> bool:
return force or cache.get(str(article_id)) != source_hash
Code explanation
- Business logic: The cache reduces wait time while keeping builds reproducible.
- Code logic: JSON serialization with sorted keys creates stable hashes for source inputs.
- Expected result: Re-running the build skips unchanged articles but still validates the release.
Hands-on Lab D — Generate a reviewer bundle for native-speaker review
Developer action
- Ask Kiro to export generated cards to CSV for reviewers.
- Include card ID, article title, English, natural Tagalog, polite Tagalog, playful version, tone, and context.
- Add a reviewer-notes column left blank.
- Package the CSV inside the release zip with generated HTML.
Kiro prompt sample
Create a reviewer CSV export for the generated Tagalog cards.
Each row should include stable card id, article title, context, English, natural Tagalog, polite Tagalog, friendly Filipino-English, playful Filipino-English, tone, and reviewerNotes.
Write the file to dist/reviewer-cards.csv and include it in the zip package.
System design decision
- Reviewers need tabular workflow: Native-speaker review often happens more efficiently in spreadsheets than inside generated HTML.
- Stable IDs link review to output: The CSV must include the same card IDs that appear in generated pages.
- Blank reviewer notes preserve human authority: The generator prepares review structure but does not invent approval decisions.
Code sample — review_export.py
import csv
from pathlib import Path
def write_reviewer_csv(output_path: Path, article: dict, cards: list[dict]) -> None:
fieldnames = [
"cardId",
"articleTitle",
"context",
"english",
"naturalTagalog",
"politeTagalog",
"friendlyFilipinoEnglish",
"playfulFilipinoEnglish",
"tone",
"reviewerNotes"
]
with output_path.open("w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
for card in cards:
writer.writerow({
"cardId": card["id"],
"articleTitle": article["title"],
"context": card["context_en"],
"english": card["english"],
"naturalTagalog": card["natural"],
"politeTagalog": card["polite"],
"friendlyFilipinoEnglish": card["friendly"],
"playfulFilipinoEnglish": card["playful"],
"tone": card["tone"],
"reviewerNotes": ""
})
Code explanation
- Business logic: The export turns generated learning cards into a review-ready artifact.
- Code logic:
csv.DictWriterwrites consistent columns and preserves UTF-8 Tagalog text. - Expected result: Reviewers receive a CSV that maps directly back to generated HTML cards.
Hands-on Lab E — Add pipeline contract tests with pytest
Developer action
- Ask Kiro to create tests for the generator’s contracts.
- Test exact card count, duplicate ID failure, escaped HTML, manifest creation, and reviewer CSV columns.
- Run
pytestlocally after modifying generation logic. - Ask Kiro to explain failing tests and propose the smallest fix.
Kiro prompt sample
Generate pytest tests for the static-site generator pipeline.
Test expansion count, deterministic card ids, HTML escaping, manifest file creation, and reviewer CSV headers.
Use small in-memory sample data where possible.
Avoid full HTML snapshots unless necessary.
System design decision
- Pipeline contracts protect release behavior: The generator has more risk than a single function because data flows through expansion, rendering, validation, manifesting, and packaging.
- Small tests are easier to diagnose: Tests should verify key contracts without comparing huge generated pages.
- Escaping is non-negotiable: Renderer tests must include unsafe characters so escaping regressions are caught early.
Code sample — tests/test_pipeline_contracts.py
import json
from pathlib import Path
from identity import assert_unique, card_id
from manifest import write_manifest
from render import render_article
def test_card_id_is_deterministic():
assert card_id(1, 2, 3, 4) == "a001-s02-c03-n004"
def test_duplicate_detection_fails_fast():
try:
assert_unique(["intro", "intro"], "slug")
except ValueError as error:
assert "Duplicate slug" in str(error)
else:
raise AssertionError("Expected duplicate slug failure")
def test_render_article_escapes_title():
article = {"title": "Hello <Tagalog>", "category": "Test", "summary": "one two three"}
html = render_article(article, [])
assert "Hello <Tagalog>" in html
def test_manifest_records_generated_files(tmp_path: Path):
(tmp_path / "article-1-demo.html").write_text("<html></html>", encoding="utf-8")
manifest_path = write_manifest(tmp_path, [{"id": 1}], [{"file": "article-1-demo.html", "sentence_cards": 40}])
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
assert manifest["articleCount"] == 1
assert "article-1-demo.html" in manifest["files"]
Code explanation
- Business logic: The tests protect stable identity, safe rendering, and release documentation.
- Code logic: Each test checks a small contract with minimal fixture data.
- Expected result: Pipeline changes are safer because important generator promises are covered by tests.
Hands-on Lab F — Add a dry-run release mode with artifact checksums
Developer action
- Ask Kiro to add
--dry-runto the build command. - In dry-run mode, generate validation output without writing the zip.
- In normal mode, compute SHA-256 checksums for the zip and manifest.
- Print a final release summary for maintainers.
Kiro prompt sample
Add a dry-run release mode and artifact checksums to the Python build process.
--dry-run should render and validate but skip zip writing.
Normal release should write checksums for the zip file and manifest.json.
Print a concise release summary with files, checks, and checksum paths.
System design decision
- Dry-run reduces release risk: Maintainers can validate a candidate build without creating or overwriting an artifact.
- Checksums support artifact integrity: A checksum lets reviewers confirm that the shared zip is the same artifact that passed validation.
- Release summary improves handoff: The build output should tell maintainers exactly what happened and where to find evidence.
Code sample — checksums.py
import hashlib
from pathlib import Path
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as file:
for chunk in iter(lambda: file.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def write_checksum(path: Path) -> Path:
checksum = sha256_file(path)
checksum_path = path.with_suffix(path.suffix + ".sha256")
checksum_path.write_text(f"{checksum} {path.name}\n", encoding="utf-8")
return checksum_path
Code explanation
- Business logic: Checksums make the generated release easier to verify and share.
- Code logic: The function streams files in chunks and writes a standard
.sha256sidecar file. - Expected result: A normal release produces zip and manifest checksums, while dry-run validates without packaging.
Reference architecture notes
- Kiro capabilities emphasized in this workshop: file-first pipeline planning, deterministic Python generation, release validation, artifact packaging, provenance documentation, pipeline tests, and build-review automation.
- Product scope: generated Tagalog learning pages for review and eventual static hosting. Language content remains draft until reviewed by native Tagalog speakers.
- Runtime scope: generated static HTML first. Optional AWS deployment can use Amazon S3 static website hosting, Amazon CloudFront, or AWS Amplify Hosting after release artifacts pass validation.
