#!/usr/bin/env nu # Template Documentation Generator # # Reads an instructions.ncl file and renders it as: # --mode human → README.md (editor-facing guidelines) # --mode agent → prompt.md (AI agent system prompt) # # The instructions.ncl is the single source of truth for the content workflow. # Same schema, different projections per actor. # # Usage: # nu generate-template-docs.nu [--type blog] [--mode human|agent] [--output PATH] # nu generate-template-docs.nu --instructions PATH [--mode human|agent] [--output PATH] # # Examples: # nu generate-template-docs.nu --type blog --mode human # nu generate-template-docs.nu --type blog --mode agent --output site/content/blog/_templates/prompt.md def main [ --type: string = "blog" # Content type: blog | recipes | projects | activities --instructions: string = "" # Override: explicit path to instructions.ncl --mode: string = "human" # Output mode: human | agent --output: string = "" # Output file path (default: stdout) ] { let instr_path = if $instructions != "" { $instructions } else { $"site/reflection/content/($type)/mod.ncl" } if not ($instr_path | path exists) { error make { msg: $"Instructions file not found: ($instr_path)" } } if not ($mode in ["human", "agent"]) { error make { msg: $"--mode must be 'human' or 'agent', got: ($mode)" } } let data = nickel-export ($instr_path | path expand) let doc = if $mode == "human" { render_human $data } else { render_agent $data } if $output != "" { $doc | save --force $output print $"(ansi green)Written(ansi reset) → ($output)" } else { print $doc } } # --------------------------------------------------------------------------- # Human-facing README # --------------------------------------------------------------------------- def render_human [d: record] { mut out = "" $out = $out + $"# ($d.content_type | str capitalize) Content Guide\n\n" $out = $out + $"Version ($d.version)\n\n" # Template files $out = $out + "## Template Files\n\n" $out = $out + $"| File | Purpose |\n|------|----------|\n" $out = $out + $"| `($d.files.metadata)` | Post metadata — validated by Nickel contracts |\n" $out = $out + $"| `($d.files.content)` | Post body — no frontmatter, pure content |\n" $out = $out + $"| `($d.files.index)` | Category index — auto-generated, do not edit |\n" $out = $out + "\n" # Fields reference $out = $out + "## Metadata Fields\n\n" $out = $out + "| Field | Type | Required | Editable | Description |\n" $out = $out + "|-------|------|----------|----------|-------------|\n" for f in $d.fields { let req = if $f.required { "✅" } else { "—" } let edt = if $f.editable { "✅" } else { "🔒 pipeline" } $out = $out + $"| `($f.name)` | `($f.type)` | ($req) | ($edt) | ($f.doc) |\n" } $out = $out + "\n" # Rules $out = $out + "## Rules\n\n" for rule_group in ($d.constraints | items { |k, v| { group: $k, items: $v } }) { $out = $out + $"### ($rule_group.group | str replace --all '_' ' ' | str capitalize)\n\n" for rule in $rule_group.items { $out = $out + $"- ($rule)\n" } $out = $out + "\n" } # Workflows $out = $out + "## Workflows\n\n" for mode_entry in ($d.modes | items { |k, v| { name: $k, mode: $v } }) { let title = $mode_entry.name | str replace --all '_' ' ' | str capitalize $out = $out + $"### ($title)\n\n" $out = $out + $"_($mode_entry.mode.trigger)_\n\n" mut step_n = 1 for step in $mode_entry.mode.steps { let actor_tag = if $step.actor == "Agent" { " _(agent only)_" } else if $step.actor == "Human" { " _(human only)_" } else { "" } let step_id = if ("id" in $step) { $" `($step.id)`" } else { "" } $out = $out + $"($step_n). **($step.action)**($step_id)($actor_tag)\n" let deps = if ("depends_on" in $step) { $step.depends_on } else { [] } if ($deps | length) > 0 { let dep_list = $deps | each { |d| if $d.kind == "Always" { $d.step } else { $"($d.step):($d.kind)" } } | str join ", " $out = $out + $" _Requires: ($dep_list)_\n" } if ("cmd" in $step) { $out = $out + $" ```\n ($step.cmd)\n ```\n" } if ("verify" in $step) { $out = $out + $" Verify: `($step.verify)`\n" } if ("on_error" in $step) { let oe = $step.on_error let retry_info = if ($oe.strategy == "Retry") { $" (max ($oe.max), backoff ($oe.backoff_s)s)" } else { "" } let target_info = if ("target" in $oe) { $" → ($oe.target)" } else { "" } $out = $out + $" On error: **($oe.strategy)**($retry_info)($target_info)\n" } if ("note" in $step) { $out = $out + $" > ($step.note)\n" } $step_n = $step_n + 1 } $out = $out + "\n" } # Style $out = $out + "## Style Guide\n\n" for style_entry in ($d.style | items { |k, v| { key: $k, val: $v } }) { $out = $out + $"**($style_entry.key | str capitalize):** ($style_entry.val)\n\n" } $out } # --------------------------------------------------------------------------- # Agent system prompt # --------------------------------------------------------------------------- def render_agent [d: record] { mut out = "" $out = $out + $"# Agent Instructions — ($d.content_type | str capitalize) Content\n\n" $out = $out + "## Persona\n\n" $out = $out + $"($d.agent.persona)\n\n" $out = $out + "## Constraints\n\n" for c in $d.agent.rules { $out = $out + $"- ($c)\n" } $out = $out + "\n" # Metadata schema $out = $out + $"## Metadata Schema \(`($d.files.metadata)`\)\n\n" $out = $out + "Required fields (must always be present):\n\n" for f in ($d.fields | where required == true) { let example = if ("example" in $f) { $" Example: `($f.example)`" } else { "" } $out = $out + $"- **($f.name)** `($f.type)` — ($f.doc)($example)\n" } $out = $out + "\nOptional fields (include when known):\n\n" for f in ($d.fields | where required == false) { let locked = if not $f.editable { " **[PIPELINE ONLY — do not set]**" } else { "" } let example = if ("example" in $f) { $" Example: `($f.example)`" } else { "" } $out = $out + $"- **($f.name)** `($f.type)` — ($f.doc)($locked)($example)\n" } $out = $out + "\n" # Rules condensed $out = $out + "## Rules\n\n" for rule_group in ($d.constraints | items { |k, v| { group: $k, items: $v } }) { for rule in $rule_group.items { $out = $out + $"- ($rule)\n" } } $out = $out + "\n" # Workflows — agent steps only $out = $out + "## Action Flows\n\n" for mode_entry in ($d.modes | items { |k, v| { name: $k, mode: $v } }) { let title = $mode_entry.name | str replace --all '_' ' ' | str capitalize $out = $out + $"### ($title)\n" $out = $out + $"Trigger: ($mode_entry.mode.trigger)\n\n" let agent_steps = $mode_entry.mode.steps | where { |s| $s.actor != "Human" } mut step_n = 1 for step in $agent_steps { let step_id = if ("id" in $step) { $" [($step.id)]" } else { "" } $out = $out + $"($step_n). ($step.action)($step_id)\n" let deps = if ("depends_on" in $step) { $step.depends_on } else { [] } if ($deps | length) > 0 { let dep_list = $deps | each { |d| if $d.kind == "Always" { $d.step } else { $"($d.step):($d.kind)" } } | str join ", " $out = $out + $" Depends on: ($dep_list)\n" } if ("cmd" in $step) { $out = $out + $" Command: `($step.cmd)`\n" } if ("verify" in $step) { $out = $out + $" Verify: `($step.verify)`\n" } if ("on_error" in $step) { let oe = $step.on_error if $oe.strategy != "Stop" { let retry_info = if ($oe.strategy == "Retry") { $" max=($oe.max) backoff=($oe.backoff_s)s" } else { "" } let target_info = if ("target" in $oe) { $" → ($oe.target)" } else { "" } $out = $out + $" On error: ($oe.strategy)($retry_info)($target_info)\n" } } $step_n = $step_n + 1 } $out = $out + "\n" } # Style $out = $out + "## Style Requirements\n\n" for style_entry in ($d.style | items { |k, v| { key: $k, val: $v } }) { $out = $out + $"- **($style_entry.key | str capitalize):** ($style_entry.val)\n" } $out = $out + "\n" # File templates $out = $out + "## File Templates\n\n" $out = $out + $"### `($d.files.metadata)` structure\n\n" $out = $out + "```nickel\n" $out = $out + "let schema = import \"content/metadata/post_metadata.ncl\" in\n\n" $out = $out + "schema.make_post {\n" for f in ($d.fields | where { |f| $f.required == true or $f.name in ["subtitle", "excerpt", "tags", "read_time"] }) { let example = if ("example" in $f) { $"\"($f.example)\"" } else if $f.type == "bool" { "false" } else if $f.type == "number" { "0" } else { "\"\"" } $out = $out + $" ($f.name) = ($example),\n" } $out = $out + "}\n```\n\n" $out = $out + $"### `($d.files.content)` structure\n\n" $out = $out + "```markdown\n\n\n# Post Title\n\nContent body.\n```\n" $out }