Files
2026-05-27 23:29:53 +01:00
..
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00

README-generation templates

This directory holds @suaveplan/templates engine templates for auto-generating README.md files at three levels of the packages/ tree.

Template Target path Purpose
package-readme.template.md packages/<category>/<plugin>/<pkg>/README.md Stand-alone npm-package readme
plugin-readme.template.md packages/<category>/<plugin>/README.md Index of packages inside one plugin
category-readme.template.md packages/<category>/README.md Index of all packages in a category

How to render

Load the template file as a string, build a context object, and pass both to TemplateEngine with the loop-aware preset:

import {
  TemplateEngine,
  fullPresetWithLoops,
} from "@suaveplan/templates";
import type { ParseState } from "@suaveplan/templates";

const engine = new TemplateEngine();
engine.usePreset(
  fullPresetWithLoops(
    (tpl, ctx, state) =>
      (engine as unknown as { parseInternal: Function }).parseInternal(
        tpl,
        ctx,
        state
      )
  )
);

const template = await Bun.file(
  ".claude/templates/package-readme.template.md"
).text();

const context = {
  packageName: "@suaveplan/api-plugin-vault",
  shortDescription: "Per-user vault HTTP plugin for `@suaveplan/api`.",
  summary: "Per-user vault HTTP plugin...",
  installCommand: "bun add @suaveplan/api-plugin-vault",
  quickStartCode: `import { createVaultPlugin } from "@suaveplan/api-plugin-vault";`,
  quickStartDescription: "After `registry.start()`, the vault plugin...",
  features: [
    "Persists `site` records under SurrealDB...",
    "Enforces per-organization and per-site caps...",
  ],
  modules: [
    { name: "error-codes/error-codes", path: "./src/error-codes/error-codes.md", description: "9 stable `VAULT_PLUGIN_*` codes." },
    { name: "plugin/plugin", path: "./src/plugin/plugin.md", description: "`createVaultPlugin` factory." },
  ],
  otelInstruments: [
    { type: "Counter", name: "webhook_plugin.deliveries_total", labels: "{event,status,endpoint_id}" },
  ],
  otelStatusValues: "`delivered`, `exhausted`, `not_found`",
  license: "MIT",
};

const readme = engine.parse(template, context);

Context shapes

package-readme.template.md

Key Type Required Notes
packageName string ✅ Full scoped name, e.g. @suaveplan/templates
shortDescription string ✅ ≤120 characters; appended to H1
summary string ✅ Multi-line paragraph(s)
installCommand string ✅ Shell command, e.g. bun add @suaveplan/templates
quickStartCode string ✅ Code inside the ts fence (no backticks)
quickStartDescription string ❌ Paragraph after the quick-start block
features string[] ❌ Renders the "What it does" section
modules {name,path,description}[] ✅ Module index bullets
otelInstruments {type,name,labels?}[] ❌ Renders the "OpenTelemetry" section
otelStatusValues string ❌ Sentence fragment after the instrument list
license string ✅ e.g. MIT

plugin-readme.template.md

Key Type Required
pluginName string ✅
pluginDescription string ✅
packages {name,path,description}[] ✅

category-readme.template.md

Key Type Required
categoryName string ✅
categoryDescription string ✅
packages {name,path,description}[] ✅

Brace-escaping caveat

The @suaveplan/templates engine scans the entire template (including text inside code blocks) for {identifier} tokens. If a context string contains a literal brace pair that looks like a variable — e.g. GET /sites/{id}/public — the engine will try to resolve it and, when the identifier is absent from context, replace it with an empty string.

When building context objects, avoid bare {foo} patterns. Safe work-arounds:

  1. Add a space — { id } does not match any plugin regex.
  2. Back-tick wrapping — `{id}` is still scanned, but if the value is not in context the engine replaces it with "". Only use this when the loss of the braces is acceptable.
  3. Post-process — after rendering, run a regex replace that restores any sentinels you placed in the context strings (e.g. «LBRACE» → {).

Because the package-level readme is aimed at npm consumers with no knowledge of the monorepo, the generated text should already avoid monorepo-specific tags (genesis, migration phases, universal tiers, etc.). The AI generating the context object is responsible for stripping that content before rendering.

Conditional nesting

The @suaveplan/templates conditional plugin ({if:…}…{/if}) does not support nesting. The package template keeps all conditionals at the top level so they never wrap each other. Loop bodies may contain inner conditionals safely because the loop plugin resolves them recursively before the outer conditional runs.