@funkai/prompts 0.2.0 → 0.3.0

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,28 +1,28 @@
1
1
 
2
- > @funkai/prompts@0.2.0 build /home/runner/work/funkai/funkai/packages/prompts
2
+ > @funkai/prompts@0.3.0 build /home/runner/work/funkai/funkai/packages/prompts
3
3
  > tsdown && cp -r src/prompts dist/prompts
4
4
 
5
- ℹ tsdown v0.21.3 powered by rolldown v1.0.0-rc.9
5
+ ℹ tsdown v0.21.4 powered by rolldown v1.0.0-rc.9
6
6
  ℹ config file: /home/runner/work/funkai/funkai/packages/prompts/tsdown.config.ts
7
7
  ℹ entry: src/index.ts, src/runtime.ts, src/cli.ts
8
8
  ℹ target: node22
9
9
  ℹ tsconfig: tsconfig.json
10
10
  ℹ Build start
11
- ℹ dist/lib/index.mjs 1.66 kB │ gzip: 0.80 kB
12
- ℹ dist/lib/cli.mjs 1.10 kB │ gzip: 0.63 kB
11
+ ℹ dist/lib/index.mjs 1.70 kB │ gzip: 0.82 kB
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 2.48 kB │ gzip: 1.12 kB
15
- ℹ dist/lib/engine-B53whAlC.mjs.map 1.57 kB │ gzip: 0.79 kB
16
- ℹ dist/lib/cli.mjs.map 1.37 kB │ gzip: 0.75 kB
17
- ℹ dist/lib/engine-B53whAlC.mjs 1.15 kB │ gzip: 0.61 kB
18
- ℹ dist/lib/types-CiSdNM0W.d.mts.map 0.53 kB │ gzip: 0.29 kB
19
- ℹ dist/lib/engine-B6JHetwb.d.mts.map 0.22 kB │ gzip: 0.18 kB
20
- ℹ dist/lib/index.d.mts.map 0.19 kB │ gzip: 0.16 kB
21
- ℹ dist/lib/cli.d.mts.map 0.17 kB │ gzip: 0.15 kB
14
+ ℹ dist/lib/index.mjs.map 2.71 kB │ gzip: 1.16 kB
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
18
+ ℹ dist/lib/types-2PI_9h-M.d.mts.map 0.53 kB │ gzip: 0.29 kB
19
+ ℹ dist/lib/engine-CQfJbVz6.d.mts.map 0.22 kB │ gzip: 0.18 kB
20
+ ℹ dist/lib/index.d.mts.map 0.19 kB │ gzip: 0.15 kB
21
+ ℹ dist/lib/cli.d.mts.map 0.18 kB │ gzip: 0.15 kB
22
22
  ℹ dist/lib/index.d.mts 1.04 kB │ gzip: 0.54 kB
23
- ℹ dist/lib/cli.d.mts 0.68 kB │ gzip: 0.41 kB
23
+ ℹ dist/lib/cli.d.mts 0.94 kB │ gzip: 0.53 kB
24
24
  ℹ dist/lib/runtime.d.mts 0.08 kB │ gzip: 0.08 kB
25
- ℹ dist/lib/types-CiSdNM0W.d.mts 1.47 kB │ gzip: 0.71 kB
26
- ℹ dist/lib/engine-B6JHetwb.d.mts 1.06 kB │ gzip: 0.57 kB
27
- ℹ 16 files, total: 14.86 kB
28
- ✔ Build complete in 1822ms
25
+ ℹ dist/lib/types-2PI_9h-M.d.mts 1.43 kB │ gzip: 0.69 kB
26
+ ℹ dist/lib/engine-CQfJbVz6.d.mts 1.08 kB │ gzip: 0.58 kB
27
+ ℹ 16 files, total: 16.28 kB
28
+ ✔ Build complete in 1612ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @funkai/prompts
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b1e1d01: Fix bug and correctness issues across all packages.
8
+
9
+ **@funkai/agents**
10
+
11
+ - Export `createFlowEngine` and related types (`FlowEngineConfig`, `FlowFactory`, `CustomStepFactory`, `CustomStepDefinitions`, `TypedCustomSteps`)
12
+ - Guard `resolvedInput` before passing to `onError` hooks (was `undefined` cast as `TInput`)
13
+ - Fix `onStepStart` hook asymmetry: per-call override now merged with config hook
14
+ - Remove deprecated unused `TraceType` alias
15
+
16
+ **@funkai/prompts**
17
+
18
+ - **Breaking:** `strictFilters` and `ownPropertyOnly` removed from `CreateEngineOptions` (now enforced as non-overridable safety defaults)
19
+
20
+ **@funkai/models**
21
+
22
+ - Add `provider/model` format validation for model IDs
23
+ - Wrap `languageModel()` errors with model ID context
24
+
25
+ **@funkai/cli**
26
+
27
+ - Fix `$`-substitution bug in `flattenPartials` (`String.replace` function-form)
28
+ - Add error context for partial render failures
29
+ - `readJsonFile` now throws on malformed JSON instead of silently returning `{}`
30
+ - Replace naive YAML line parsing with proper `yaml` parser
31
+ - Extract `Liquid` engine to module-level singleton in `extractVariables`
32
+
3
33
  ## 0.2.0
4
34
 
5
35
  ### Minor Changes
@@ -1,5 +1,5 @@
1
- import { n as Liquid, t as CreateEngineOptions } from "./types-CiSdNM0W.mjs";
2
- import { t as createEngine } from "./engine-B6JHetwb.mjs";
1
+ import { n as Liquid, t as CreateEngineOptions } from "./types-2PI_9h-M.mjs";
2
+ import { t as createEngine } from "./engine-CQfJbVz6.mjs";
3
3
 
4
4
  //#region src/clean.d.ts
5
5
  /**
@@ -7,6 +7,14 @@ import { t as createEngine } from "./engine-B6JHetwb.mjs";
7
7
  *
8
8
  * Runs the source through a pipeline of transforms — currently
9
9
  * strips frontmatter, with more steps added over time.
10
+ *
11
+ * @param text - Raw `.prompt` file content including frontmatter.
12
+ * @returns The cleaned template string, ready for rendering.
13
+ * @example
14
+ * ```ts
15
+ * const template = clean("---\nname: greeting\n---\nHello {{ name }}!");
16
+ * // "Hello {{ name }}!"
17
+ * ```
10
18
  */
11
19
  declare function clean(text: string): string;
12
20
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.mts","names":[],"sources":["../../src/clean.ts","../../src/partials-dir.ts"],"mappings":";;;;;;;;AAsBA;;iBAAgB,KAAA,CAAM,IAAA;;;;cClBT,YAAA"}
1
+ {"version":3,"file":"cli.d.mts","names":[],"sources":["../../src/clean.ts","../../src/partials-dir.ts"],"mappings":";;;;;;;;AAkBA;;;;;;;;ACdA;;iBDcgB,KAAA,CAAM,IAAA;;;;cCdT,YAAA"}
package/dist/lib/cli.mjs CHANGED
@@ -1,28 +1,39 @@
1
- import { t as createEngine } from "./engine-B53whAlC.mjs";
1
+ import { t as createEngine } from "./engine-CPKs9QbX.mjs";
2
2
  import { flow } from "es-toolkit";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/clean.ts
6
6
  const FRONTMATTER_RE = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/;
7
7
  /**
8
+ * Clean a raw `.prompt` file into a render-ready template.
9
+ *
10
+ * Runs the source through a pipeline of transforms — currently
11
+ * strips frontmatter, with more steps added over time.
12
+ *
13
+ * @param text - Raw `.prompt` file content including frontmatter.
14
+ * @returns The cleaned template string, ready for rendering.
15
+ * @example
16
+ * ```ts
17
+ * const template = clean("---\nname: greeting\n---\nHello {{ name }}!");
18
+ * // "Hello {{ name }}!"
19
+ * ```
20
+ */
21
+ function clean(text) {
22
+ return pipeline(text);
23
+ }
24
+ /**
8
25
  * Remove YAML frontmatter from the beginning of a string.
9
26
  *
10
27
  * Frontmatter is a block delimited by `---` at the start of the file.
11
28
  * If no frontmatter is present, the string is returned unchanged.
29
+ *
30
+ * @private
12
31
  */
13
32
  function stripFrontmatter(text) {
14
33
  return text.replace(FRONTMATTER_RE, "");
15
34
  }
35
+ /** @private */
16
36
  const pipeline = flow(stripFrontmatter);
17
- /**
18
- * Clean a raw `.prompt` file into a render-ready template.
19
- *
20
- * Runs the source through a pipeline of transforms — currently
21
- * strips frontmatter, with more steps added over time.
22
- */
23
- function clean(text) {
24
- return pipeline(text);
25
- }
26
37
  //#endregion
27
38
  //#region src/partials-dir.ts
28
39
  /** Absolute path to the SDK's built-in partials directory. */
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../../src/clean.ts","../../src/partials-dir.ts"],"sourcesContent":["import { flow } from \"es-toolkit\";\n\nconst FRONTMATTER_RE = /^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?/;\n\n/**\n * Remove YAML frontmatter from the beginning of a string.\n *\n * Frontmatter is a block delimited by `---` at the start of the file.\n * If no frontmatter is present, the string is returned unchanged.\n */\nfunction stripFrontmatter(text: string): string {\n return text.replace(FRONTMATTER_RE, \"\");\n}\n\nconst pipeline = flow(stripFrontmatter);\n\n/**\n * Clean a raw `.prompt` file into a render-ready template.\n *\n * Runs the source through a pipeline of transforms — currently\n * strips frontmatter, with more steps added over time.\n */\nexport function clean(text: string): string {\n return pipeline(text);\n}\n","import { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** Absolute path to the SDK's built-in partials directory. */\nexport const PARTIALS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), \"../prompts\");\n"],"mappings":";;;;;AAEA,MAAM,iBAAiB;;;;;;;AAQvB,SAAS,iBAAiB,MAAsB;AAC9C,QAAO,KAAK,QAAQ,gBAAgB,GAAG;;AAGzC,MAAM,WAAW,KAAK,iBAAiB;;;;;;;AAQvC,SAAgB,MAAM,MAAsB;AAC1C,QAAO,SAAS,KAAK;;;;;ACnBvB,MAAa,eAAe,QAAQ,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EAAE,aAAa"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../../src/clean.ts","../../src/partials-dir.ts"],"sourcesContent":["import { flow } from \"es-toolkit\";\n\nconst FRONTMATTER_RE = /^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?/;\n\n/**\n * Clean a raw `.prompt` file into a render-ready template.\n *\n * Runs the source through a pipeline of transforms currently\n * strips frontmatter, with more steps added over time.\n *\n * @param text - Raw `.prompt` file content including frontmatter.\n * @returns The cleaned template string, ready for rendering.\n * @example\n * ```ts\n * const template = clean(\"---\\nname: greeting\\n---\\nHello {{ name }}!\");\n * // \"Hello {{ name }}!\"\n * ```\n */\nexport function clean(text: string): string {\n return pipeline(text);\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Remove YAML frontmatter from the beginning of a string.\n *\n * Frontmatter is a block delimited by `---` at the start of the file.\n * If no frontmatter is present, the string is returned unchanged.\n *\n * @private\n */\nfunction stripFrontmatter(text: string): string {\n return text.replace(FRONTMATTER_RE, \"\");\n}\n\n/** @private */\nconst pipeline = flow(stripFrontmatter);\n","import { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** Absolute path to the SDK's built-in partials directory. */\nexport const PARTIALS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), \"../prompts\");\n"],"mappings":";;;;;AAEA,MAAM,iBAAiB;;;;;;;;;;;;;;;AAgBvB,SAAgB,MAAM,MAAsB;AAC1C,QAAO,SAAS,KAAK;;;;;;;;;;AAevB,SAAS,iBAAiB,MAAsB;AAC9C,QAAO,KAAK,QAAQ,gBAAgB,GAAG;;;AAIzC,MAAM,WAAW,KAAK,iBAAiB;;;;ACnCvC,MAAa,eAAe,QAAQ,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EAAE,aAAa"}
@@ -16,9 +16,9 @@ function createEngine(partialsDir, options) {
16
16
  partials: [partialsDir],
17
17
  extname: ".prompt",
18
18
  cache: true,
19
+ ...options,
19
20
  strictFilters: true,
20
- ownPropertyOnly: true,
21
- ...options
21
+ ownPropertyOnly: true
22
22
  });
23
23
  }
24
24
  /**
@@ -27,6 +27,8 @@ function createEngine(partialsDir, options) {
27
27
  * Partials are flattened at codegen time by the CLI, so this engine
28
28
  * only needs to handle `{{ var }}` expressions and basic Liquid
29
29
  * control flow (`{% if %}`, `{% for %}`). No filesystem access required.
30
+ *
31
+ * @type {Liquid}
30
32
  */
31
33
  const liquidEngine = new Liquid({
32
34
  strictFilters: true,
@@ -35,4 +37,4 @@ const liquidEngine = new Liquid({
35
37
  //#endregion
36
38
  export { liquidEngine as n, createEngine as t };
37
39
 
38
- //# sourceMappingURL=engine-B53whAlC.mjs.map
40
+ //# sourceMappingURL=engine-CPKs9QbX.mjs.map
@@ -0,0 +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,4 +1,4 @@
1
- import { t as CreateEngineOptions } from "./types-CiSdNM0W.mjs";
1
+ import { t as CreateEngineOptions } from "./types-2PI_9h-M.mjs";
2
2
  import { Liquid } from "liquidjs";
3
3
 
4
4
  //#region src/engine.d.ts
@@ -19,8 +19,10 @@ declare function createEngine(partialsDir: string, options?: Partial<CreateEngin
19
19
  * Partials are flattened at codegen time by the CLI, so this engine
20
20
  * only needs to handle `{{ var }}` expressions and basic Liquid
21
21
  * control flow (`{% if %}`, `{% for %}`). No filesystem access required.
22
+ *
23
+ * @type {Liquid}
22
24
  */
23
25
  declare const liquidEngine: Liquid;
24
26
  //#endregion
25
27
  export { liquidEngine as n, createEngine as t };
26
- //# sourceMappingURL=engine-B6JHetwb.d.mts.map
28
+ //# sourceMappingURL=engine-CQfJbVz6.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine-B6JHetwb.d.mts","names":[],"sources":["../../src/engine.ts"],"mappings":";;;;;;AAcA;;;;;;;;iBAAgB,YAAA,CAAa,WAAA,UAAqB,OAAA,GAAU,OAAA,CAAQ,mBAAA,IAAuB,MAAA;;;;;;;AAmB3F;cAAa,YAAA,EAAY,MAAA"}
1
+ {"version":3,"file":"engine-CQfJbVz6.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,4 +1,4 @@
1
- import { a as PromptRegistry, i as PromptNamespace, r as PromptModule } from "./types-CiSdNM0W.mjs";
1
+ import { a as PromptRegistry, i as PromptNamespace, r as PromptModule } from "./types-2PI_9h-M.mjs";
2
2
 
3
3
  //#region src/registry.d.ts
4
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/registry.ts"],"mappings":";;;;;AA8DA;;;;;;;;;;;;;;;;;iBAAgB,oBAAA,WAA+B,eAAA,CAAA,CAAiB,OAAA,EAAS,CAAA,GAAI,cAAA,CAAe,CAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/registry.ts"],"mappings":";;;;;AAqBA;;;;;;;;;;;;;;;;;iBAAgB,oBAAA,WAA+B,eAAA,CAAA,CAAiB,OAAA,EAAS,CAAA,GAAI,cAAA,CAAe,CAAA"}
@@ -1,31 +1,5 @@
1
1
  //#region src/registry.ts
2
2
  /**
3
- * Check whether a value looks like a PromptModule leaf.
4
- * Leaves have `name`, `schema`, and `render` — namespaces do not.
5
- *
6
- * @private
7
- */
8
- function isPromptModule(value) {
9
- return typeof value === "object" && value !== null && "render" in value && "schema" in value && "name" in value;
10
- }
11
- /**
12
- * Recursively freeze a prompt namespace tree.
13
- * Only recurses into plain namespace nodes — PromptModule leaves
14
- * (which contain Zod schemas) are frozen shallowly.
15
- *
16
- * @param obj - The namespace object to freeze.
17
- * @returns The frozen object cast to its deep-readonly type.
18
- *
19
- * @private
20
- */
21
- function deepFreeze(obj) {
22
- Object.freeze(obj);
23
- Object.values(obj).forEach((value) => {
24
- if (typeof value === "object" && value !== null && !Object.isFrozen(value) && !isPromptModule(value)) deepFreeze(value);
25
- });
26
- return obj;
27
- }
28
- /**
29
3
  * Create a typed, frozen prompt registry from a (possibly nested) map of prompt modules.
30
4
  *
31
5
  * The registry is typically created by generated code — the CLI produces
@@ -47,6 +21,31 @@ function deepFreeze(obj) {
47
21
  function createPromptRegistry(modules) {
48
22
  return deepFreeze({ ...modules });
49
23
  }
24
+ /**
25
+ * Check whether a value looks like a PromptModule leaf.
26
+ * Leaves have `name`, `schema`, and `render` — namespaces do not.
27
+ *
28
+ * @private
29
+ */
30
+ function isPromptModule(value) {
31
+ return typeof value === "object" && value !== null && "render" in value && "schema" in value && "name" in value;
32
+ }
33
+ /**
34
+ * Recursively freeze a prompt namespace tree.
35
+ * Only recurses into plain namespace nodes — PromptModule leaves
36
+ * (which contain Zod schemas) are intentionally left unfrozen
37
+ * to avoid breaking Zod internal state.
38
+ *
39
+ * @param obj - The namespace object to freeze.
40
+ * @returns The frozen object cast to its deep-readonly type.
41
+ *
42
+ * @private
43
+ */
44
+ function deepFreeze(obj) {
45
+ Object.freeze(obj);
46
+ for (const value of Object.values(obj)) if (typeof value === "object" && value !== null && !Object.isFrozen(value) && !isPromptModule(value)) deepFreeze(value);
47
+ return obj;
48
+ }
50
49
  //#endregion
51
50
  export { createPromptRegistry };
52
51
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/registry.ts"],"sourcesContent":["import type { PromptModule, PromptNamespace, PromptRegistry } from \"./types.js\";\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 frozen shallowly.\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 Object.values(obj).forEach((value) => {\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\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"],"mappings":";;;;;;;AAQA,SAAS,eAAe,OAAuC;AAC7D,QACE,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACZ,YAAY,SACZ,UAAU;;;;;;;;;;;;AAcd,SAAS,WAAsC,KAA2B;AACxE,QAAO,OAAO,IAAI;AAClB,QAAO,OAAO,IAAI,CAAC,SAAS,UAAU;AACpC,MACE,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,OAAO,SAAS,MAAM,IACvB,CAAC,eAAe,MAAM,CAEtB,YAAW,MAAyB;GAEtC;AACF,QAAO;;;;;;;;;;;;;;;;;;;;;AAsBT,SAAgB,qBAAgD,SAA+B;AAC7F,QAAO,WAAW,EAAE,GAAG,SAAS,CAAC"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/registry.ts"],"sourcesContent":["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":";;;;;;;;;;;;;;;;;;;;AAqBA,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,2 +1,2 @@
1
- import { n as liquidEngine } from "./engine-B6JHetwb.mjs";
1
+ import { n as liquidEngine } from "./engine-CQfJbVz6.mjs";
2
2
  export { liquidEngine };
@@ -1,2 +1,2 @@
1
- import { n as liquidEngine } from "./engine-B53whAlC.mjs";
1
+ import { n as liquidEngine } from "./engine-CPKs9QbX.mjs";
2
2
  export { liquidEngine };
@@ -5,7 +5,7 @@ import { ZodType } from "zod";
5
5
  /**
6
6
  * Options for creating a custom LiquidJS engine.
7
7
  */
8
- type CreateEngineOptions = Pick<LiquidOptions, "root" | "partials" | "extname" | "cache" | "strictFilters" | "strictVariables" | "ownPropertyOnly">;
8
+ type CreateEngineOptions = Pick<LiquidOptions, "root" | "partials" | "extname" | "cache" | "strictVariables">;
9
9
  /**
10
10
  * A single prompt module produced by codegen.
11
11
  *
@@ -22,9 +22,9 @@ interface PromptModule<T = unknown> {
22
22
  * A nested namespace node in the prompt tree.
23
23
  * Values are either PromptModule leaves or further nested namespaces.
24
24
  */
25
- type PromptNamespace = {
25
+ interface PromptNamespace {
26
26
  readonly [key: string]: PromptModule | PromptNamespace;
27
- };
27
+ }
28
28
  /**
29
29
  * Deep-readonly version of a prompt tree.
30
30
  * Prevents reassignment at any nesting level.
@@ -40,4 +40,4 @@ type PromptNamespace = {
40
40
  type PromptRegistry<T extends PromptNamespace> = { readonly [K in keyof T]: T[K] extends PromptModule ? T[K] : T[K] extends PromptNamespace ? PromptRegistry<T[K]> : T[K] };
41
41
  //#endregion
42
42
  export { PromptRegistry as a, PromptNamespace as i, Liquid$1 as n, PromptModule as r, CreateEngineOptions as t };
43
- //# sourceMappingURL=types-CiSdNM0W.d.mts.map
43
+ //# sourceMappingURL=types-2PI_9h-M.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-2PI_9h-M.d.mts","names":[],"sources":["../../src/types.ts"],"mappings":";;;;;;AAMA;KAAY,mBAAA,GAAsB,IAAA,CAChC,aAAA;;;;AASF;;UAAiB,YAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA,EAAQ,OAAA,CAAQ,CAAA;EACzB,MAAA,CAAO,SAAA,EAAW,CAAA;EAClB,QAAA,CAAS,SAAA,YAAqB,CAAA;AAAA;;;;;UAOf,eAAA;EAAA,UACL,GAAA,WAAc,YAAA,GAAe,eAAA;AAAA;;;;;;;;AADzC;;;;;KAgBY,cAAA,WAAyB,eAAA,2BACd,CAAA,GAAI,CAAA,CAAE,CAAA,UAAW,YAAA,GAClC,CAAA,CAAE,CAAA,IACF,CAAA,CAAE,CAAA,UAAW,eAAA,GACX,cAAA,CAAe,CAAA,CAAE,CAAA,KACjB,CAAA,CAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funkai/prompts",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "Prompt SDK with LiquidJS templating and Zod validation",
6
6
  "keywords": [
@@ -37,6 +37,9 @@
37
37
  "import": "./dist/lib/cli.mjs"
38
38
  }
39
39
  },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
40
43
  "dependencies": {
41
44
  "es-toolkit": "^1.45.1",
42
45
  "liquidjs": "^10.25.0",
@@ -45,7 +48,7 @@
45
48
  "devDependencies": {
46
49
  "@types/node": "^25.5.0",
47
50
  "@vitest/coverage-v8": "^4.1.0",
48
- "tsdown": "^0.21.3",
51
+ "tsdown": "^0.21.4",
49
52
  "typescript": "^5.9.3",
50
53
  "vitest": "^4.1.0"
51
54
  },
package/src/clean.test.ts CHANGED
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
2
2
 
3
3
  import { clean } from "@/clean.js";
4
4
 
5
- describe("clean", () => {
5
+ describe(clean, () => {
6
6
  it("should strip YAML frontmatter from a prompt file", () => {
7
7
  const input = "---\nname: test-prompt\ngroup: agents\n---\nHello {{ name }}";
8
8
  expect(clean(input)).toBe("Hello {{ name }}");
package/src/clean.ts CHANGED
@@ -2,24 +2,39 @@ import { flow } from "es-toolkit";
2
2
 
3
3
  const FRONTMATTER_RE = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/;
4
4
 
5
+ /**
6
+ * Clean a raw `.prompt` file into a render-ready template.
7
+ *
8
+ * Runs the source through a pipeline of transforms — currently
9
+ * strips frontmatter, with more steps added over time.
10
+ *
11
+ * @param text - Raw `.prompt` file content including frontmatter.
12
+ * @returns The cleaned template string, ready for rendering.
13
+ * @example
14
+ * ```ts
15
+ * const template = clean("---\nname: greeting\n---\nHello {{ name }}!");
16
+ * // "Hello {{ name }}!"
17
+ * ```
18
+ */
19
+ export function clean(text: string): string {
20
+ return pipeline(text);
21
+ }
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Private
25
+ // ---------------------------------------------------------------------------
26
+
5
27
  /**
6
28
  * Remove YAML frontmatter from the beginning of a string.
7
29
  *
8
30
  * Frontmatter is a block delimited by `---` at the start of the file.
9
31
  * If no frontmatter is present, the string is returned unchanged.
32
+ *
33
+ * @private
10
34
  */
11
35
  function stripFrontmatter(text: string): string {
12
36
  return text.replace(FRONTMATTER_RE, "");
13
37
  }
14
38
 
39
+ /** @private */
15
40
  const pipeline = flow(stripFrontmatter);
16
-
17
- /**
18
- * Clean a raw `.prompt` file into a render-ready template.
19
- *
20
- * Runs the source through a pipeline of transforms — currently
21
- * strips frontmatter, with more steps added over time.
22
- */
23
- export function clean(text: string): string {
24
- return pipeline(text);
25
- }
@@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
3
3
 
4
4
  import { createEngine, liquidEngine } from "@/engine.js";
5
5
 
6
- describe("createEngine", () => {
6
+ describe(createEngine, () => {
7
7
  it("should return a Liquid instance", () => {
8
8
  const eng = createEngine("/tmp/test-partials");
9
9
  expect(eng).toBeInstanceOf(Liquid);
@@ -18,15 +18,20 @@ describe("createEngine", () => {
18
18
  it("should merge custom options with defaults", () => {
19
19
  const eng = createEngine("/tmp/test-partials", {
20
20
  cache: false,
21
- strictFilters: false,
22
21
  });
23
- // Should not throw on unknown filter when strictFilters is disabled
24
- const result = eng.parseAndRenderSync("{{ name | nonexistent }}", { name: "test" });
25
- expect(result).toBe("test");
22
+ const result = eng.parseAndRenderSync("Hello {{ name }}", { name: "World" });
23
+ expect(result).toBe("Hello World");
24
+ });
25
+
26
+ it("should enforce strictFilters regardless of options", () => {
27
+ const eng = createEngine("/tmp/test-partials");
28
+ expect(() => {
29
+ eng.parseAndRenderSync("{{ name | bogus }}", { name: "test" });
30
+ }).toThrow();
26
31
  });
27
32
  });
28
33
 
29
- describe("liquidEngine", () => {
34
+ describe("liquidEngine instance", () => {
30
35
  it("should be a Liquid instance", () => {
31
36
  expect(liquidEngine).toBeInstanceOf(Liquid);
32
37
  });
package/src/engine.ts CHANGED
@@ -18,9 +18,10 @@ export function createEngine(partialsDir: string, options?: Partial<CreateEngine
18
18
  partials: [partialsDir],
19
19
  extname: ".prompt",
20
20
  cache: true,
21
+ ...options,
22
+ // Safety defaults — applied after spread so callers cannot disable them
21
23
  strictFilters: true,
22
24
  ownPropertyOnly: true,
23
- ...options,
24
25
  });
25
26
  }
26
27
 
@@ -30,6 +31,8 @@ export function createEngine(partialsDir: string, options?: Partial<CreateEngine
30
31
  * Partials are flattened at codegen time by the CLI, so this engine
31
32
  * only needs to handle `{{ var }}` expressions and basic Liquid
32
33
  * control flow (`{% if %}`, `{% for %}`). No filesystem access required.
34
+ *
35
+ * @type {Liquid}
33
36
  */
34
37
  export const liquidEngine = new Liquid({
35
38
  strictFilters: true,
@@ -4,9 +4,9 @@ import { describe, expect, it } from "vitest";
4
4
 
5
5
  import { PARTIALS_DIR } from "@/partials-dir.js";
6
6
 
7
- describe("PARTIALS_DIR", () => {
7
+ describe(PARTIALS_DIR, () => {
8
8
  it("should be an absolute path", () => {
9
- expect(isAbsolute(PARTIALS_DIR)).toBe(true);
9
+ expect(isAbsolute(PARTIALS_DIR)).toBeTruthy();
10
10
  });
11
11
 
12
12
  it("should point to the prompts directory", () => {
@@ -27,7 +27,7 @@ const emptyPrompt = {
27
27
  },
28
28
  };
29
29
 
30
- describe("createPromptRegistry", () => {
30
+ describe(createPromptRegistry, () => {
31
31
  it("should provide dot-access to a registered prompt", () => {
32
32
  const registry = createPromptRegistry({ testPrompt: mockPrompt });
33
33
  expect(registry.testPrompt.name).toBe("test-prompt");
@@ -44,14 +44,14 @@ describe("createPromptRegistry", () => {
44
44
 
45
45
  it("should freeze the top-level registry object", () => {
46
46
  const registry = createPromptRegistry({ testPrompt: mockPrompt });
47
- expect(Object.isFrozen(registry)).toBe(true);
47
+ expect(Object.isFrozen(registry)).toBeTruthy();
48
48
  });
49
49
 
50
50
  it("should freeze nested namespace objects", () => {
51
51
  const registry = createPromptRegistry({
52
52
  agents: { testPrompt: mockPrompt },
53
53
  });
54
- expect(Object.isFrozen(registry.agents)).toBe(true);
54
+ expect(Object.isFrozen(registry.agents)).toBeTruthy();
55
55
  });
56
56
 
57
57
  it("should expose all keys via Object.keys", () => {
package/src/registry.ts CHANGED
@@ -1,5 +1,32 @@
1
1
  import type { PromptModule, PromptNamespace, PromptRegistry } from "./types.js";
2
2
 
3
+ /**
4
+ * Create a typed, frozen prompt registry from a (possibly nested) map of prompt modules.
5
+ *
6
+ * The registry is typically created by generated code — the CLI produces
7
+ * an `index.ts` that calls `createPromptRegistry()` with all discovered
8
+ * prompt modules keyed by camelCase name, nested by group.
9
+ *
10
+ * @param modules - Record mapping camelCase prompt names (or group namespaces) to their modules.
11
+ * @returns A deep-frozen, typed record with direct property access.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const prompts = createPromptRegistry({
16
+ * agents: { coverageAssessor },
17
+ * greeting,
18
+ * })
19
+ * prompts.agents.coverageAssessor.render({ scope: 'full' })
20
+ * ```
21
+ */
22
+ export function createPromptRegistry<T extends PromptNamespace>(modules: T): PromptRegistry<T> {
23
+ return deepFreeze({ ...modules });
24
+ }
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Private
28
+ // ---------------------------------------------------------------------------
29
+
3
30
  /**
4
31
  * Check whether a value looks like a PromptModule leaf.
5
32
  * Leaves have `name`, `schema`, and `render` — namespaces do not.
@@ -19,7 +46,8 @@ function isPromptModule(value: unknown): value is PromptModule {
19
46
  /**
20
47
  * Recursively freeze a prompt namespace tree.
21
48
  * Only recurses into plain namespace nodes — PromptModule leaves
22
- * (which contain Zod schemas) are frozen shallowly.
49
+ * (which contain Zod schemas) are intentionally left unfrozen
50
+ * to avoid breaking Zod internal state.
23
51
  *
24
52
  * @param obj - The namespace object to freeze.
25
53
  * @returns The frozen object cast to its deep-readonly type.
@@ -28,7 +56,7 @@ function isPromptModule(value: unknown): value is PromptModule {
28
56
  */
29
57
  function deepFreeze<T extends PromptNamespace>(obj: T): PromptRegistry<T> {
30
58
  Object.freeze(obj);
31
- Object.values(obj).forEach((value) => {
59
+ for (const value of Object.values(obj)) {
32
60
  if (
33
61
  typeof value === "object" &&
34
62
  value !== null &&
@@ -37,29 +65,6 @@ function deepFreeze<T extends PromptNamespace>(obj: T): PromptRegistry<T> {
37
65
  ) {
38
66
  deepFreeze(value as PromptNamespace);
39
67
  }
40
- });
68
+ }
41
69
  return obj as PromptRegistry<T>;
42
70
  }
43
-
44
- /**
45
- * Create a typed, frozen prompt registry from a (possibly nested) map of prompt modules.
46
- *
47
- * The registry is typically created by generated code — the CLI produces
48
- * an `index.ts` that calls `createPromptRegistry()` with all discovered
49
- * prompt modules keyed by camelCase name, nested by group.
50
- *
51
- * @param modules - Record mapping camelCase prompt names (or group namespaces) to their modules.
52
- * @returns A deep-frozen, typed record with direct property access.
53
- *
54
- * @example
55
- * ```ts
56
- * const prompts = createPromptRegistry({
57
- * agents: { coverageAssessor },
58
- * greeting,
59
- * })
60
- * prompts.agents.coverageAssessor.render({ scope: 'full' })
61
- * ```
62
- */
63
- export function createPromptRegistry<T extends PromptNamespace>(modules: T): PromptRegistry<T> {
64
- return deepFreeze({ ...modules });
65
- }
package/src/types.ts CHANGED
@@ -6,13 +6,7 @@ import type { ZodType } from "zod";
6
6
  */
7
7
  export type CreateEngineOptions = Pick<
8
8
  LiquidOptions,
9
- | "root"
10
- | "partials"
11
- | "extname"
12
- | "cache"
13
- | "strictFilters"
14
- | "strictVariables"
15
- | "ownPropertyOnly"
9
+ "root" | "partials" | "extname" | "cache" | "strictVariables"
16
10
  >;
17
11
 
18
12
  /**
@@ -32,9 +26,9 @@ export interface PromptModule<T = unknown> {
32
26
  * A nested namespace node in the prompt tree.
33
27
  * Values are either PromptModule leaves or further nested namespaces.
34
28
  */
35
- export type PromptNamespace = {
29
+ export interface PromptNamespace {
36
30
  readonly [key: string]: PromptModule | PromptNamespace;
37
- };
31
+ }
38
32
 
39
33
  /**
40
34
  * Deep-readonly version of a prompt tree.
package/tsconfig.json CHANGED
@@ -18,7 +18,8 @@
18
18
  "rootDir": ".",
19
19
  "paths": {
20
20
  "@/*": ["./src/*"]
21
- }
21
+ },
22
+ "types": ["node"]
22
23
  },
23
24
  "include": ["src"],
24
25
  "exclude": ["node_modules", "dist"]
@@ -1 +0,0 @@
1
- {"version":3,"file":"engine-B53whAlC.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 strictFilters: true,\n ownPropertyOnly: true,\n ...options,\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 */\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,eAAe;EACf,iBAAiB;EACjB,GAAG;EACJ,CAAC;;;;;;;;;AAUJ,MAAa,eAAe,IAAI,OAAO;CACrC,eAAe;CACf,iBAAiB;CAClB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-CiSdNM0W.d.mts","names":[],"sources":["../../src/types.ts"],"mappings":";;;;;;AAMA;KAAY,mBAAA,GAAsB,IAAA,CAChC,aAAA;;;;AAeF;;UAAiB,YAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA,EAAQ,OAAA,CAAQ,CAAA;EACzB,MAAA,CAAO,SAAA,EAAW,CAAA;EAClB,QAAA,CAAS,SAAA,YAAqB,CAAA;AAAA;;;;;KAOpB,eAAA;EAAA,UACA,GAAA,WAAc,YAAA,GAAe,eAAA;AAAA;;;;;;;;AADzC;;;;;KAgBY,cAAA,WAAyB,eAAA,2BACd,CAAA,GAAI,CAAA,CAAE,CAAA,UAAW,YAAA,GAClC,CAAA,CAAE,CAAA,IACF,CAAA,CAAE,CAAA,UAAW,eAAA,GACX,cAAA,CAAe,CAAA,CAAE,CAAA,KACjB,CAAA,CAAE,CAAA"}