@funkai/prompts 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
 
2
- > @funkai/prompts@0.4.0 build /home/runner/work/funkai/funkai/packages/prompts
2
+ > @funkai/prompts@0.4.1 build /home/runner/work/funkai/funkai/packages/prompts
3
3
  > tsdown && cp -r src/prompts dist/prompts
4
4
 
5
5
  ℹ tsdown v0.21.4 powered by rolldown v1.0.0-rc.9
@@ -8,13 +8,13 @@
8
8
  ℹ target: node22
9
9
  ℹ tsconfig: tsconfig.json
10
10
  ℹ Build start
11
- ℹ dist/lib/index.mjs 3.92 kB │ gzip: 1.53 kB
11
+ ℹ dist/lib/index.mjs 3.99 kB │ gzip: 1.56 kB
12
12
  ℹ dist/lib/cli.mjs 1.39 kB │ gzip: 0.75 kB
13
13
  ℹ dist/lib/runtime.mjs 0.08 kB │ gzip: 0.08 kB
14
- ℹ dist/lib/index.mjs.map 5.72 kB │ gzip: 2.08 kB
14
+ ℹ dist/lib/index.mjs.map 6.42 kB │ gzip: 2.30 kB
15
15
  ℹ dist/lib/cli.mjs.map 1.86 kB │ gzip: 0.90 kB
16
- ℹ dist/lib/engine-CPKs9QbX.mjs.map 1.67 kB │ gzip: 0.85 kB
17
- ℹ dist/lib/engine-CPKs9QbX.mjs 1.17 kB │ gzip: 0.62 kB
16
+ ℹ dist/lib/engine-Bhv5Zxdu.mjs.map 1.75 kB │ gzip: 0.87 kB
17
+ ℹ dist/lib/engine-Bhv5Zxdu.mjs 1.21 kB │ gzip: 0.64 kB
18
18
  ℹ dist/lib/types-DmnHC99v.d.mts.map 0.66 kB │ gzip: 0.34 kB
19
19
  ℹ dist/lib/index.d.mts.map 0.45 kB │ gzip: 0.25 kB
20
20
  ℹ dist/lib/engine-BAYeiay3.d.mts.map 0.22 kB │ gzip: 0.17 kB
@@ -24,5 +24,5 @@
24
24
  ℹ dist/lib/runtime.d.mts 0.08 kB │ gzip: 0.08 kB
25
25
  ℹ dist/lib/types-DmnHC99v.d.mts 2.17 kB │ gzip: 0.94 kB
26
26
  ℹ dist/lib/engine-BAYeiay3.d.mts 1.08 kB │ gzip: 0.58 kB
27
- ℹ 16 files, total: 24.62 kB
28
- ✔ Build complete in 2257ms
27
+ ℹ 16 files, total: 25.52 kB
28
+ ✔ Build complete in 2824ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @funkai/prompts
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - ef51bd7: fix(packages/prompts): enable strictVariables, fix deepFreeze mutation, add @throws docs
8
+
9
+ - Add `strictVariables: true` to shared liquid engine — template/schema mismatches now throw instead of silently rendering empty strings
10
+ - Fix `deepFreeze` to shallow-copy nested namespace objects before freezing (previously mutated caller references)
11
+ - Replace `for...of` loop in `deepFreeze` with `Object.entries().reduce()`
12
+ - Add `@throws {ZodError}` documentation to `render()` and `validate()` methods
13
+
3
14
  ## 0.4.0
4
15
 
5
16
  ### Minor Changes
package/README.md CHANGED
@@ -39,7 +39,7 @@ You are a {{ tone }} writer.
39
39
  ### Generate typed modules
40
40
 
41
41
  ```bash
42
- npx funkai prompts generate --out .prompts/client --roots src/agents
42
+ npx funkai prompts generate --out .prompts/client --includes "src/agents/**"
43
43
  ```
44
44
 
45
45
  ### Consume prompts
@@ -94,8 +94,8 @@ Use `{% render 'name', key: 'value' %}` to include shared partials. Partials res
94
94
 
95
95
  ## Documentation
96
96
 
97
- For comprehensive documentation, see [docs/overview.md](docs/overview.md).
97
+ For comprehensive documentation, see the [Prompts concept](/concepts/prompts) and [Prompts CLI reference](/reference/prompts/cli).
98
98
 
99
99
  ## License
100
100
 
101
- [MIT](../../LICENSE)
101
+ [MIT](https://github.com/joggrdocs/funkai/blob/main/LICENSE)
package/dist/lib/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as createEngine } from "./engine-CPKs9QbX.mjs";
1
+ import { t as createEngine } from "./engine-Bhv5Zxdu.mjs";
2
2
  import { flow } from "es-toolkit";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -1 +1 @@
1
- {"version":3,"file":"engine-BAYeiay3.d.mts","names":[],"sources":["../../src/engine.ts"],"mappings":";;;;;;AAcA;;;;;;;;iBAAgB,YAAA,CAAa,WAAA,UAAqB,OAAA,GAAU,OAAA,CAAQ,mBAAA,IAAuB,MAAA;;;;;;;AAsB3F;;;cAAa,YAAA,EAAY,MAAA"}
1
+ {"version":3,"file":"engine-BAYeiay3.d.mts","names":[],"sources":["../../src/engine.ts"],"mappings":";;;;;;AAcA;;;;;;;;iBAAgB,YAAA,CAAa,WAAA,UAAqB,OAAA,GAAU,OAAA,CAAQ,mBAAA,IAAuB,MAAA;;;;;;;AAuB3F;;;cAAa,YAAA,EAAY,MAAA"}
@@ -18,6 +18,7 @@ function createEngine(partialsDir, options) {
18
18
  cache: true,
19
19
  ...options,
20
20
  strictFilters: true,
21
+ strictVariables: true,
21
22
  ownPropertyOnly: true
22
23
  });
23
24
  }
@@ -32,9 +33,10 @@ function createEngine(partialsDir, options) {
32
33
  */
33
34
  const liquidEngine = new Liquid({
34
35
  strictFilters: true,
36
+ strictVariables: true,
35
37
  ownPropertyOnly: true
36
38
  });
37
39
  //#endregion
38
40
  export { liquidEngine as n, createEngine as t };
39
41
 
40
- //# sourceMappingURL=engine-CPKs9QbX.mjs.map
42
+ //# sourceMappingURL=engine-Bhv5Zxdu.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine-CPKs9QbX.mjs","names":[],"sources":["../../src/engine.ts"],"sourcesContent":["import { Liquid } from \"liquidjs\";\n\nimport type { CreateEngineOptions } from \"./types.js\";\n\n/**\n * Create a LiquidJS engine with custom options.\n *\n * The `partialsDir` is used as the root for `{% render %}` resolution.\n * The `.prompt` extension is appended automatically.\n *\n * @param partialsDir - Root directory for `{% render %}` partial resolution.\n * @param options - Optional overrides for the LiquidJS engine configuration.\n * @returns A configured {@link Liquid} engine instance.\n */\nexport function createEngine(partialsDir: string, options?: Partial<CreateEngineOptions>): Liquid {\n return new Liquid({\n root: [partialsDir],\n partials: [partialsDir],\n extname: \".prompt\",\n cache: true,\n ...options,\n // Safety defaults — applied after spread so callers cannot disable them\n strictFilters: true,\n ownPropertyOnly: true,\n });\n}\n\n/**\n * Shared LiquidJS engine for rendering prompt templates at runtime.\n *\n * Partials are flattened at codegen time by the CLI, so this engine\n * only needs to handle `{{ var }}` expressions and basic Liquid\n * control flow (`{% if %}`, `{% for %}`). No filesystem access required.\n *\n * @type {Liquid}\n */\nexport const liquidEngine = new Liquid({\n strictFilters: true,\n ownPropertyOnly: true,\n});\n"],"mappings":";;;;;;;;;;;;AAcA,SAAgB,aAAa,aAAqB,SAAgD;AAChG,QAAO,IAAI,OAAO;EAChB,MAAM,CAAC,YAAY;EACnB,UAAU,CAAC,YAAY;EACvB,SAAS;EACT,OAAO;EACP,GAAG;EAEH,eAAe;EACf,iBAAiB;EAClB,CAAC;;;;;;;;;;;AAYJ,MAAa,eAAe,IAAI,OAAO;CACrC,eAAe;CACf,iBAAiB;CAClB,CAAC"}
1
+ {"version":3,"file":"engine-Bhv5Zxdu.mjs","names":[],"sources":["../../src/engine.ts"],"sourcesContent":["import { Liquid } from \"liquidjs\";\n\nimport type { CreateEngineOptions } from \"./types.js\";\n\n/**\n * Create a LiquidJS engine with custom options.\n *\n * The `partialsDir` is used as the root for `{% render %}` resolution.\n * The `.prompt` extension is appended automatically.\n *\n * @param partialsDir - Root directory for `{% render %}` partial resolution.\n * @param options - Optional overrides for the LiquidJS engine configuration.\n * @returns A configured {@link Liquid} engine instance.\n */\nexport function createEngine(partialsDir: string, options?: Partial<CreateEngineOptions>): Liquid {\n return new Liquid({\n root: [partialsDir],\n partials: [partialsDir],\n extname: \".prompt\",\n cache: true,\n ...options,\n // Safety defaults — applied after spread so callers cannot disable them\n strictFilters: true,\n strictVariables: true,\n ownPropertyOnly: true,\n });\n}\n\n/**\n * Shared LiquidJS engine for rendering prompt templates at runtime.\n *\n * Partials are flattened at codegen time by the CLI, so this engine\n * only needs to handle `{{ var }}` expressions and basic Liquid\n * control flow (`{% if %}`, `{% for %}`). No filesystem access required.\n *\n * @type {Liquid}\n */\nexport const liquidEngine = new Liquid({\n strictFilters: true,\n strictVariables: true,\n ownPropertyOnly: true,\n});\n"],"mappings":";;;;;;;;;;;;AAcA,SAAgB,aAAa,aAAqB,SAAgD;AAChG,QAAO,IAAI,OAAO;EAChB,MAAM,CAAC,YAAY;EACnB,UAAU,CAAC,YAAY;EACvB,SAAS;EACT,OAAO;EACP,GAAG;EAEH,eAAe;EACf,iBAAiB;EACjB,iBAAiB;EAClB,CAAC;;;;;;;;;;;AAYJ,MAAa,eAAe,IAAI,OAAO;CACrC,eAAe;CACf,iBAAiB;CACjB,iBAAiB;CAClB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { n as liquidEngine } from "./engine-CPKs9QbX.mjs";
1
+ import { n as liquidEngine } from "./engine-Bhv5Zxdu.mjs";
2
2
  //#region src/group.ts
3
3
  /**
4
4
  * Create a prompt group namespace from a record of prompt modules.
@@ -118,9 +118,13 @@ function isPromptModule(value) {
118
118
  * @private
119
119
  */
120
120
  function deepFreeze(obj) {
121
- Object.freeze(obj);
122
- for (const value of Object.values(obj)) if (typeof value === "object" && value !== null && !Object.isFrozen(value) && !isPromptModule(value)) deepFreeze(value);
123
- return obj;
121
+ const copied = Object.entries(obj).reduce((acc, [key, value]) => {
122
+ if (typeof value === "object" && value !== null && !isPromptModule(value)) acc[key] = deepFreeze({ ...value });
123
+ else acc[key] = value;
124
+ return acc;
125
+ }, {});
126
+ Object.freeze(copied);
127
+ return copied;
124
128
  }
125
129
  //#endregion
126
130
  export { createPrompt, createPromptGroup, createPromptRegistry };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/group.ts","../../src/prompt.ts","../../src/registry.ts"],"sourcesContent":["import type { PromptModule } from \"./types.js\";\n\n/**\n * Create a prompt group namespace from a record of prompt modules.\n *\n * Sets the `group` field on each module to the given group name,\n * producing a new namespace object without mutating the originals.\n *\n * @param name - Group name applied to each prompt (e.g. `'agents'`, `'agents/core'`).\n * @param prompts - Record of prompt modules to group.\n * @returns A new {@link PromptNamespace} with group-tagged prompt modules.\n *\n * @example\n * ```ts\n * import { createPrompt, createPromptGroup } from '@funkai/prompts'\n * import { z } from 'zod'\n *\n * const agents = createPromptGroup('agents', {\n * greeting: createPrompt({\n * name: 'greeting',\n * template: 'Hello {{ name }}!',\n * schema: z.object({ name: z.string() }),\n * }),\n * })\n *\n * agents.greeting.render({ name: 'Alice' })\n * ```\n */\nexport function createPromptGroup<T extends Record<string, PromptModule>>(\n name: string,\n prompts: T,\n): T {\n return Object.fromEntries(\n Object.entries(prompts).map(([key, module]) => [key, { ...module, group: name }]),\n ) as T;\n}\n","import { liquidEngine } from \"./engine.js\";\nimport type { PromptConfig, PromptModule } from \"./types.js\";\n\n/**\n * Create a prompt module from a config object.\n *\n * Encapsulates template rendering (via LiquidJS) and variable validation\n * (via Zod) into a single {@link PromptModule}. Works for both codegen\n * output and runtime on-the-fly prompt construction.\n *\n * @param config - Prompt configuration with name, template, schema, and optional group.\n * @returns A {@link PromptModule} with `render` and `validate` methods.\n *\n * @example\n * ```ts\n * import { createPrompt } from '@funkai/prompts'\n * import { z } from 'zod'\n *\n * const greeting = createPrompt({\n * name: 'greeting',\n * template: 'Hello {{ name }}, welcome to {{ place }}!',\n * schema: z.object({ name: z.string(), place: z.string() }),\n * })\n *\n * greeting.render({ name: 'Alice', place: 'Wonderland' })\n * // => \"Hello Alice, welcome to Wonderland!\"\n * ```\n */\nexport function createPrompt<T>(config: PromptConfig<T>): PromptModule<T> {\n const { name, group, template, schema } = config;\n\n return {\n name,\n group,\n schema,\n render(variables: T): string {\n return liquidEngine.parseAndRenderSync(\n template,\n schema.parse(variables) as Record<string, unknown>,\n );\n },\n validate(variables: unknown): T {\n return schema.parse(variables);\n },\n };\n}\n","import type { PromptModule, PromptNamespace, PromptRegistry } from \"./types.js\";\n\n/**\n * Create a typed, frozen prompt registry from a (possibly nested) map of prompt modules.\n *\n * The registry is typically created by generated code — the CLI produces\n * an `index.ts` that calls `createPromptRegistry()` with all discovered\n * prompt modules keyed by camelCase name, nested by group.\n *\n * @param modules - Record mapping camelCase prompt names (or group namespaces) to their modules.\n * @returns A deep-frozen, typed record with direct property access.\n *\n * @example\n * ```ts\n * const prompts = createPromptRegistry({\n * agents: { coverageAssessor },\n * greeting,\n * })\n * prompts.agents.coverageAssessor.render({ scope: 'full' })\n * ```\n */\nexport function createPromptRegistry<T extends PromptNamespace>(modules: T): PromptRegistry<T> {\n return deepFreeze({ ...modules });\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether a value looks like a PromptModule leaf.\n * Leaves have `name`, `schema`, and `render` — namespaces do not.\n *\n * @private\n */\nfunction isPromptModule(value: unknown): value is PromptModule {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"render\" in value &&\n \"schema\" in value &&\n \"name\" in value\n );\n}\n\n/**\n * Recursively freeze a prompt namespace tree.\n * Only recurses into plain namespace nodes — PromptModule leaves\n * (which contain Zod schemas) are intentionally left unfrozen\n * to avoid breaking Zod internal state.\n *\n * @param obj - The namespace object to freeze.\n * @returns The frozen object cast to its deep-readonly type.\n *\n * @private\n */\nfunction deepFreeze<T extends PromptNamespace>(obj: T): PromptRegistry<T> {\n Object.freeze(obj);\n for (const value of Object.values(obj)) {\n if (\n typeof value === \"object\" &&\n value !== null &&\n !Object.isFrozen(value) &&\n !isPromptModule(value)\n ) {\n deepFreeze(value as PromptNamespace);\n }\n }\n return obj as PromptRegistry<T>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,kBACd,MACA,SACG;AACH,QAAO,OAAO,YACZ,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC,KAAK;EAAE,GAAG;EAAQ,OAAO;EAAM,CAAC,CAAC,CAClF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACNH,SAAgB,aAAgB,QAA0C;CACxE,MAAM,EAAE,MAAM,OAAO,UAAU,WAAW;AAE1C,QAAO;EACL;EACA;EACA;EACA,OAAO,WAAsB;AAC3B,UAAO,aAAa,mBAClB,UACA,OAAO,MAAM,UAAU,CACxB;;EAEH,SAAS,WAAuB;AAC9B,UAAO,OAAO,MAAM,UAAU;;EAEjC;;;;;;;;;;;;;;;;;;;;;;;ACvBH,SAAgB,qBAAgD,SAA+B;AAC7F,QAAO,WAAW,EAAE,GAAG,SAAS,CAAC;;;;;;;;AAanC,SAAS,eAAe,OAAuC;AAC7D,QACE,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACZ,YAAY,SACZ,UAAU;;;;;;;;;;;;;AAed,SAAS,WAAsC,KAA2B;AACxE,QAAO,OAAO,IAAI;AAClB,MAAK,MAAM,SAAS,OAAO,OAAO,IAAI,CACpC,KACE,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,OAAO,SAAS,MAAM,IACvB,CAAC,eAAe,MAAM,CAEtB,YAAW,MAAyB;AAGxC,QAAO"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/group.ts","../../src/prompt.ts","../../src/registry.ts"],"sourcesContent":["import type { PromptModule } from \"./types.js\";\n\n/**\n * Create a prompt group namespace from a record of prompt modules.\n *\n * Sets the `group` field on each module to the given group name,\n * producing a new namespace object without mutating the originals.\n *\n * @param name - Group name applied to each prompt (e.g. `'agents'`, `'agents/core'`).\n * @param prompts - Record of prompt modules to group.\n * @returns A new {@link PromptNamespace} with group-tagged prompt modules.\n *\n * @example\n * ```ts\n * import { createPrompt, createPromptGroup } from '@funkai/prompts'\n * import { z } from 'zod'\n *\n * const agents = createPromptGroup('agents', {\n * greeting: createPrompt({\n * name: 'greeting',\n * template: 'Hello {{ name }}!',\n * schema: z.object({ name: z.string() }),\n * }),\n * })\n *\n * agents.greeting.render({ name: 'Alice' })\n * ```\n */\nexport function createPromptGroup<T extends Record<string, PromptModule>>(\n name: string,\n prompts: T,\n): T {\n return Object.fromEntries(\n Object.entries(prompts).map(([key, module]) => [key, { ...module, group: name }]),\n ) as T;\n}\n","import { liquidEngine } from \"./engine.js\";\nimport type { PromptConfig, PromptModule } from \"./types.js\";\n\n/**\n * Create a prompt module from a config object.\n *\n * Encapsulates template rendering (via LiquidJS) and variable validation\n * (via Zod) into a single {@link PromptModule}. Works for both codegen\n * output and runtime on-the-fly prompt construction.\n *\n * @param config - Prompt configuration with name, template, schema, and optional group.\n * @returns A {@link PromptModule} with `render` and `validate` methods.\n *\n * @example\n * ```ts\n * import { createPrompt } from '@funkai/prompts'\n * import { z } from 'zod'\n *\n * const greeting = createPrompt({\n * name: 'greeting',\n * template: 'Hello {{ name }}, welcome to {{ place }}!',\n * schema: z.object({ name: z.string(), place: z.string() }),\n * })\n *\n * greeting.render({ name: 'Alice', place: 'Wonderland' })\n * // => \"Hello Alice, welcome to Wonderland!\"\n * ```\n */\nexport function createPrompt<T>(config: PromptConfig<T>): PromptModule<T> {\n const { name, group, template, schema } = config;\n\n return {\n name,\n group,\n schema,\n /**\n * Render the prompt template with the given variables.\n *\n * @param variables - Template variables matching the prompt schema.\n * @returns The rendered prompt string.\n * @throws {ZodError} If variables fail schema validation.\n */\n render(variables: T): string {\n return liquidEngine.parseAndRenderSync(\n template,\n schema.parse(variables) as Record<string, unknown>,\n );\n },\n /**\n * Validate variables against the prompt schema.\n *\n * @param variables - Variables to validate.\n * @returns The parsed and validated variables.\n * @throws {ZodError} If variables fail schema validation.\n */\n validate(variables: unknown): T {\n return schema.parse(variables);\n },\n };\n}\n","import type { PromptModule, PromptNamespace, PromptRegistry } from \"./types.js\";\n\n/**\n * Create a typed, frozen prompt registry from a (possibly nested) map of prompt modules.\n *\n * The registry is typically created by generated code — the CLI produces\n * an `index.ts` that calls `createPromptRegistry()` with all discovered\n * prompt modules keyed by camelCase name, nested by group.\n *\n * @param modules - Record mapping camelCase prompt names (or group namespaces) to their modules.\n * @returns A deep-frozen, typed record with direct property access.\n *\n * @example\n * ```ts\n * const prompts = createPromptRegistry({\n * agents: { coverageAssessor },\n * greeting,\n * })\n * prompts.agents.coverageAssessor.render({ scope: 'full' })\n * ```\n */\nexport function createPromptRegistry<T extends PromptNamespace>(modules: T): PromptRegistry<T> {\n return deepFreeze({ ...modules });\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether a value looks like a PromptModule leaf.\n * Leaves have `name`, `schema`, and `render` — namespaces do not.\n *\n * @private\n */\nfunction isPromptModule(value: unknown): value is PromptModule {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"render\" in value &&\n \"schema\" in value &&\n \"name\" in value\n );\n}\n\n/**\n * Recursively freeze a prompt namespace tree.\n * Only recurses into plain namespace nodes — PromptModule leaves\n * (which contain Zod schemas) are intentionally left unfrozen\n * to avoid breaking Zod internal state.\n *\n * @param obj - The namespace object to freeze.\n * @returns The frozen object cast to its deep-readonly type.\n *\n * @private\n */\nfunction deepFreeze<T extends PromptNamespace>(obj: T): PromptRegistry<T> {\n const copied = Object.entries(obj).reduce<Record<string, unknown>>((acc, [key, value]) => {\n const isNamespace = typeof value === \"object\" && value !== null && !isPromptModule(value);\n if (isNamespace) {\n acc[key] = deepFreeze({ ...value } as PromptNamespace);\n } else {\n acc[key] = value;\n }\n return acc;\n }, {});\n\n Object.freeze(copied);\n return copied as PromptRegistry<T>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,kBACd,MACA,SACG;AACH,QAAO,OAAO,YACZ,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC,KAAK;EAAE,GAAG;EAAQ,OAAO;EAAM,CAAC,CAAC,CAClF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACNH,SAAgB,aAAgB,QAA0C;CACxE,MAAM,EAAE,MAAM,OAAO,UAAU,WAAW;AAE1C,QAAO;EACL;EACA;EACA;EAQA,OAAO,WAAsB;AAC3B,UAAO,aAAa,mBAClB,UACA,OAAO,MAAM,UAAU,CACxB;;EASH,SAAS,WAAuB;AAC9B,UAAO,OAAO,MAAM,UAAU;;EAEjC;;;;;;;;;;;;;;;;;;;;;;;ACrCH,SAAgB,qBAAgD,SAA+B;AAC7F,QAAO,WAAW,EAAE,GAAG,SAAS,CAAC;;;;;;;;AAanC,SAAS,eAAe,OAAuC;AAC7D,QACE,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACZ,YAAY,SACZ,UAAU;;;;;;;;;;;;;AAed,SAAS,WAAsC,KAA2B;CACxE,MAAM,SAAS,OAAO,QAAQ,IAAI,CAAC,QAAiC,KAAK,CAAC,KAAK,WAAW;AAExF,MADoB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,eAAe,MAAM,CAEvF,KAAI,OAAO,WAAW,EAAE,GAAG,OAAO,CAAoB;MAEtD,KAAI,OAAO;AAEb,SAAO;IACN,EAAE,CAAC;AAEN,QAAO,OAAO,OAAO;AACrB,QAAO"}
@@ -1,2 +1,2 @@
1
- import { n as liquidEngine } from "./engine-CPKs9QbX.mjs";
1
+ import { n as liquidEngine } from "./engine-Bhv5Zxdu.mjs";
2
2
  export { liquidEngine };
@@ -6,14 +6,14 @@ Generate typed TypeScript modules from `.prompt` files.
6
6
 
7
7
  **Alias:** `gen`
8
8
 
9
- | Flag | Alias | Required | Description |
10
- | ---------- | ----- | -------- | ------------------------------------------------------- |
11
- | `--out` | `-o` | Yes | Output directory for generated files |
12
- | `--roots` | `-r` | Yes | Space-separated directories to scan for `.prompt` files |
13
- | `--silent` | --- | No | Suppress output except errors |
9
+ | Flag | Alias | Required | Description |
10
+ | ------------ | ----- | -------- | ----------------------------------------- |
11
+ | `--out` | `-o` | Yes | Output directory for generated files |
12
+ | `--includes` | `-r` | Yes | Glob patterns to scan for `.prompt` files |
13
+ | `--silent` | --- | No | Suppress output except errors |
14
14
 
15
15
  ```bash
16
- prompts generate --out .prompts/client --roots prompts src/agents src/workflows
16
+ prompts generate --out .prompts/client --includes "prompts/**" "src/agents/**" "src/workflows/**"
17
17
  ```
18
18
 
19
19
  Custom partials are auto-discovered from the sibling `partials/` directory (relative to `--out`).
@@ -26,7 +26,7 @@ Validate `.prompt` files without generating output.
26
26
 
27
27
  | Flag | Alias | Required | Description |
28
28
  | ------------ | ----- | -------- | -------------------------------------------------------- |
29
- | `--roots` | `-r` | Yes | Directories to scan |
29
+ | `--includes` | `-r` | Yes | Glob patterns to scan for `.prompt` files |
30
30
  | `--partials` | `-p` | No | Custom partials directory (default: `.prompts/partials`) |
31
31
  | `--silent` | --- | No | Suppress output except errors |
32
32
 
@@ -38,7 +38,7 @@ Validate `.prompt` files without generating output.
38
38
  | Warn | Schema variable not used in template |
39
39
 
40
40
  ```bash
41
- prompts lint --roots prompts src/agents
41
+ prompts lint --includes "prompts/**" "src/agents/**"
42
42
  ```
43
43
 
44
44
  ## `prompts create`
@@ -61,7 +61,7 @@ Add a generate script to your `package.json`:
61
61
  ```json
62
62
  {
63
63
  "scripts": {
64
- "prompts:generate": "prompts generate --out .prompts/client --roots prompts src/agents"
64
+ "prompts:generate": "prompts generate --out .prompts/client --includes \"prompts/**\" \"src/agents/**\""
65
65
  }
66
66
  }
67
67
  ```
package/docs/cli.md ADDED
@@ -0,0 +1,132 @@
1
+ # CLI
2
+
3
+ The `prompts` CLI discovers, validates, and generates typed TypeScript from `.prompt` files.
4
+
5
+ ## Installation
6
+
7
+ Available as the `prompts` binary from `@funkai/cli`. Install it as a workspace dependency:
8
+
9
+ ```bash
10
+ pnpm add @funkai/cli --workspace
11
+ ```
12
+
13
+ ## Workflow
14
+
15
+ ```mermaid
16
+ %%{init: {
17
+ 'theme': 'base',
18
+ 'themeVariables': {
19
+ 'primaryColor': '#313244',
20
+ 'primaryTextColor': '#cdd6f4',
21
+ 'primaryBorderColor': '#6c7086',
22
+ 'lineColor': '#89b4fa',
23
+ 'secondaryColor': '#45475a',
24
+ 'tertiaryColor': '#1e1e2e',
25
+ 'actorBkg': '#313244',
26
+ 'actorBorder': '#89b4fa',
27
+ 'actorTextColor': '#cdd6f4',
28
+ 'signalColor': '#cdd6f4',
29
+ 'signalTextColor': '#cdd6f4'
30
+ }
31
+ }}%%
32
+ sequenceDiagram
33
+ participant Dev as Developer
34
+ participant CLI as prompts CLI
35
+ participant FS as File System
36
+
37
+ Dev->>CLI: prompts generate
38
+ CLI->>FS: Discover .prompt files from --includes globs
39
+ CLI->>CLI: Parse frontmatter + extract variables
40
+ CLI->>CLI: Lint (schema vs template match)
41
+ CLI->>CLI: Flatten partials
42
+ CLI->>FS: Write generated .ts modules
43
+ FS-->>Dev: Import typed prompts from ~prompts
44
+ ```
45
+
46
+ ## Commands Reference
47
+
48
+ ### `prompts generate`
49
+
50
+ Generate typed TypeScript modules from `.prompt` files.
51
+
52
+ **Alias:** `gen`
53
+
54
+ | Flag | Alias | Required | Description |
55
+ | ------------ | ----- | -------- | ----------------------------------------- |
56
+ | `--out` | `-o` | Yes | Output directory for generated files |
57
+ | `--includes` | `-r` | Yes | Glob patterns to scan for `.prompt` files |
58
+ | `--silent` | --- | No | Suppress output except errors |
59
+
60
+ ```bash
61
+ prompts generate --out .prompts/client --includes "prompts/**" "src/agents/**" "src/workflows/**"
62
+ ```
63
+
64
+ Custom partials are auto-discovered from the sibling `partials/` directory (relative to `--out`).
65
+
66
+ Runs lint validation automatically before generating. Exits with code 1 on lint errors.
67
+
68
+ ### `prompts lint`
69
+
70
+ Validate `.prompt` files without generating output.
71
+
72
+ | Flag | Alias | Required | Description |
73
+ | ------------ | ----- | -------- | -------------------------------------------------------- |
74
+ | `--includes` | `-r` | Yes | Glob patterns to scan for `.prompt` files |
75
+ | `--partials` | `-p` | No | Custom partials directory (default: `.prompts/partials`) |
76
+ | `--silent` | --- | No | Suppress output except errors |
77
+
78
+ **Diagnostics:**
79
+
80
+ | Level | Meaning |
81
+ | ----- | ---------------------------------------- |
82
+ | Error | Template variable not declared in schema |
83
+ | Warn | Schema variable not used in template |
84
+
85
+ ```bash
86
+ prompts lint --includes "prompts/**" "src/agents/**"
87
+ ```
88
+
89
+ ### `prompts create`
90
+
91
+ Scaffold a new `.prompt` file.
92
+
93
+ | Arg/Flag | Required | Description |
94
+ | ----------- | -------- | ------------------------------------------------------------- |
95
+ | `<name>` | Yes | Prompt name (kebab-case) |
96
+ | `--out` | No | Output directory (defaults to cwd) |
97
+ | `--partial` | No | Create as a partial in `.prompts/partials/` (ignores `--out`) |
98
+
99
+ ```bash
100
+ prompts create coverage-assessor --out src/agents/coverage-assessor
101
+ prompts create summary --partial
102
+ ```
103
+
104
+ ### `prompts setup`
105
+
106
+ Interactive project configuration for `.prompt` development. No flags -- fully interactive.
107
+
108
+ Configures:
109
+
110
+ 1. VSCode file association (`*.prompt` -> Markdown)
111
+ 2. VSCode Liquid extension recommendation
112
+ 3. `.gitignore` entry for generated `.prompts/client/` directory
113
+ 4. `tsconfig.json` path alias (`~prompts` -> `./.prompts/client/index.ts`)
114
+
115
+ ## Integration
116
+
117
+ Add a generate script to your `package.json`:
118
+
119
+ ```json
120
+ {
121
+ "scripts": {
122
+ "prompts:generate": "prompts generate --out .prompts/client --includes \"prompts/**\" \"src/agents/**\""
123
+ }
124
+ }
125
+ ```
126
+
127
+ ## References
128
+
129
+ - [File Format](file-format.md)
130
+ - [Code Generation & Library](codegen.md)
131
+ - [Setup](setup.md)
132
+ - [Troubleshooting](troubleshooting.md)
@@ -0,0 +1,142 @@
1
+ # Code Generation & Library
2
+
3
+ The CLI transforms `.prompt` source files into typed TypeScript modules. This doc covers the pipeline stages, generated output shape, and the runtime library API.
4
+
5
+ ## Pipeline
6
+
7
+ ```mermaid
8
+ %%{init: {
9
+ 'theme': 'base',
10
+ 'themeVariables': {
11
+ 'primaryColor': '#313244',
12
+ 'primaryTextColor': '#cdd6f4',
13
+ 'primaryBorderColor': '#6c7086',
14
+ 'lineColor': '#89b4fa',
15
+ 'secondaryColor': '#45475a',
16
+ 'tertiaryColor': '#1e1e2e',
17
+ 'background': '#1e1e2e',
18
+ 'mainBkg': '#313244',
19
+ 'clusterBkg': '#1e1e2e',
20
+ 'clusterBorder': '#45475a'
21
+ },
22
+ 'flowchart': { 'curve': 'basis', 'padding': 15 }
23
+ }}%%
24
+ flowchart TD
25
+ classDef core fill:#313244,stroke:#89b4fa,stroke-width:2px,color:#cdd6f4
26
+ classDef agent fill:#313244,stroke:#a6e3a1,stroke-width:2px,color:#cdd6f4
27
+
28
+ subgraph Per Prompt
29
+ A[discoverPrompts]:::core --> B[parseFrontmatter]:::core
30
+ B --> C[clean]:::core
31
+ C --> D[flattenPartials]:::core
32
+ D --> E[extractVariables]:::core
33
+ E --> F[lintPrompt]:::core
34
+ end
35
+
36
+ subgraph Output
37
+ F --> G[generatePromptModule]:::agent
38
+ F --> H[generateRegistry]:::agent
39
+ end
40
+ ```
41
+
42
+ ## Pipeline Stages
43
+
44
+ | Stage | Input | Output | Description |
45
+ | ----------------- | ---------------------------- | ---------------------------------- | ------------------------------------------------------- |
46
+ | Discover | Root directories | `DiscoveredPrompt[]` | Scans for `.prompt` files (max depth 5) |
47
+ | Parse Frontmatter | Raw file content | `{ name, group, version, schema }` | Extracts and validates YAML metadata |
48
+ | Clean | Raw content | Template string | Strips frontmatter delimiters |
49
+ | Flatten Partials | Template with `{% render %}` | Resolved template | Inlines partial content with bound params |
50
+ | Extract Variables | Template string | `string[]` | Finds `{{ var }}`, `{% if var %}`, `{% for x in var %}` |
51
+ | Lint | Schema + variables | Diagnostics | Checks schema/template variable alignment |
52
+
53
+ ## Generated Output
54
+
55
+ ### Per-Prompt Module (`<name>.ts`)
56
+
57
+ Each module exports a default object conforming to `PromptModule`:
58
+
59
+ | Member | Type | Description |
60
+ | --------------------- | ------------------------ | ------------------------------------------------ |
61
+ | `name` | `string` (const) | Prompt name from frontmatter |
62
+ | `group` | `string \| undefined` | Optional grouping key |
63
+ | `schema` | `ZodObject` | Zod schema built from frontmatter `schema` block |
64
+ | `render(variables)` | `(Variables) => string` | Validates input then renders via LiquidJS |
65
+ | `validate(variables)` | `(unknown) => Variables` | Zod parse only |
66
+
67
+ ### Registry (`index.ts`)
68
+
69
+ Aggregates all per-prompt modules into a single entry point:
70
+
71
+ | Export | Type | Description |
72
+ | --------- | --------------------- | ------------------------------------------------------------------------------------------------------ |
73
+ | `prompts` | `PromptRegistry<...>` | Deep-frozen const object with dot-access, nested by group. Use `typeof prompts` for type-level access. |
74
+
75
+ ## Output Directory
76
+
77
+ Generated files go to the `--out` directory (conventionally `.prompts/client/`). This subdirectory should be gitignored. The parent `.prompts/` directory also holds `partials/` for custom partials (committed to git). Import generated code via the `~prompts` tsconfig alias.
78
+
79
+ ## Runtime Library API
80
+
81
+ The library surface provides the runtime engine and registry used by generated code and consuming packages.
82
+
83
+ ### Exports
84
+
85
+ | Export | Type | Description |
86
+ | ---------------------- | ----------------------------------- | -------------------------------------------------------------------------- |
87
+ | `engine` | `Liquid` | Shared LiquidJS instance (no filesystem, strict filters) |
88
+ | `createEngine` | `(partialsDir, options?) => Liquid` | Factory for filesystem-backed engines (used by CLI for partial resolution) |
89
+ | `clean` | `(content: string) => string` | Strips frontmatter, returns render-ready template |
90
+ | `createPromptRegistry` | `(modules) => PromptRegistry` | Creates typed registry from prompt module map |
91
+
92
+ ### Engine
93
+
94
+ The shared `engine` instance is configured with `ownPropertyOnly: true` and `strictFilters: true` for security. No filesystem access -- templates are rendered from strings via `parseAndRenderSync`.
95
+
96
+ `createEngine` accepts a `partialsDir` and optional overrides. It enables filesystem-backed partial resolution (`.prompt` extension, caching enabled) for use during codegen flattening.
97
+
98
+ ### Registry
99
+
100
+ `createPromptRegistry` accepts a (possibly nested) record of `PromptModule` objects and namespace nodes. It returns a deep-frozen `PromptRegistry` with direct property access:
101
+
102
+ ```ts
103
+ const prompts = createPromptRegistry({
104
+ agents: { coverageAssessor },
105
+ greeting,
106
+ });
107
+ prompts.agents.coverageAssessor.render({ scope: "full" });
108
+ prompts.greeting.render();
109
+ ```
110
+
111
+ Nesting is driven by the `group` field in frontmatter. Each `/`-separated segment becomes a nesting level, with all names converted to camelCase. The registry is frozen at creation time to prevent mutation.
112
+
113
+ ## Consumer Import Pattern
114
+
115
+ The generated `index.ts` calls `createPromptRegistry` with all prompt modules organized by group and exports a `prompts` const object. Consumers import via the `~prompts` tsconfig alias:
116
+
117
+ ```ts
118
+ import { prompts } from "~prompts";
119
+
120
+ // Flat (no group)
121
+ const text = prompts.greeting.render();
122
+
123
+ // Nested (group: agents)
124
+ const text = prompts.agents.coverageAssessor.render({ scope: "full" });
125
+ ```
126
+
127
+ Types are inferred from the object structure, giving full type safety on `render` and `validate` arguments at every nesting level.
128
+
129
+ ## Types Reference
130
+
131
+ | Type | Description |
132
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------- |
133
+ | `PromptModule` | Interface: `name`, `group`, `schema` (ZodType), `render(vars)`, `validate(vars)` |
134
+ | `PromptNamespace` | A nested namespace node -- values are `PromptModule` leaves or further nested namespaces |
135
+ | `PromptRegistry` | Deep-readonly mapped type over a `PromptNamespace` tree |
136
+ | `CreateEngineOptions` | Options for `createEngine`: `root`, `partials`, `extname`, `cache`, `strictFilters`, `strictVariables`, `ownPropertyOnly` |
137
+ | `Liquid` | Re-exported LiquidJS engine type |
138
+
139
+ ## References
140
+
141
+ - [File Format](file-format.md)
142
+ - [CLI](cli.md)
@@ -44,7 +44,7 @@ Names must match `^[a-z0-9-]+$` (lowercase, digits, hyphens). The `name` field i
44
44
 
45
45
  ## Discovery
46
46
 
47
- The CLI scans `--roots` directories recursively (max depth 5). Files must have the `.prompt` extension. Symbolic links are skipped. Duplicate names across roots cause an error with paths listed.
47
+ The CLI scans `--includes` glob patterns recursively (max depth 5). Files must have the `.prompt` extension. Symbolic links are skipped. Duplicate names across roots cause an error with paths listed.
48
48
 
49
49
  Results are sorted alphabetically by name.
50
50
 
@@ -0,0 +1,306 @@
1
+ # .prompt File Format
2
+
3
+ A `.prompt` file is a LiquidJS template with YAML frontmatter. It is a declarative prompt authoring format compiled to typed TypeScript at build time.
4
+
5
+ ## File Anatomy
6
+
7
+ Every `.prompt` file has two sections: a YAML frontmatter block delimited by `---` fences, and a LiquidJS template body.
8
+
9
+ ```text
10
+ ---
11
+ name: coverage-assessor
12
+ group: agents/coverage-assessor
13
+ schema:
14
+ scope:
15
+ type: string
16
+ description: Assessment scope
17
+ target:
18
+ type: string
19
+ required: false
20
+ ---
21
+
22
+ You are a coverage assessor for {{ scope }}.
23
+ {% if target %}Targeting {{ target }} docs.{% endif %}
24
+ ```
25
+
26
+ | Section | Description |
27
+ | ------------------- | ------------------------------------------------------------- |
28
+ | Frontmatter (`---`) | YAML metadata block defining name, group, and variable schema |
29
+ | Body | LiquidJS template rendered at runtime with typed variables |
30
+
31
+ ## Template Syntax
32
+
33
+ | Syntax | Purpose |
34
+ | --------------------------------------- | ----------------------------------- |
35
+ | `{{ var }}` | Variable output |
36
+ | `{{ var \| filter }}` | Filtered output |
37
+ | `{% if var %}...{% endif %}` | Conditional |
38
+ | `{% for item in list %}...{% endfor %}` | Iteration |
39
+ | `{% render 'name', key: 'value' %}` | Partial inclusion (build-time only) |
40
+
41
+ Strict filters are enabled -- unknown filters throw an error. Variable access is restricted to own properties only.
42
+
43
+ ## Frontmatter Reference
44
+
45
+ The YAML frontmatter block defines metadata and the variable schema.
46
+
47
+ ### Fields
48
+
49
+ | Field | Required | Type | Description |
50
+ | --------- | -------- | -------- | ------------------------------------------------ |
51
+ | `name` | Yes | `string` | Unique kebab-case identifier (`^[a-z0-9-]+$`) |
52
+ | `group` | No | `string` | Namespace path (e.g. `agents/coverage-assessor`) |
53
+ | `version` | No | `string` | Version identifier |
54
+ | `schema` | No | `object` | Variable declarations map |
55
+
56
+ ### Validation Rules
57
+
58
+ - `name` is required and must match `^[a-z0-9-]+$`
59
+ - Frontmatter must be valid YAML between `---` delimiters
60
+ - `schema` must be an object (not an array)
61
+ - Missing or empty `name` throws a parse error with the file path
62
+ - Non-object frontmatter (e.g. a bare string) is rejected
63
+
64
+ ## Schema Variables
65
+
66
+ Each key under `schema` declares a template variable. Two syntaxes are supported.
67
+
68
+ **Shorthand** -- type string only, defaults to required:
69
+
70
+ ```yaml
71
+ schema:
72
+ scope: string
73
+ ```
74
+
75
+ **Full object** -- explicit control over all fields:
76
+
77
+ ```yaml
78
+ schema:
79
+ scope:
80
+ type: string
81
+ required: true
82
+ description: Assessment scope
83
+ ```
84
+
85
+ Shorthand `scope: string` expands to `{ type: 'string', required: true }`.
86
+
87
+ ### Variable Fields
88
+
89
+ | Field | Default | Description |
90
+ | ------------- | -------- | ---------------------------------------------------- |
91
+ | `type` | `string` | Variable type (only `string` supported) |
92
+ | `required` | `true` | Whether the variable must be provided at render time |
93
+ | `description` | -- | Human-readable description (used in generated JSDoc) |
94
+
95
+ ## Naming and Discovery
96
+
97
+ Names must match `^[a-z0-9-]+$` (lowercase, digits, hyphens). The `name` field in frontmatter is required and takes precedence. A file named `prompt.prompt` derives its name from the parent directory (e.g. `agents/gap-detector/prompt.prompt` becomes `gap-detector`).
98
+
99
+ The CLI scans `--includes` glob patterns recursively (max depth 5). Files must have the `.prompt` extension. Symbolic links are skipped. Duplicate names across roots cause an error with paths listed. Results are sorted alphabetically by name.
100
+
101
+ ### Recommended File Structure
102
+
103
+ ```text
104
+ src/
105
+ agents/
106
+ coverage-assessor/
107
+ prompt.prompt
108
+ prompts/
109
+ identity.prompt
110
+ constraints.prompt
111
+ ```
112
+
113
+ ## Partials
114
+
115
+ Partials are reusable template fragments included with `{% render %}` tags. They are resolved and flattened at build time -- the generated output contains no render tags.
116
+
117
+ ### Syntax
118
+
119
+ ```liquid
120
+ {% render 'identity', role: 'Coverage Assessor', desc: 'an expert at assessing documentation coverage' %}
121
+ ```
122
+
123
+ Only literal string parameters are supported. Variable references (e.g. `key: myVar`) are not allowed and throw an error at codegen time. Whitespace trim variants `{%-` and `-%}` are supported.
124
+
125
+ ### Resolution Order
126
+
127
+ Partials are resolved from two locations, searched in order (first match wins):
128
+
129
+ | Priority | Location | Description |
130
+ | -------- | -------------------- | ------------------------------------------------ |
131
+ | 1 | `.prompts/partials/` | Custom project partials (committed to git) |
132
+ | 2 | SDK `src/prompts/` | Built-in partials shipped with `@funkai/prompts` |
133
+
134
+ Custom partials take precedence -- a custom partial with the same name as a built-in overrides it.
135
+
136
+ ### Built-in Partials
137
+
138
+ | Partial | Parameters | Purpose |
139
+ | ------------- | -------------------------------------------------- | --------------------------------------------------- |
140
+ | `identity` | `role`, `desc`, `context` (optional) | Agent identity block (`<identity>` wrapper) |
141
+ | `constraints` | `in_scope`, `out_of_scope`, `rules` (all optional) | Scoping constraints block (`<constraints>` wrapper) |
142
+ | `tools` | `tools` (optional) | Tool listing block (`<tools>` wrapper) |
143
+
144
+ **identity** source:
145
+
146
+ ```liquid
147
+ <identity>
148
+ You are {{ role }}, {{ desc }}.
149
+ {% if context %}
150
+ {{ context }}
151
+ {% endif %}
152
+ </identity>
153
+ ```
154
+
155
+ **constraints** source:
156
+
157
+ ```liquid
158
+ <constraints>
159
+ {% if in_scope %}
160
+ ## In Scope
161
+ {% for item in in_scope %}
162
+ - {{ item }}
163
+ {% endfor %}
164
+ {% endif %}
165
+ {% if out_of_scope %}
166
+ ## Out of Scope
167
+ {% for item in out_of_scope %}
168
+ - {{ item }}
169
+ {% endfor %}
170
+ {% endif %}
171
+ {% if rules %}
172
+ ## Rules
173
+ {% for rule in rules %}
174
+ - {{ rule }}
175
+ {% endfor %}
176
+ {% endif %}
177
+ </constraints>
178
+ ```
179
+
180
+ ### Custom Partials
181
+
182
+ Place custom `.prompt` files in `.prompts/partials/`:
183
+
184
+ ```text
185
+ .prompts/
186
+ client/ # Generated (gitignored)
187
+ partials/ # Custom partials (committed)
188
+ summary.prompt
189
+ ```
190
+
191
+ The CLI auto-discovers this directory:
192
+
193
+ - `prompts generate` derives it from `--out` (sibling `partials/` dir)
194
+ - `prompts lint` defaults to `.prompts/partials` (configurable via `--partials`)
195
+
196
+ **Creating a custom partial:**
197
+
198
+ ```bash
199
+ prompts create summary --partial
200
+ ```
201
+
202
+ Or create `.prompts/partials/<name>.prompt` by hand:
203
+
204
+ ```liquid
205
+ <summary>
206
+ {{ content }}
207
+ {% if notes %}
208
+ Notes: {{ notes }}
209
+ {% endif %}
210
+ </summary>
211
+ ```
212
+
213
+ Use it in a `.prompt` file:
214
+
215
+ ```liquid
216
+ {% render 'summary', content: 'Analysis complete' %}
217
+ ```
218
+
219
+ Run `prompts generate` -- the partial is flattened into the generated output. No `{% render %}` tags remain.
220
+
221
+ **Overriding built-ins:** Create a file with the same name in `.prompts/partials/` (e.g. `.prompts/partials/identity.prompt`). Custom partials take precedence over SDK built-ins.
222
+
223
+ **Adding a built-in partial (SDK contributors):**
224
+
225
+ 1. Create `packages/prompts/src/prompts/<name>.prompt`
226
+ 2. Write the partial template using XML-style wrapper tags and Liquid variables
227
+ 3. Test with a consumer `.prompt` file and run `prompts generate`
228
+
229
+ ## Authoring Walkthrough
230
+
231
+ ### Prerequisites
232
+
233
+ - `@funkai/prompts` installed
234
+ - Project configured ([Setup guide](setup.md))
235
+
236
+ ### Steps
237
+
238
+ 1. **Scaffold** with the CLI:
239
+
240
+ ```bash
241
+ prompts create my-agent --out src/agents/my-agent
242
+ ```
243
+
244
+ 2. **Edit** the frontmatter -- set `name`, `group`, and `schema` variables.
245
+
246
+ 3. **Write** the template body using `{{ var }}` syntax and conditionals.
247
+
248
+ 4. **Add partials** if needed:
249
+
250
+ ```liquid
251
+ {% render 'identity', role: 'Analyzer', desc: 'a code analyzer' %}
252
+ ```
253
+
254
+ 5. **Lint:**
255
+
256
+ ```bash
257
+ prompts lint --includes "src/agents/**"
258
+ ```
259
+
260
+ 6. **Generate:**
261
+
262
+ ```bash
263
+ prompts generate --out .prompts/client --includes "src/agents/**"
264
+ ```
265
+
266
+ 7. **Import and use:**
267
+
268
+ ```ts
269
+ import { prompts } from "~prompts";
270
+
271
+ const text = prompts.myAgent.render({ scope: "full" });
272
+ ```
273
+
274
+ ### Verification
275
+
276
+ - `prompts lint` reports no errors
277
+ - Generated file exists at `.prompts/client/my-agent.ts`
278
+ - TypeScript compiles without errors
279
+
280
+ ### Troubleshooting
281
+
282
+ #### Undefined variable error
283
+
284
+ **Fix:** Add the variable to the frontmatter `schema` block.
285
+
286
+ #### Duplicate prompt name
287
+
288
+ **Fix:** Two `.prompt` files share the same `name` -- rename one to a unique kebab-case identifier.
289
+
290
+ #### TypeScript can't find `~prompts`
291
+
292
+ **Fix:** Run `prompts setup` or add the path alias to `tsconfig.json`. See [setup.md](setup.md).
293
+
294
+ #### Variable reference not supported in partial
295
+
296
+ **Fix:** Only literal string params are allowed in `{% render %}` tags. Replace variable references with string literals.
297
+
298
+ #### Partial not found
299
+
300
+ **Fix:** Verify the file is in `.prompts/partials/` (custom) or `src/prompts/` (built-in) with `.prompt` extension.
301
+
302
+ ## References
303
+
304
+ - [Code Generation & Library](codegen.md)
305
+ - [CLI](cli.md)
306
+ - [Setup](setup.md)
@@ -26,13 +26,13 @@ prompts create my-agent --out src/agents/my-agent
26
26
  5. Lint:
27
27
 
28
28
  ```bash
29
- prompts lint --roots src/agents
29
+ prompts lint --includes "src/agents/**"
30
30
  ```
31
31
 
32
32
  6. Generate:
33
33
 
34
34
  ```bash
35
- prompts generate --out .prompts/client --roots src/agents
35
+ prompts generate --out .prompts/client --includes "src/agents/**"
36
36
  ```
37
37
 
38
38
  7. Import and use:
@@ -50,7 +50,7 @@ Or configure manually (steps 3-6).
50
50
  ```json
51
51
  {
52
52
  "scripts": {
53
- "prompts:generate": "prompts generate --out .prompts/client --roots prompts src/agents"
53
+ "prompts:generate": "prompts generate --out .prompts/client --includes \"prompts/**\" \"src/agents/**\""
54
54
  }
55
55
  }
56
56
  ```
package/docs/overview.md CHANGED
@@ -1,4 +1,4 @@
1
- # Prompts SDK Overview
1
+ # Prompts SDK
2
2
 
3
3
  Prompt authoring SDK with two surfaces: a **CLI** for build-time code generation from `.prompt` files, and a **library** for runtime template rendering with full type safety.
4
4
 
@@ -51,28 +51,6 @@ flowchart LR
51
51
  classDef gateway fill:#313244,stroke:#fab387,stroke-width:2px,color:#cdd6f4
52
52
  ```
53
53
 
54
- ## Package Structure
55
-
56
- ```
57
- 📁 packages/prompts/
58
- ├── 📁 src/
59
- │ ├── 📁 prompts/ # Built-in partials (identity, constraints, tools)
60
- │ ├── 📄 engine.ts # LiquidJS engine factory
61
- │ ├── 📄 registry.ts # Typed prompt registry
62
- │ ├── 📄 clean.ts # Frontmatter stripping pipeline
63
- │ ├── 📄 partials-dir.ts # PARTIALS_DIR export for CLI/consumers
64
- │ ├── 📄 types.ts # PromptModule, PromptNamespace, PromptRegistry types
65
- │ └── 📄 index.ts # Public exports
66
- └── 📁 docs/
67
-
68
- 📁 packages/cli/ # @funkai/cli — CLI binary (see @funkai/cli README)
69
- ├── 📁 commands/ # generate, lint, create, setup
70
- ├── 📁 src/lib/ # codegen, frontmatter, flatten, lint, paths
71
- └── 📄 index.ts # CLI entry point (kidd-cli)
72
- ```
73
-
74
- > **Note:** The CLI was extracted to `@funkai/cli`. Install it separately for the `prompts` binary.
75
-
76
54
  ## Dual Surface
77
55
 
78
56
  | Surface | When | What |
@@ -83,20 +61,15 @@ flowchart LR
83
61
  ## Quick Start
84
62
 
85
63
  1. Create a `.prompt` file with YAML frontmatter and a LiquidJS template body.
86
- 2. Run `prompts generate --out .prompts/client --roots src/agents` to produce typed modules.
64
+ 2. Run `prompts generate --out .prompts/client --includes "src/agents/**"` to produce typed modules.
87
65
  3. Import from the `~prompts` alias in your application code.
88
66
  4. Call `.render({ vars })` with full type safety derived from the Zod schema in frontmatter.
89
67
 
90
- ## References
68
+ ## Documentation
91
69
 
92
- - [File Format](file-format/overview.md)
93
- - [Frontmatter](file-format/frontmatter.md)
94
- - [Partials](file-format/partials.md)
95
- - [CLI](cli/overview.md)
96
- - [CLI Commands](cli/commands.md)
97
- - [Code Generation](codegen/overview.md)
98
- - [Library API](library/overview.md)
99
- - [Guide: Author a Prompt](guides/author-prompt.md)
100
- - [Guide: Setup Project](guides/setup-project.md)
101
- - [Guide: Add a Partial](guides/add-partial.md)
102
- - [Troubleshooting](troubleshooting.md)
70
+ | Topic | Description |
71
+ | --------------------------------------- | ------------------------------------------------------------------------- |
72
+ | [File Format](file-format.md) | .prompt anatomy, frontmatter, schema variables, partials, authoring guide |
73
+ | [Code Generation & Library](codegen.md) | Build pipeline, generated output, runtime API, consumer patterns |
74
+ | [Project Setup](setup.md) | VSCode, .gitignore, tsconfig, package.json configuration |
75
+ | [Troubleshooting](troubleshooting.md) | Common errors and fixes |
package/docs/setup.md ADDED
@@ -0,0 +1,76 @@
1
+ # Setup Prompt Development
2
+
3
+ ## Prerequisites
4
+
5
+ - Node 24
6
+ - pnpm workspace
7
+
8
+ ## Steps
9
+
10
+ 1. Install:
11
+
12
+ ```bash
13
+ pnpm add @funkai/prompts --workspace
14
+ ```
15
+
16
+ 2. Run interactive setup:
17
+
18
+ ```bash
19
+ prompts setup
20
+ ```
21
+
22
+ Or configure manually (steps 3-6).
23
+
24
+ 3. Add VSCode file association in `.vscode/settings.json`:
25
+
26
+ ```json
27
+ {
28
+ "files.associations": {
29
+ "*.prompt": "markdown"
30
+ }
31
+ }
32
+ ```
33
+
34
+ 4. Add `.prompts/client/` to `.gitignore`.
35
+
36
+ 5. Add `~prompts` path alias to `tsconfig.json`:
37
+
38
+ ```json
39
+ {
40
+ "compilerOptions": {
41
+ "paths": {
42
+ "~prompts": ["./.prompts/client/index.ts"]
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ 6. Add generate script to `package.json`:
49
+
50
+ ```json
51
+ {
52
+ "scripts": {
53
+ "prompts:generate": "prompts generate --out .prompts/client --includes \"prompts/**\" \"src/agents/**\""
54
+ }
55
+ }
56
+ ```
57
+
58
+ ## Verification
59
+
60
+ Run `prompts generate` and verify `.prompts/client/` directory is created with an `index.ts`.
61
+
62
+ ## Troubleshooting
63
+
64
+ ### VSCode not highlighting `.prompt` files
65
+
66
+ **Fix:** Check `.vscode/settings.json` file association is set correctly.
67
+
68
+ ### TypeScript can't resolve `~prompts`
69
+
70
+ **Fix:** Verify `tsconfig.json` paths alias points to `./.prompts/client/index.ts`.
71
+
72
+ ## References
73
+
74
+ - [CLI](cli.md)
75
+ - [File Format](file-format.md)
76
+ - [Code Generation & Library](codegen.md)
@@ -14,7 +14,7 @@
14
14
 
15
15
  ## Invalid prompt name
16
16
 
17
- **Fix:** Names must match `^[a-z0-9-]+$` lowercase letters, digits, and hyphens only.
17
+ **Fix:** Names must match `^[a-z0-9-]+$` -- lowercase letters, digits, and hyphens only.
18
18
 
19
19
  ## Partial variable reference error
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funkai/prompts",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "private": false,
5
5
  "description": "Prompt SDK with LiquidJS templating and Zod validation",
6
6
  "keywords": [
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "es-toolkit": "^1.45.1",
45
- "liquidjs": "^10.25.0",
45
+ "liquidjs": "^10.25.1",
46
46
  "zod": "^4.3.6"
47
47
  },
48
48
  "devDependencies": {
package/src/engine.ts CHANGED
@@ -21,6 +21,7 @@ export function createEngine(partialsDir: string, options?: Partial<CreateEngine
21
21
  ...options,
22
22
  // Safety defaults — applied after spread so callers cannot disable them
23
23
  strictFilters: true,
24
+ strictVariables: true,
24
25
  ownPropertyOnly: true,
25
26
  });
26
27
  }
@@ -36,5 +37,6 @@ export function createEngine(partialsDir: string, options?: Partial<CreateEngine
36
37
  */
37
38
  export const liquidEngine = new Liquid({
38
39
  strictFilters: true,
40
+ strictVariables: true,
39
41
  ownPropertyOnly: true,
40
42
  });
package/src/prompt.ts CHANGED
@@ -33,12 +33,26 @@ export function createPrompt<T>(config: PromptConfig<T>): PromptModule<T> {
33
33
  name,
34
34
  group,
35
35
  schema,
36
+ /**
37
+ * Render the prompt template with the given variables.
38
+ *
39
+ * @param variables - Template variables matching the prompt schema.
40
+ * @returns The rendered prompt string.
41
+ * @throws {ZodError} If variables fail schema validation.
42
+ */
36
43
  render(variables: T): string {
37
44
  return liquidEngine.parseAndRenderSync(
38
45
  template,
39
46
  schema.parse(variables) as Record<string, unknown>,
40
47
  );
41
48
  },
49
+ /**
50
+ * Validate variables against the prompt schema.
51
+ *
52
+ * @param variables - Variables to validate.
53
+ * @returns The parsed and validated variables.
54
+ * @throws {ZodError} If variables fail schema validation.
55
+ */
42
56
  validate(variables: unknown): T {
43
57
  return schema.parse(variables);
44
58
  },
package/src/registry.ts CHANGED
@@ -55,16 +55,16 @@ function isPromptModule(value: unknown): value is PromptModule {
55
55
  * @private
56
56
  */
57
57
  function deepFreeze<T extends PromptNamespace>(obj: T): PromptRegistry<T> {
58
- Object.freeze(obj);
59
- for (const value of Object.values(obj)) {
60
- if (
61
- typeof value === "object" &&
62
- value !== null &&
63
- !Object.isFrozen(value) &&
64
- !isPromptModule(value)
65
- ) {
66
- deepFreeze(value as PromptNamespace);
58
+ const copied = Object.entries(obj).reduce<Record<string, unknown>>((acc, [key, value]) => {
59
+ const isNamespace = typeof value === "object" && value !== null && !isPromptModule(value);
60
+ if (isNamespace) {
61
+ acc[key] = deepFreeze({ ...value } as PromptNamespace);
62
+ } else {
63
+ acc[key] = value;
67
64
  }
68
- }
69
- return obj as PromptRegistry<T>;
65
+ return acc;
66
+ }, {});
67
+
68
+ Object.freeze(copied);
69
+ return copied as PromptRegistry<T>;
70
70
  }
package/tsconfig.json CHANGED
@@ -5,21 +5,28 @@
5
5
  "module": "NodeNext",
6
6
  "moduleResolution": "NodeNext",
7
7
  "lib": ["ES2024"],
8
+ "types": ["node"],
9
+
8
10
  "strict": true,
9
- "esModuleInterop": true,
11
+ "noUncheckedIndexedAccess": true,
12
+ "exactOptionalPropertyTypes": true,
13
+ "noFallthroughCasesInSwitch": true,
14
+ "noPropertyAccessFromIndexSignature": true,
15
+
16
+ "verbatimModuleSyntax": true,
17
+ "resolveJsonModule": true,
10
18
  "skipLibCheck": true,
11
19
  "forceConsistentCasingInFileNames": true,
12
- "resolveJsonModule": true,
13
- "isolatedModules": true,
20
+
14
21
  "declaration": true,
15
22
  "declarationMap": true,
16
23
  "sourceMap": true,
24
+
17
25
  "outDir": "./dist",
18
26
  "rootDir": ".",
19
27
  "paths": {
20
28
  "@/*": ["./src/*"]
21
- },
22
- "types": ["node"]
29
+ }
23
30
  },
24
31
  "include": ["src"],
25
32
  "exclude": ["node_modules", "dist"]