Skip to content

Instantly share code, notes, and snippets.

@jwieringa
Created March 25, 2026 17:52
Show Gist options
  • Select an option

  • Save jwieringa/751a125eedae19a43641fac706adef51 to your computer and use it in GitHub Desktop.

Select an option

Save jwieringa/751a125eedae19a43641fac706adef51 to your computer and use it in GitHub Desktop.
write-tutorial skill for Claude Code — structured technical tutorial conventions with prediction prompts, alert boxes, verification queries, and concept-before-use ordering

Write Tutorial — Reference Examples

Concrete examples organized by pattern type. Examples are drawn from multiple technologies (SQL, shell, Kubernetes, Redis, etc.) to show that the patterns are technology-agnostic. Read this file when you need to see how a specific pattern looks in practice.

1. Alert box examples

Each alert box is exactly 2 lines: type + content.

IMPORTANT — states the failure mode of skipping a step

> [!IMPORTANT]
> Without this constraint, `ATTACH PARTITION` must scan every row to verify the partition constraint — on 1.1B rows, that scan takes hours under an `ACCESS EXCLUSIVE` lock that blocks all reads and writes.

NOTE — supplementary context that enriches understanding

> [!NOTE]
> A Kubernetes Service doesn't route traffic directly to containers — it targets Pods via label selectors. When a Pod matches the selector and passes its readiness probe, the Service adds it to the endpoint list automatically.

TIP — practical operator advice

> [!TIP]
> Use `--dry-run` to preview changes before applying. For example, `kubectl apply --dry-run=client -f manifest.yaml` shows what would change without touching the cluster.

WARNING — silent failures or data loss if ignored

> [!WARNING]
> If sequence ownership is not transferred before DROP, the sequence is cascade-dropped with the table — all future INSERTs fail.

CAUTION — subtle traps where the obvious approach is wrong

> [!CAUTION]
> `IS NOT TRUE` is the specific form PostgreSQL's predicate prover recognizes. Logically equivalent expressions like `sorted = FALSE OR sorted IS NULL` may *not* trigger the optimization — the prover does pattern matching on predicate forms, not arbitrary logical reasoning.

2. Prediction prompt examples

Testing observable state (partition membership)

Context: The reader has just run a drain batch setting sorted = TRUE.

Before running this, predict: which partition holds all 5 rows right now?

‎```sql
SELECT tableoid::regclass AS partition, count(*)
FROM public.archived_orders
GROUP BY 1 ORDER BY 1;
‎```

Expected: all 5 rows in `archived_orders_unsorted`, zero in the sorted partitions.

Testing infrastructure state

Context: The reader has just scaled a deployment to 3 replicas.

Before running this, predict: how many pods will show `Running` status?

‎```bash
kubectl get pods -l app=order-service -o wide
‎```

Expected: 3 pods in `Running` state. If you see fewer, one or more pods may be stuck in `Pending` (insufficient resources) or `CrashLoopBackOff` (application error).

Testing ownership

Context: About to transfer sequence ownership before dropping a table.

Before running this, predict: which table currently owns the sequence?

‎```sql
SELECT s.relname AS sequence_name,
       t.relname AS owning_table,
       a.attname AS owning_column
FROM pg_class s
JOIN pg_depend d ON d.objid = s.oid AND d.deptype = 'a'
JOIN pg_class t ON t.oid = d.refobjid
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.refobjsubid
WHERE s.relname = 'archived_orders_id_seq';
‎```

Expected: `owning_table = archived_orders_unsorted`. This is the table we're about to drop — if we don't transfer ownership first, the sequence dies with it.

3. Verification command examples

Simple verify with expected output

‎```sql
-- Verify: unsorted is no longer a partition of archived_orders.
SELECT inhrelid::regclass AS child
FROM pg_inherits
WHERE inhparent = 'public.archived_orders'::regclass;
‎```

Expected: only `archived_orders_sorted` remains.

Verify with failure diagnosis

‎```sql
-- Verify: the sequence still exists.
SELECT relname FROM pg_class
WHERE relname = 'archived_orders_id_seq' AND relkind = 'S';
‎```

Expected: one row. If this returns nothing, the sequence was cascade-dropped — which means 7a was skipped or pointed at the wrong table.

Multi-query verify (checking several things at once)

‎```sql
-- Verify: sorted column is gone, PK exists, pg_partman knows the new name.
SELECT column_name FROM information_schema.columns
WHERE table_name = 'archived_orders' AND column_name = 'sorted';

SELECT conname, contype FROM pg_constraint
WHERE conrelid = 'public.archived_orders'::regclass AND contype = 'p';

SELECT parent_table, partition_type, partition_interval
FROM partman.part_config
WHERE parent_table = 'public.archived_orders';
‎```

Expected: no rows from the first query (column gone), one row from the second showing the primary key constraint, one row from the third (pg_partman config updated).

Shell-based verify with expected output

‎```bash
# Verify: replica is connected and syncing.
redis-cli -h replica-01 INFO replication | grep master_link_status
‎```

Expected: `master_link_status:up`. If you see `master_link_status:down`, the replica lost connection — check network and authentication settings.

4. Bold-question concept introductions

Design decision within a step (midweight)

**Why no primary key here?** PostgreSQL requires every partitioning column to appear in any unique constraint on a partitioned table. This LIST parent partitions by `sorted`, and the RANGE sub-partition below partitions by `archived_at` — so a PK would need to include `(id, archived_at, sorted)`. Since `sorted` is temporary scaffolding that we drop in step 7, including it in the PK would force us to drop and recreate the constraint during teardown.

Explaining why a tool doesn't fit (midweight)

**Why not `partition_data_proc()`?** pg_partman's `partition_data_proc()` moves rows based on their *existing* column values. Our drain needs to *change* `sorted` from NULL to TRUE to trigger cross-partition row movement. `partition_data_proc()` doesn't modify column values, so manual batched UPDATEs are required.

Comparing approaches (midweight)

**Why YAML and not JSON for the manifest?** Kubernetes accepts both, but YAML supports comments, multi-document files (`---` separators), and is far more readable for nested structures. Every upstream example and Helm chart uses YAML — matching the ecosystem reduces friction when adapting snippets.

5. State diagram example

Bold label introduces the diagram, followed by the ASCII tree:

**During migration (steps 3–6):**
‎```
archived_orders (LIST by sorted)            ← temporary scaffolding
├── archived_orders_unsorted                ← original table (sorted IN (FALSE, NULL))
└── archived_orders_sorted                  ← RANGE by archived_at (sorted IN (TRUE))
    ├── archived_orders_sorted_p2019_01
    ├── archived_orders_sorted_p2019_02
    ├── ...
    └── archived_orders_sorted_default
‎```

**After step 7 (scaffolding removed):**
‎```
archived_orders (RANGE by archived_at)      ← this IS archived_orders_sorted, renamed
├── archived_orders_sorted_p2019_01         ← child names unchanged (cosmetic only)
├── archived_orders_sorted_p2019_02
├── ...
└── archived_orders_sorted_default
‎```

Infrastructure hierarchy example

**Service topology after deployment:**
‎```
production (namespace)
├── order-service (Deployment, 3 replicas)       ← handles API traffic
│   ├── order-service-7f8b9c-abc12 (Pod)
│   ├── order-service-7f8b9c-def34 (Pod)
│   └── order-service-7f8b9c-ghi56 (Pod)
├── order-worker (Deployment, 2 replicas)        ← processes background jobs
│   ├── order-worker-4a2d1e-jkl78 (Pod)
│   └── order-worker-4a2d1e-mno90 (Pod)
└── redis (StatefulSet, 1 replica)               ← session store + cache
    └── redis-0 (Pod)
‎```

Key features:

  • Tree characters: ├── for middle children, └── for last child
  • Annotations right-aligned with
  • Resource type/strategy shown in parentheses after the name
  • Indented children show hierarchy depth

6. Section flow example

One complete sub-step showing the full motivation → mechanism → code → verify cycle:

#### 7a. Transfer sequence ownership

> [!WARNING]
> If sequence ownership is not transferred before DROP, the sequence is cascade-dropped with the table — all future INSERTs fail.

`archived_orders_id_seq` is the sequence behind the `id bigserial` column — it generates the next ID for every INSERT. PostgreSQL tracks which table column *owns* a sequence. When you DROP a table, PostgreSQL automatically drops any sequences it owns.

Right now the sequence is owned by `archived_orders_unsorted.id` (the original table, renamed in step 5b). If we drop that table first, the sequence dies with it, and every future INSERT fails. We must transfer ownership to a table that will survive the teardown *before* dropping anything.

Before running this, predict: which table currently owns the sequence?

‎```sql
SELECT s.relname AS sequence_name,
       t.relname AS owning_table,
       a.attname AS owning_column
FROM pg_class s
JOIN pg_depend d ON d.objid = s.oid AND d.deptype = 'a'
JOIN pg_class t ON t.oid = d.refobjid
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.refobjsubid
WHERE s.relname = 'archived_orders_id_seq';
‎```

Expected: `owning_table = archived_orders_unsorted`. This is the table we're about to drop — if we don't transfer ownership first, the sequence dies with it.

‎```sql
-- Transfer sequence ownership to the sorted table (which will become the final table).
-- This breaks the ownership link to archived_orders_unsorted so we can safely drop it.
ALTER SEQUENCE public.archived_orders_id_seq
    OWNED BY public.archived_orders_sorted.id;
‎```

‎```sql
-- Verify: sequence is now owned by archived_orders_sorted.id
SELECT s.relname AS sequence_name,
       t.relname AS owning_table,
       a.attname AS owning_column
FROM pg_class s
JOIN pg_depend d ON d.objid = s.oid AND d.deptype = 'a'
JOIN pg_class t ON t.oid = d.refobjid
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.refobjsubid
WHERE s.relname = 'archived_orders_id_seq';
‎```

Expected: `owning_table = archived_orders_sorted`, `owning_column = id`.

This sub-step demonstrates: WARNING alert before the explanation, mechanism (why ownership matters), prediction prompt, verify-before (check current state), action (transfer), verify-after (confirm transfer).

7. Quick reference table example

## Quick reference: pg_partman functions

| Function | Purpose | When to run |
|----------|---------|-------------|
| `create_parent()` | Initialize partitioning on a table | Once at setup |
| `show_partitions()` | List all children of a parent | Monitoring, validation |
| `run_maintenance()` | Create/drop partitions, manage default | Auto via BGW, or manually |
| `apply_constraints()` | Add CHECK constraints to old partitions | After setup, then via maintenance |
| `check_parent()` | Verify partition tree health | Monitoring |
| `check_default()` | Monitor default partition row count | Continuous monitoring |
| `partition_data_proc()` | Batch-move misrouted rows from default | Cleanup |
| `undo_partition()` | Reverse partitioning entirely | Rollback |

CLI command reference example

## Quick reference: kubectl debugging commands

| Command | Purpose | When to run |
|---------|---------|-------------|
| `kubectl get pods` | List pods and their status | First look at any issue |
| `kubectl describe pod <name>` | Show events, conditions, resource limits | Pod stuck in Pending/CrashLoop |
| `kubectl logs <pod> --previous` | Fetch logs from the last crashed container | After a CrashLoopBackOff |
| `kubectl top pods` | Show CPU/memory consumption | Investigating OOMKilled or throttling |
| `kubectl exec -it <pod> -- sh` | Open a shell inside the container | Network or filesystem debugging |
| `kubectl port-forward svc/<name> 8080:80` | Tunnel traffic to a service | Testing without an ingress |

Features:

  • H2 heading (same level as Part headings)
  • Command/function names in backticks
  • Purpose column: present-tense verb phrase, no articles, under 10 words
  • "When to run" gives operational context

8. Truth table example

Used for boolean/enum logic with a small finite set of values:

| `sorted` value | `sorted IS NOT TRUE` | Accepted by `IN (FALSE, NULL)`? |
|---|---|---|
| `TRUE` | `false` | No |
| `FALSE` | `true` | Yes |
| `NULL` | `true` | Yes |

Features:

  • Column headers describe the evaluation being demonstrated
  • All possible values enumerated (boolean has exactly three: TRUE, FALSE, NULL)
  • Results show the mapping between the constraint form and the partition bound
name write-tutorial
description Write a structured, pedagogically sound technical tutorial following established conventions — step-by-step walkthroughs with prediction prompts, alert boxes, verification queries, and concept-before-use ordering
argument-hint
topic

Write Tutorial

You are writing a technical tutorial. Follow this workflow and the rules below. For concrete examples of each pattern, read reference.md in this skill directory.

1. Workflow

  1. Understand the scope: Ask the user what the tutorial covers, who the audience is, and what the end state looks like. Clarify tool versions, environment assumptions, and whether the tutorial should be runnable against a disposable environment.
  2. Outline first: Build the Part/Step/Sub-step skeleton before writing prose. Share it with the user for approval.
  3. Draft linearly: Write in document order so concept dependencies are satisfied naturally. Never reference a concept before defining it.
  4. Add pedagogy: Insert prediction prompts, alert boxes, and verification queries as you draft each step — don't bolt them on after.
  5. Cross-reference pass: Add forward/backward step references (e.g., "This pays off in step N", "The constraint from step 2b").
  6. Verify pass: Check numbering continuity, concept-before-use ordering, alert box count/density, and that every code block has a verification query.

2. Document structure

  • H1: Document title (exactly one)
  • H2: ## Part N: Name — major phases (typically 4-6)
  • H3: ### N. Step name — numbered steps within a part (contiguous integers starting from 0)
  • H4: #### Na. Sub-step name — lettered sub-steps, or named subsections like #### Entering state, #### Gap detection
  • Unnumbered H3s are allowed in the introduction (before Part 1) for conceptual primers (e.g., ### What is LIST partitioning?)
  • Step 0 is always "Tutorial setup" — a disposable sandbox environment the reader can tear down and recreate
  • Compound sub-steps are allowed when operations MUST be atomic: #### 7d/7e/7f.

3. Section flow pattern

Each numbered step follows: Motivation → Mechanism → Code → Verify

  1. Motivation (1-2 sentences): What the step does and why it matters
  2. Mechanism (0-many paragraphs): How the approach works, design decisions (bold-question format), truth tables if applicable
  3. Code: Commands or configuration in fenced blocks with language tag
  4. Verify: Verification command with Expected: annotation

The fullest expression (for critical sub-steps): alert → explain → predict → verify-before → act → verify-after.

Not every step needs all four components — simple steps may skip Mechanism entirely. But every step that runs code must have a Verify.

4. Pedagogical techniques

Prediction prompts

Target 7-10 per tutorial. Format:

Before running this, predict: [question]?

Rules:

  • Standalone paragraph, placed before the verification command
  • Questions test observable state (system output, config values, resource status), not abstract concepts
  • The code output immediately following IS the answer — no collapsible sections or separate answer blocks
  • Space them throughout the tutorial, not clustered in one section

Alert boxes

Target ~1 per 60 lines of output (~16 per ~1000-line tutorial). Use these types:

  • > [!IMPORTANT] — consequences of skipping or misunderstanding a step. States the failure mode.
  • > [!NOTE] — supplementary context the reader might not know. Enriches understanding, not critical.
  • > [!TIP] — practical operator advice, actionable shortcuts.
  • > [!WARNING] — things that cause silent failures or data loss if ignored. Real but recoverable.
  • > [!CAUTION] — subtle technical traps where the "obvious" approach is wrong.

Rules:

  • All alert boxes are exactly 2 lines: the type line + one content line
  • Appear AFTER the prose introducing a concept and BEFORE the code implementing it
  • Distribute types — don't use 10 NOTEs and no WARNINGs

Verification commands

Every step that runs code gets a verification block. The form depends on the technology:

-- Verify: [what to check]
SELECT ...;
# Verify: [what to check]
kubectl get pods -n ...
# Verify: [what to check]
redis-cli INFO replication

Followed by:

Expected: [concise expected output description]

Where applicable, add failure diagnosis: "If this returns nothing, [what went wrong]."

5. Concept introduction patterns

Use the weight appropriate to the concept's importance:

  • Heavyweight (tutorial prerequisites): Standalone H3 question heading in the intro, before Part 1. Full paragraphs of explanation.
  • Midweight (within-step design decisions): **Bold question?** Answer in same paragraph.
  • Lightweight (single-sentence definitions): Inline backtick definition at point of first use.
  • Truth tables: For boolean/enum logic with a finite set of values (3-5 rows). Use a markdown table.

Rule: Every concept must be defined before its first use. If a diagram or early section needs a concept, add a forward definition (NOTE alert box or lightweight inline) before the first reference.

6. Formatting conventions

State diagrams (ASCII art)

Use at phase boundaries (3-5 per tutorial), not every step. Format:

root_resource (type/strategy)              ← annotation
├── child_one                              ← annotation
└── child_two                              ← annotation
    ├── grandchild_one
    └── grandchild_default

Rules:

  • Tree format with ├──, └──
  • Right-aligned annotations with
  • Show resource hierarchy with type/strategy labels in parentheses
  • Preceded by bold label: **During migration (steps 3-6):** or **After step N:**

Code commenting

  • INSIDE code blocks: intent comments (what the command does operationally), parameter annotations, verify comments
  • OUTSIDE code blocks: design decisions (why this approach), internals explanations, cross-references, expected results

Cross-references

  • Forward: "This pays off in step N", "gets removed in step N"
  • Backward: "added in step Na", "The CHECK constraint from step Nb"
  • Always include sub-step letter suffix when referencing sub-steps
  • External doc links use inline markdown: [name](url)

Quick reference table

Place at the end, before Sources. Same heading level as Parts (H2).

  • Columns: Function/Command | Purpose | When to run
  • Only include functions/commands discussed in the tutorial
  • Purpose column: present-tense verb phrase, no articles, under 10 words

Sources

Final section. List all external references cited in the tutorial.

7. Prose style

  • Imperative mood for instructions ("Add the column", "Create the config file", "Verify the result")
  • Declarative for explanations ("Redis requires...", "The scheduler works by...", "PostgreSQL uses...")
  • Each paragraph in markdown is a single line (no hard wrapping) — let the renderer handle wrapping
  • No emojis
  • Backtick-wrap all identifiers, function names, column/field values, file paths, and CLI commands
  • Bold for key output tokens in Expected annotations: **Seq Scan**, **master**, **Running**
  • Values like dates, sizes, and thresholds are illustrative — note that readers should adjust them for their environment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment