website-htmx-rustelo-code/scripts/content/generate-template-docs.nu

244 lines
9.7 KiB
Text
Raw Normal View History

2026-07-10 03:44:13 +01:00
#!/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<!-- hero -->\n\n# Post Title\n\nContent body.\n```\n"
$out
}