Skip to content

Commit 9aed05d

Browse files
blessuselesskclaude
andcommitted
Add render-prose convenience function, upgrade to docstrings
render-prose(raw) takes a TOML string, parses via from-toml, and dispatches to the appropriate PROSE renderer. Eliminates the need for callers to carry their own dispatch logic. Also converts all source comments to /// docstrings for Typst tooling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ff093d9 commit 9aed05d

7 files changed

Lines changed: 264 additions & 200 deletions

File tree

lib.typ

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
// PROSE adapter layer (Phase 3, evolving — NOT under 10-symbol contract):
4343
// p-agent, p-instruction, p-skill, p-workflow constructors
4444
// render-agent, render-instruction, render-skill, render-workflow renderers
45+
// render-prose TOML string → Markdown string (auto-dispatches by top-level key)
4546

4647
#import "src/primitives.typ": p-context, p-schema, p-checkpoint, p-chat-mode, p-prompt
4748
#import "src/render.typ": render-context, render-schema, render-checkpoint, render-chat-mode, render-prompt
@@ -50,3 +51,34 @@
5051
#import "src/adapters/prose.typ": render-agent, render-instruction, render-skill, render-workflow
5152
#import "src/ingest.typ": from-toml, from-yaml
5253
#import "src/helpers.typ": ctx, schema, field, entry, checkpoint
54+
55+
/// Render a PROSE TOML source to Markdown.
56+
///
57+
/// Parses the TOML string via `from-toml`, detects the top-level primitive key
58+
/// (agent, instruction, skill, workflow, prompt, context, schema), and dispatches
59+
/// to the appropriate renderer.
60+
///
61+
/// Panics if no recognized primitive key is found.
62+
///
63+
/// - raw (str): Raw TOML string containing exactly one PROSE primitive.
64+
/// -> str
65+
#let render-prose(raw) = {
66+
let data = from-toml(raw)
67+
if data.at("agent", default: none) != none {
68+
render-agent(data.agent)
69+
} else if data.at("instruction", default: none) != none {
70+
render-instruction(data.instruction)
71+
} else if data.at("skill", default: none) != none {
72+
render-skill(data.skill)
73+
} else if data.at("workflow", default: none) != none {
74+
render-workflow(data.workflow)
75+
} else if data.at("prompt", default: none) != none {
76+
render-prompt(data.prompt)
77+
} else if data.at("context", default: none) != none {
78+
render-context(data.context)
79+
} else if data.at("schema", default: none) != none {
80+
render-schema(data.schema)
81+
} else {
82+
panic("promptyst: render-prose found no recognized primitive in TOML input")
83+
}
84+
}

src/.keep

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/adapters/prose.typ

Lines changed: 79 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,22 @@
4343
// ═════════════════════════════════════════════
4444

4545

46-
// ─────────────────────────────────────────────
47-
// p-agent
48-
//
49-
// id: string (required)
50-
// description: string (required)
51-
// tools: array of string (optional, default ())
52-
// handoffs: array of string (optional, default ())
53-
// model: string (optional, default none)
54-
// persona: string (required — the introductory paragraph)
55-
// expertise: array of string (required)
56-
// boundaries: dict with can, cannot, approval keys (required)
57-
// extra-sections: array of (heading, body) dicts (optional, default ())
58-
// Rendered after Boundaries, before References. Body is raw Markdown.
59-
// references: array of (label, path) dicts (optional, default ())
60-
// ─────────────────────────────────────────────
61-
46+
/// Construct an agent dictionary for PROSE agent primitives.
47+
///
48+
/// Agents have a persona, domain expertise, and operational boundaries.
49+
/// Extra sections and references are optional.
50+
///
51+
/// - id (str): Unique agent identifier.
52+
/// - description (str): Short description (used in frontmatter).
53+
/// - tools (array): Tool names the agent can use. Default: `()`.
54+
/// - handoffs (array): Agent IDs this agent can hand off to. Default: `()`.
55+
/// - model (str): Preferred model identifier. Default: `none`.
56+
/// - persona (str): Introductory paragraph defining the agent's voice.
57+
/// - expertise (array): Non-empty list of domain expertise strings.
58+
/// - boundaries (dictionary): Dict with `can`, `cannot`, and `approval` string keys.
59+
/// - extra-sections (array): List of `(heading: str, body: str)` dicts. Default: `()`.
60+
/// - references (array): List of `(label: str, path: str)` dicts. Default: `()`.
61+
/// -> dictionary
6262
#let p-agent(
6363
id: none,
6464
description: none,
@@ -97,20 +97,18 @@
9797
}
9898

9999

100-
// ─────────────────────────────────────────────
101-
// p-instruction
102-
//
103-
// id: string (required)
104-
// apply-to: string glob (required)
105-
// description: string (optional, default none)
106-
// sections: array of (heading, items|body) dicts (required)
107-
// each section has exactly one of:
108-
// items: array of string → rendered as bullet list
109-
// body: string → rendered as raw Markdown
110-
// prohibited: array of string (optional, default ())
111-
// references: array of (label, path) dicts (optional, default ())
112-
// ─────────────────────────────────────────────
113-
100+
/// Construct an instruction dictionary for PROSE instruction primitives.
101+
///
102+
/// Each section must have exactly one of `items` (bullet list) or `body` (raw Markdown).
103+
/// If a section is named "Prohibited", the auto-appended Prohibited section is skipped.
104+
///
105+
/// - id (str): Unique instruction identifier.
106+
/// - apply-to (str): Glob pattern for files this instruction applies to.
107+
/// - description (str): Short description (used in frontmatter). Default: `none`.
108+
/// - sections (array): Non-empty list of `(heading: str, items?: array, body?: str)` dicts.
109+
/// - prohibited (array): List of prohibited-action strings. Default: `()`.
110+
/// - references (array): List of `(label: str, path: str)` dicts. Default: `()`.
111+
/// -> dictionary
114112
#let p-instruction(
115113
id: none,
116114
apply-to: none,
@@ -153,20 +151,18 @@
153151
}
154152

155153

156-
// ─────────────────────────────────────────────
157-
// p-skill
158-
//
159-
// name: string (required)
160-
// description: string (required, can be multiline)
161-
// trigger: string (required)
162-
// rules: array of string (required)
163-
// extra-sections: array of (heading, body) dicts (optional, default ())
164-
// Rendered after Quick Rules, before Detailed References.
165-
// references-preamble: string (optional, default none)
166-
// Prose paragraph before the reference links.
167-
// references: array of (label, path) dicts (optional, default ())
168-
// ─────────────────────────────────────────────
169-
154+
/// Construct a skill dictionary for PROSE skill primitives.
155+
///
156+
/// Skills define trigger conditions, quick rules, and optional detailed references.
157+
///
158+
/// - name (str): Skill name (used in frontmatter).
159+
/// - description (str): Skill description (can be multiline, rendered as YAML block scalar).
160+
/// - trigger (str): Condition that activates this skill.
161+
/// - rules (array): Non-empty list of rule strings (rendered as numbered list).
162+
/// - extra-sections (array): List of `(heading: str, body: str)` dicts. Default: `()`.
163+
/// - references-preamble (str): Prose paragraph before reference links. Default: `none`.
164+
/// - references (array): List of `(label: str, path: str)` dicts. Default: `()`.
165+
/// -> dictionary
170166
#let p-skill(
171167
name: none,
172168
description: none,
@@ -194,17 +190,17 @@
194190
}
195191

196192

197-
// ─────────────────────────────────────────────
198-
// p-workflow
199-
//
200-
// id: string (required)
201-
// description: string (required)
202-
// mode: string (optional, default "agent")
203-
// agent: string (optional, default none)
204-
// phases: array of (name, steps) dicts (required)
205-
// each phase may optionally include checkpoint: string
206-
// ─────────────────────────────────────────────
207-
193+
/// Construct a workflow dictionary for PROSE workflow primitives.
194+
///
195+
/// Workflows define phased execution plans. Each phase has a name, steps,
196+
/// and an optional checkpoint string.
197+
///
198+
/// - id (str): Unique workflow identifier.
199+
/// - description (str): Workflow description (used in frontmatter).
200+
/// - mode (str): Execution mode. Default: `"agent"`.
201+
/// - agent (str): Agent identifier to execute this workflow. Default: `none`.
202+
/// - phases (array): Non-empty list of `(name: str, steps: array, checkpoint?: str)` dicts.
203+
/// -> dictionary
208204
#let p-workflow(
209205
id: none,
210206
description: none,
@@ -238,10 +234,13 @@
238234
// ═════════════════════════════════════════════
239235

240236

241-
// ─────────────────────────────────────────────
242-
// render-agent
243-
// ─────────────────────────────────────────────
244-
237+
/// Render an agent dictionary as Markdown with YAML frontmatter.
238+
///
239+
/// Outputs frontmatter (description, tools, optional handoffs/model),
240+
/// persona, expertise list, boundaries, extra sections, and references.
241+
///
242+
/// - a (dictionary): An agent dictionary (from `p-agent`).
243+
/// -> str
245244
#let render-agent(a) = {
246245
if a.at("_type", default: none) != "agent" {
247246
panic("promptyst: render-agent requires an agent dictionary.")
@@ -292,10 +291,13 @@
292291
}
293292

294293

295-
// ─────────────────────────────────────────────
296-
// render-instruction
297-
// ─────────────────────────────────────────────
298-
294+
/// Render an instruction dictionary as Markdown with YAML frontmatter.
295+
///
296+
/// Sections with `items` render as bullet lists; sections with `body` render as raw Markdown.
297+
/// If a section is named "Prohibited", the auto-appended Prohibited section is skipped.
298+
///
299+
/// - instr (dictionary): An instruction dictionary (from `p-instruction`).
300+
/// -> str
299301
#let render-instruction(instr) = {
300302
if instr.at("_type", default: none) != "instruction" {
301303
panic("promptyst: render-instruction requires an instruction dictionary.")
@@ -340,10 +342,13 @@
340342
}
341343

342344

343-
// ─────────────────────────────────────────────
344-
// render-skill
345-
// ─────────────────────────────────────────────
346-
345+
/// Render a skill dictionary as Markdown with YAML frontmatter.
346+
///
347+
/// Outputs frontmatter (name, block-scalar description), trigger section,
348+
/// numbered rules, extra sections, and optional detailed references.
349+
///
350+
/// - sk (dictionary): A skill dictionary (from `p-skill`).
351+
/// -> str
347352
#let render-skill(sk) = {
348353
if sk.at("_type", default: none) != "skill" {
349354
panic("promptyst: render-skill requires a skill dictionary.")
@@ -385,10 +390,13 @@
385390
}
386391

387392

388-
// ─────────────────────────────────────────────
389-
// render-workflow
390-
// ─────────────────────────────────────────────
391-
393+
/// Render a workflow dictionary as Markdown with YAML frontmatter.
394+
///
395+
/// Each phase renders as a numbered heading with its steps.
396+
/// Phases with a `checkpoint` key append a bold checkpoint line.
397+
///
398+
/// - wf (dictionary): A workflow dictionary (from `p-workflow`).
399+
/// -> str
392400
#let render-workflow(wf) = {
393401
if wf.at("_type", default: none) != "workflow" {
394402
panic("promptyst: render-workflow requires a workflow dictionary.")

src/helpers.typ

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,35 @@
1313
#import "primitives.typ": p-context, p-schema, p-checkpoint
1414

1515

16-
// ─────────────────────────────────────────────
17-
// entry(key, value)
18-
// Convenience for building context entry dicts.
19-
// ─────────────────────────────────────────────
20-
16+
/// Build a context entry dictionary.
17+
///
18+
/// - key (str): Entry key.
19+
/// - value (str): Entry value.
20+
/// -> dictionary
2121
#let entry(key, value) = (key: key, value: value)
2222

2323

24-
// ─────────────────────────────────────────────
25-
// field(name, typ, desc)
26-
// Convenience for building schema field dicts.
27-
// Named `typ` not `type` — avoids shadowing Typst's type() builtin.
28-
// ─────────────────────────────────────────────
29-
24+
/// Build a schema field dictionary.
25+
///
26+
/// Named `typ` (not `type`) to avoid shadowing Typst's `type()` builtin.
27+
///
28+
/// - name (str): Field name.
29+
/// - typ (str): Field type (passed through verbatim).
30+
/// - desc (str): Field description.
31+
/// -> dictionary
3032
#let field(name, typ, desc) = (name: name, type: typ, description: desc)
3133

3234

33-
// ─────────────────────────────────────────────
34-
// ctx(id, ..pairs)
35-
// Shorthand for p-context. Accepts positional entry dicts.
36-
//
37-
// ctx("my-ctx", entry("k", "v"), entry("k2", "v2"))
38-
//
39-
// Or with inline tuples:
40-
// ctx("my-ctx", ("k", "v"), ("k2", "v2"))
41-
// ─────────────────────────────────────────────
42-
35+
/// Shorthand for `p-context`. Accepts positional entry dicts or tuple pairs.
36+
///
37+
/// ```typst
38+
/// ctx("my-ctx", entry("k", "v"), entry("k2", "v2"))
39+
/// ctx("my-ctx", ("k", "v"), ("k2", "v2")) // tuple shorthand
40+
/// ```
41+
///
42+
/// - id (str): Context identifier.
43+
/// - ..entries (arguments): Positional `entry()` dicts or `(key, value)` tuples.
44+
/// -> dictionary
4345
#let ctx(id, ..entries) = {
4446
let parsed = entries.pos().map(e => {
4547
if type(e) == array {
@@ -54,26 +56,32 @@
5456
}
5557

5658

57-
// ─────────────────────────────────────────────
58-
// schema(id, ..fields)
59-
// Shorthand for p-schema. Accepts positional field dicts.
60-
//
61-
// schema("my-schema", field("name", "string", "desc"))
62-
// ─────────────────────────────────────────────
63-
59+
/// Shorthand for `p-schema`. Accepts positional field dicts.
60+
///
61+
/// ```typst
62+
/// schema("my-schema", field("name", "string", "A description"))
63+
/// ```
64+
///
65+
/// - id (str): Schema identifier.
66+
/// - ..fields (arguments): Positional `field()` dictionaries.
67+
/// -> dictionary
6468
#let schema(id, ..fields) = p-schema(
6569
id: id,
6670
fields: fields.pos(),
6771
)
6872

6973

70-
// ─────────────────────────────────────────────
71-
// checkpoint(id, after-step, assertion, on-fail)
72-
// Shorthand for p-checkpoint. Positional args for conciseness.
73-
//
74-
// checkpoint("verify", 2, "Data is valid", "halt")
75-
// ─────────────────────────────────────────────
76-
74+
/// Shorthand for `p-checkpoint`. All positional args for conciseness.
75+
///
76+
/// ```typst
77+
/// checkpoint("verify", 2, "Data is valid", "halt")
78+
/// ```
79+
///
80+
/// - id (str): Checkpoint identifier.
81+
/// - after-step (int): Step number (>= 1) after which to evaluate.
82+
/// - assertion (str): Plain-language assertion statement.
83+
/// - on-fail (str): Either `"halt"` or `"continue"`.
84+
/// -> dictionary
7785
#let checkpoint(id, after-step, assertion, on-fail) = p-checkpoint(
7886
id: id,
7987
after-step: after-step,

src/ingest.typ

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -202,19 +202,21 @@
202202
}
203203

204204

205-
// ─────────────────────────────────────────────
206-
// from-toml
207-
//
208-
// raw: string — TOML-encoded prompt data
209-
// ─────────────────────────────────────────────
210-
205+
/// Parse a TOML string into a prompt result dictionary.
206+
///
207+
/// Missing sections produce absent keys (no panic). Present sections with
208+
/// invalid data panic via constructor validation. When all required sections
209+
/// are present, a full prompt is assembled via `p-prompt`.
210+
///
211+
/// - raw (str): TOML-encoded prompt data.
212+
/// -> dictionary
211213
#let from-toml(raw) = _from-data(toml(bytes(raw)))
212214

213215

214-
// ─────────────────────────────────────────────
215-
// from-yaml
216-
//
217-
// raw: string — YAML-encoded prompt data
218-
// ─────────────────────────────────────────────
219-
216+
/// Parse a YAML string into a prompt result dictionary.
217+
///
218+
/// Behaves identically to `from-toml` but accepts YAML input.
219+
///
220+
/// - raw (str): YAML-encoded prompt data.
221+
/// -> dictionary
220222
#let from-yaml(raw) = _from-data(yaml(bytes(raw)))

0 commit comments

Comments
 (0)