@mistralys/persona-builder 2.2.0 → 2.4.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AI Persona Builder
2
2
 
3
- Build AI persona instruction files for **VS Code Chat** and **Claude Code** from YAML metadata and Markdown templates — with zero configuration friction.
3
+ Build AI persona instruction files for **VS Code Chat**, **Claude Code** and **LangGraph Deep Agents** from YAML metadata and Markdown templates — with zero configuration friction.
4
4
 
5
5
  Define your personas once as simple YAML + Markdown sources, and the library generates correctly formatted instruction files for both IDEs. A plugin system lets you inject custom frontmatter, run validators, or post-process output without touching the core engine.
6
6
 
@@ -10,6 +10,8 @@ Define your personas once as simple YAML + Markdown sources, and the library gen
10
10
  - **Extensible target registry** — register custom targets via `TargetRegistry` without touching core code; each target declares its own output key, frontmatter template, and context flags
11
11
  - **YAML + Markdown templating** — separate metadata from content; merge them at build time with `{{variables}}`, `{{> partials}}`, and `{{#if}}` conditionals
12
12
  - **Shared + per-suite partials** — reuse content fragments across personas with local overrides
13
+ - **Custom variables** — inject global or per-suite template variables via `BuildConfig.variables` and `SuiteConfig.variables` without touching persona YAML files
14
+ - **Dynamic partials** — supply inline partials via `BuildConfig.partials`, or override them at suite or per-persona level through the `onPartials` and `onPersonaPartials` plugin hooks
13
15
  - **Plugin architecture** — hook into context building, post-rendering, validation, and frontmatter generation
14
16
  - **CI-friendly** — `--check` mode renders without writing; `--strict` exits non-zero on warnings
15
17
  - **Programmatic & CLI** — use the `build()` API in scripts or run `persona-build` from the command line
@@ -25,6 +27,9 @@ Define your personas once as simple YAML + Markdown sources, and the library gen
25
27
  npm install @mistralys/persona-builder
26
28
  ```
27
29
 
30
+ - View on NPM: https://www.npmjs.com/package/@mistralys/persona-builder
31
+ - View on Github: https://github.com/Mistralys/ai-persona-builder
32
+
28
33
  ### Programmatic API
29
34
 
30
35
  ```ts
@@ -68,6 +73,7 @@ See the [CLI docs](docs/cli.md) for config file format and all flags.
68
73
  | [Getting Started](docs/getting-started.md) | Step-by-step tutorial — build your first persona from scratch |
69
74
  | [Directory Convention](docs/directory-convention.md) | Expected source layout (`meta/`, `content/`, `partials/`) |
70
75
  | [Template Syntax](docs/template-syntax.md) | Variables, partials, conditionals, and built-in context variables |
76
+ | [Custom Variables & Dynamic Partials](docs/dynamic-partials.md) | Inject build-time variables and partial content at global, suite, or per-persona level |
71
77
  | [Plugins](docs/plugins.md) | `PersonaBuildPlugin` interface and examples |
72
78
 
73
79
  **Reference** — look-up material:
@@ -100,7 +106,7 @@ MIT
100
106
 
101
107
  ## Release Workflow
102
108
 
103
- 1. Add changelog entries
109
+ 1. Add changelog entries (do not change package.json version)
104
110
  2. `npm version 0.0.0` - Updates package and lock versions + commit
105
111
  3. `npm publish` - Publish version on NPM
106
112
  4. `git push origin 0.0.0` - Add the tag in GIT
package/dist/cli.cjs CHANGED
@@ -27,10 +27,30 @@ function resolvePartials(text, partialsMap, depth = 0) {
27
27
  }
28
28
 
29
29
  // src/engine/conditionals.ts
30
+ var NO_NESTED_IF = String.raw`(?:(?!\{\{#if\b)[\s\S])*?`;
31
+ var ELSE_IF_PATTERN = new RegExp(
32
+ String.raw`\{\{else if (\w+)\}\}(${NO_NESTED_IF})\{\{\/if\}\}`,
33
+ "g"
34
+ );
35
+ function resolveElseIf(text) {
36
+ if (!text.includes("{{else if ")) {
37
+ return text;
38
+ }
39
+ let result = text;
40
+ let prev;
41
+ do {
42
+ prev = result;
43
+ result = result.replace(
44
+ ELSE_IF_PATTERN,
45
+ (_match, flag, content) => `{{else}}{{#if ${flag}}}${content}{{/if}}{{/if}}`
46
+ );
47
+ } while (result !== prev);
48
+ return result;
49
+ }
30
50
  function resolveConditionals(text, context) {
31
- const noNestedIf = String.raw`(?:(?!\{\{#if\b)[\s\S])*?`;
51
+ const normalized = resolveElseIf(text);
32
52
  const pattern = new RegExp(
33
- String.raw`\n*\{\{#if (\w+)\}\}(${noNestedIf})` + String.raw`(?:\{\{else\}\}(${noNestedIf}))?\{\{\/if\}\}\n*`,
53
+ String.raw`\n*\{\{#if (\w+)\}\}(${NO_NESTED_IF})` + String.raw`(?:\{\{else\}\}(${NO_NESTED_IF}))?\{\{\/if\}\}\n*`,
34
54
  "g"
35
55
  );
36
56
  const resolve = (_match, flag, inner, elseInner) => {
@@ -42,7 +62,7 @@ function resolveConditionals(text, context) {
42
62
  }
43
63
  return "\n";
44
64
  };
45
- let result = text;
65
+ let result = normalized;
46
66
  let prev;
47
67
  do {
48
68
  prev = result;
@@ -111,7 +131,7 @@ function runBuildContext(plugins, ctx, persona, suite, target) {
111
131
  let accumulated = ctx;
112
132
  for (const plugin of plugins) {
113
133
  if (typeof plugin.onBuildContext === "function") {
114
- accumulated = plugin.onBuildContext(accumulated, persona, suite, target);
134
+ accumulated = plugin.onBuildContext(accumulated, persona, suite, target) ?? accumulated;
115
135
  }
116
136
  }
117
137
  return accumulated;
@@ -120,11 +140,29 @@ function runPostRender(plugins, rendered, persona, target) {
120
140
  let output = rendered;
121
141
  for (const plugin of plugins) {
122
142
  if (typeof plugin.onPostRender === "function") {
123
- output = plugin.onPostRender(output, persona, target);
143
+ output = plugin.onPostRender(output, persona, target) ?? output;
124
144
  }
125
145
  }
126
146
  return output;
127
147
  }
148
+ function runPartials(plugins, partialsMap, suiteName, suite) {
149
+ let accumulated = partialsMap;
150
+ for (const plugin of plugins) {
151
+ if (typeof plugin.onPartials === "function") {
152
+ accumulated = plugin.onPartials(accumulated, suiteName, suite) ?? accumulated;
153
+ }
154
+ }
155
+ return accumulated;
156
+ }
157
+ function runPersonaPartials(plugins, partialsMap, persona, context, suite, target) {
158
+ let accumulated = partialsMap;
159
+ for (const plugin of plugins) {
160
+ if (typeof plugin.onPersonaPartials === "function") {
161
+ accumulated = plugin.onPersonaPartials(accumulated, persona, context, suite, target) ?? accumulated;
162
+ }
163
+ }
164
+ return accumulated;
165
+ }
128
166
  function runValidate(plugins, persona, suite, target) {
129
167
  const results = [];
130
168
  for (const plugin of plugins) {
@@ -347,9 +385,20 @@ async function buildAgentNameMap(config) {
347
385
  }
348
386
  return agentMap;
349
387
  }
350
- function buildContext(personaMeta, sharedMeta, agentMap = {}, target, registry) {
388
+ function buildContext(options) {
389
+ const {
390
+ personaMeta,
391
+ sharedMeta,
392
+ agentMap = {},
393
+ target,
394
+ registry,
395
+ configVariables,
396
+ suiteVariables
397
+ } = options;
351
398
  const version = typeof personaMeta["version"] === "string" ? personaMeta["version"] : typeof sharedMeta["default_version"] === "string" ? sharedMeta["default_version"] : "0.0.0";
352
399
  const merged = {
400
+ ...configVariables ?? {},
401
+ ...suiteVariables ?? {},
353
402
  ...sharedMeta,
354
403
  ...personaMeta,
355
404
  version
@@ -404,16 +453,32 @@ function buildContext(personaMeta, sharedMeta, agentMap = {}, target, registry)
404
453
  }
405
454
  async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta, partialsMap, config, plugins, target, agentMap = {}, registry = defaultRegistry) {
406
455
  const personaMeta = await loadPersonaYaml(personaYamlPath);
407
- let context = buildContext(personaMeta, sharedMeta, agentMap, target, registry);
456
+ let context = buildContext({
457
+ personaMeta,
458
+ sharedMeta,
459
+ agentMap,
460
+ target,
461
+ registry,
462
+ configVariables: config.variables,
463
+ suiteVariables: suiteConfig.variables
464
+ });
408
465
  const personaMetaTyped = personaMeta;
409
466
  context = runBuildContext(plugins, context, personaMetaTyped, suiteConfig, target);
467
+ const personaPartialsMap = runPersonaPartials(
468
+ plugins,
469
+ { ...partialsMap },
470
+ personaMetaTyped,
471
+ context,
472
+ suiteConfig,
473
+ target
474
+ );
410
475
  const fmTemplate = resolveFrontmatterTemplate(target, plugins, config.frontmatter, registry);
411
476
  const contentBasename = path2__default.default.basename(personaYamlPath, ".yaml") + ".md";
412
477
  const frontmatter = renderFrontmatter(fmTemplate, context, contentBasename);
413
478
  const contentSubdir = suiteConfig.contentSubdir ?? "content";
414
479
  const contentPath = path2__default.default.join(suiteConfig.srcDir, contentSubdir, contentBasename);
415
480
  const bodyTemplate = normalizeNewlines(await promises.readFile(contentPath, "utf8"));
416
- let body = resolvePartials(bodyTemplate, partialsMap);
481
+ let body = resolvePartials(bodyTemplate, personaPartialsMap);
417
482
  body = resolveConditionals(body, context);
418
483
  body = resolveVariables(body, context, contentBasename);
419
484
  body = collapseBlankLines(body);
@@ -451,7 +516,7 @@ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agent
451
516
  const metaSubdir = suiteConfig.metaSubdir ?? "meta";
452
517
  const sharedYamlPath = path2__default.default.join(suiteConfig.srcDir, metaSubdir, "_shared.yaml");
453
518
  const sharedMeta = await loadRawYaml(sharedYamlPath);
454
- let partialsMap = {};
519
+ let partialsMap = { ...config.partials ?? {} };
455
520
  if (config.sharedPartialsDir && fs.existsSync(config.sharedPartialsDir)) {
456
521
  partialsMap = { ...partialsMap, ...await loadPartials(config.sharedPartialsDir) };
457
522
  }
@@ -461,6 +526,7 @@ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agent
461
526
  partialsMap = { ...partialsMap, ...await loadPartials(suitePartialsDir) };
462
527
  }
463
528
  runSuiteInit(plugins, suiteConfig, sharedMeta);
529
+ partialsMap = runPartials(plugins, partialsMap, suiteName, suiteConfig);
464
530
  const personaYamlPaths = await discoverSuitePersonaYamls(suiteConfig);
465
531
  const results = [];
466
532
  for (const yamlPath of personaYamlPaths) {