@mistralys/persona-builder 2.3.0 → 2.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.
package/README.md CHANGED
@@ -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
@@ -71,6 +73,7 @@ See the [CLI docs](docs/cli.md) for config file format and all flags.
71
73
  | [Getting Started](docs/getting-started.md) | Step-by-step tutorial — build your first persona from scratch |
72
74
  | [Directory Convention](docs/directory-convention.md) | Expected source layout (`meta/`, `content/`, `partials/`) |
73
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 |
74
77
  | [Plugins](docs/plugins.md) | `PersonaBuildPlugin` interface and examples |
75
78
 
76
79
  **Reference** — look-up material:
package/dist/cli.cjs CHANGED
@@ -131,7 +131,7 @@ function runBuildContext(plugins, ctx, persona, suite, target) {
131
131
  let accumulated = ctx;
132
132
  for (const plugin of plugins) {
133
133
  if (typeof plugin.onBuildContext === "function") {
134
- accumulated = plugin.onBuildContext(accumulated, persona, suite, target);
134
+ accumulated = plugin.onBuildContext(accumulated, persona, suite, target) ?? accumulated;
135
135
  }
136
136
  }
137
137
  return accumulated;
@@ -140,11 +140,29 @@ function runPostRender(plugins, rendered, persona, target) {
140
140
  let output = rendered;
141
141
  for (const plugin of plugins) {
142
142
  if (typeof plugin.onPostRender === "function") {
143
- output = plugin.onPostRender(output, persona, target);
143
+ output = plugin.onPostRender(output, persona, target) ?? output;
144
144
  }
145
145
  }
146
146
  return output;
147
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
+ }
148
166
  function runValidate(plugins, persona, suite, target) {
149
167
  const results = [];
150
168
  for (const plugin of plugins) {
@@ -367,9 +385,20 @@ async function buildAgentNameMap(config) {
367
385
  }
368
386
  return agentMap;
369
387
  }
370
- 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;
371
398
  const version = typeof personaMeta["version"] === "string" ? personaMeta["version"] : typeof sharedMeta["default_version"] === "string" ? sharedMeta["default_version"] : "0.0.0";
372
399
  const merged = {
400
+ ...configVariables ?? {},
401
+ ...suiteVariables ?? {},
373
402
  ...sharedMeta,
374
403
  ...personaMeta,
375
404
  version
@@ -422,18 +451,49 @@ function buildContext(personaMeta, sharedMeta, agentMap = {}, target, registry)
422
451
  }
423
452
  return merged;
424
453
  }
454
+ function validateSubagentRefs(persona, agentMap) {
455
+ const subagents = persona.subagents;
456
+ if (!Array.isArray(subagents) || subagents.length === 0) return [];
457
+ const results = [];
458
+ for (const slug of subagents) {
459
+ const key = `agent_slug_${slug.replace(/-/g, "_")}`;
460
+ if (!(key in agentMap)) {
461
+ results.push({
462
+ severity: "error",
463
+ message: `Persona '${persona.name}' declares subagent '${slug}' but no persona with that slug exists in any configured suite.`
464
+ });
465
+ }
466
+ }
467
+ return results;
468
+ }
425
469
  async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta, partialsMap, config, plugins, target, agentMap = {}, registry = defaultRegistry) {
426
470
  const personaMeta = await loadPersonaYaml(personaYamlPath);
427
- let context = buildContext(personaMeta, sharedMeta, agentMap, target, registry);
471
+ let context = buildContext({
472
+ personaMeta,
473
+ sharedMeta,
474
+ agentMap,
475
+ target,
476
+ registry,
477
+ configVariables: config.variables,
478
+ suiteVariables: suiteConfig.variables
479
+ });
428
480
  const personaMetaTyped = personaMeta;
429
481
  context = runBuildContext(plugins, context, personaMetaTyped, suiteConfig, target);
482
+ const personaPartialsMap = runPersonaPartials(
483
+ plugins,
484
+ { ...partialsMap },
485
+ personaMetaTyped,
486
+ context,
487
+ suiteConfig,
488
+ target
489
+ );
430
490
  const fmTemplate = resolveFrontmatterTemplate(target, plugins, config.frontmatter, registry);
431
491
  const contentBasename = path2__default.default.basename(personaYamlPath, ".yaml") + ".md";
432
492
  const frontmatter = renderFrontmatter(fmTemplate, context, contentBasename);
433
493
  const contentSubdir = suiteConfig.contentSubdir ?? "content";
434
494
  const contentPath = path2__default.default.join(suiteConfig.srcDir, contentSubdir, contentBasename);
435
495
  const bodyTemplate = normalizeNewlines(await promises.readFile(contentPath, "utf8"));
436
- let body = resolvePartials(bodyTemplate, partialsMap);
496
+ let body = resolvePartials(bodyTemplate, personaPartialsMap);
437
497
  body = resolveConditionals(body, context);
438
498
  body = resolveVariables(body, context, contentBasename);
439
499
  body = collapseBlankLines(body);
@@ -444,7 +504,10 @@ async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta,
444
504
  ${body}
445
505
  `);
446
506
  output = runPostRender(plugins, output, personaMetaTyped, target);
447
- const validationResults = runValidate(plugins, personaMetaTyped, suiteConfig, target);
507
+ const validationResults = [
508
+ ...runValidate(plugins, personaMetaTyped, suiteConfig, target),
509
+ ...validateSubagentRefs(personaMetaTyped, agentMap)
510
+ ];
448
511
  const def = registry.has(target) ? registry.get(target) : void 0;
449
512
  const outputDir = resolveOutputDir(target, suiteConfig, def);
450
513
  const fnKey = def?.filenameContextKey;
@@ -471,7 +534,7 @@ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agent
471
534
  const metaSubdir = suiteConfig.metaSubdir ?? "meta";
472
535
  const sharedYamlPath = path2__default.default.join(suiteConfig.srcDir, metaSubdir, "_shared.yaml");
473
536
  const sharedMeta = await loadRawYaml(sharedYamlPath);
474
- let partialsMap = {};
537
+ let partialsMap = { ...config.partials ?? {} };
475
538
  if (config.sharedPartialsDir && fs.existsSync(config.sharedPartialsDir)) {
476
539
  partialsMap = { ...partialsMap, ...await loadPartials(config.sharedPartialsDir) };
477
540
  }
@@ -481,6 +544,7 @@ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agent
481
544
  partialsMap = { ...partialsMap, ...await loadPartials(suitePartialsDir) };
482
545
  }
483
546
  runSuiteInit(plugins, suiteConfig, sharedMeta);
547
+ partialsMap = runPartials(plugins, partialsMap, suiteName, suiteConfig);
484
548
  const personaYamlPaths = await discoverSuitePersonaYamls(suiteConfig);
485
549
  const results = [];
486
550
  for (const yamlPath of personaYamlPaths) {