@mistralys/persona-builder 2.1.3 → 2.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.
package/dist/index.js CHANGED
@@ -19,19 +19,48 @@ function resolvePartials(text, partialsMap, depth = 0) {
19
19
  }
20
20
 
21
21
  // src/engine/conditionals.ts
22
+ var NO_NESTED_IF = String.raw`(?:(?!\{\{#if\b)[\s\S])*?`;
23
+ var ELSE_IF_PATTERN = new RegExp(
24
+ String.raw`\{\{else if (\w+)\}\}(${NO_NESTED_IF})\{\{\/if\}\}`,
25
+ "g"
26
+ );
27
+ function resolveElseIf(text) {
28
+ if (!text.includes("{{else if ")) {
29
+ return text;
30
+ }
31
+ let result = text;
32
+ let prev;
33
+ do {
34
+ prev = result;
35
+ result = result.replace(
36
+ ELSE_IF_PATTERN,
37
+ (_match, flag, content) => `{{else}}{{#if ${flag}}}${content}{{/if}}{{/if}}`
38
+ );
39
+ } while (result !== prev);
40
+ return result;
41
+ }
22
42
  function resolveConditionals(text, context) {
23
- return text.replace(
24
- /\n*\{\{#if (\w+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}\n*/g,
25
- (_match, flag, inner, elseInner) => {
26
- if (context[flag]) {
27
- return "\n" + inner.replace(/^\n+/, "").replace(/\n+$/, "") + "\n";
28
- }
29
- if (elseInner !== void 0) {
30
- return "\n" + elseInner.replace(/^\n+/, "").replace(/\n+$/, "") + "\n";
31
- }
32
- return "\n";
33
- }
43
+ const normalized = resolveElseIf(text);
44
+ const pattern = new RegExp(
45
+ String.raw`\n*\{\{#if (\w+)\}\}(${NO_NESTED_IF})` + String.raw`(?:\{\{else\}\}(${NO_NESTED_IF}))?\{\{\/if\}\}\n*`,
46
+ "g"
34
47
  );
48
+ const resolve = (_match, flag, inner, elseInner) => {
49
+ if (context[flag]) {
50
+ return "\n" + inner.replace(/^\n+/, "").replace(/\n+$/, "") + "\n";
51
+ }
52
+ if (elseInner !== void 0) {
53
+ return "\n" + elseInner.replace(/^\n+/, "").replace(/\n+$/, "") + "\n";
54
+ }
55
+ return "\n";
56
+ };
57
+ let result = normalized;
58
+ let prev;
59
+ do {
60
+ prev = result;
61
+ result = result.replace(pattern, resolve);
62
+ } while (result !== prev);
63
+ return result;
35
64
  }
36
65
 
37
66
  // src/engine/variables.ts
@@ -116,11 +145,11 @@ function runSuiteInit(plugins, suite, sharedMeta) {
116
145
  }
117
146
  }
118
147
  }
119
- function runBuildContext(plugins, ctx, persona, suite) {
148
+ function runBuildContext(plugins, ctx, persona, suite, target) {
120
149
  let accumulated = ctx;
121
150
  for (const plugin of plugins) {
122
151
  if (typeof plugin.onBuildContext === "function") {
123
- accumulated = plugin.onBuildContext(accumulated, persona, suite);
152
+ accumulated = plugin.onBuildContext(accumulated, persona, suite, target);
124
153
  }
125
154
  }
126
155
  return accumulated;
@@ -145,7 +174,10 @@ function runValidate(plugins, persona, suite, target) {
145
174
  return results;
146
175
  }
147
176
 
148
- // src/builders/frontmatter.ts
177
+ // src/targets/types.ts
178
+ var TARGET_VSCODE = "vscode";
179
+ var TARGET_CLAUDE_CODE = "claude-code";
180
+ var TARGET_DEEP_AGENTS = "deep-agents";
149
181
  var DEFAULT_FRONTMATTER_VSCODE = `---
150
182
  name: '{{name}} v{{version}}'
151
183
  description: '{{description}}'
@@ -158,7 +190,13 @@ model: {{cc_model}}
158
190
  memory: {{cc_memory}}
159
191
  allowedTools: [{{cc_tools_list}}]
160
192
  ---`;
161
- function resolveFrontmatterTemplate(target, plugins, configTemplates) {
193
+ var DEFAULT_FRONTMATTER_DEEP_AGENTS = `---
194
+ name: {{name}}
195
+ description: {{description}}
196
+ ---`;
197
+
198
+ // src/builders/frontmatter.ts
199
+ function resolveFrontmatterTemplate(target, plugins, configTemplates, registry) {
162
200
  for (const plugin of plugins) {
163
201
  if (plugin.frontmatterTemplates && target in plugin.frontmatterTemplates) {
164
202
  const tpl = plugin.frontmatterTemplates[target];
@@ -169,13 +207,124 @@ function resolveFrontmatterTemplate(target, plugins, configTemplates) {
169
207
  const tpl = configTemplates[target];
170
208
  if (tpl !== void 0) return tpl;
171
209
  }
172
- return target === "vscode" ? DEFAULT_FRONTMATTER_VSCODE : DEFAULT_FRONTMATTER_CLAUDE_CODE;
210
+ if (registry && registry.has(target)) {
211
+ return registry.get(target).defaultFrontmatter;
212
+ }
213
+ return DEFAULT_FRONTMATTER_VSCODE;
173
214
  }
174
215
  function renderFrontmatter(template, context, filename) {
175
216
  let rendered = resolveConditionals(template, context);
176
217
  rendered = resolveVariables(rendered, context, filename);
177
218
  return rendered;
178
219
  }
220
+
221
+ // src/targets/registry.ts
222
+ var TargetRegistry = class _TargetRegistry {
223
+ // Map preserves insertion order — names() and allDefinitions() are
224
+ // therefore deterministic and match registration sequence. This is
225
+ // intentional: the built-in registry guarantees ['vscode', 'claude-code']
226
+ // ordering for the default targets (AC-2).
227
+ _definitions = /* @__PURE__ */ new Map();
228
+ /**
229
+ * Register a new target definition.
230
+ *
231
+ * @param definition The target descriptor to register.
232
+ * @throws {Error} If a target with the same `name` is already registered.
233
+ */
234
+ register(definition) {
235
+ if (this._definitions.has(definition.name)) {
236
+ throw new Error(
237
+ `TargetRegistry: target "${definition.name}" is already registered. Use a unique name or remove the existing registration first.`
238
+ );
239
+ }
240
+ this._definitions.set(definition.name, definition);
241
+ }
242
+ /**
243
+ * Retrieve a registered target definition by name.
244
+ *
245
+ * Returns a shallow copy — mutating the returned object does not affect
246
+ * the registry's internal state.
247
+ *
248
+ * @param name The target name to look up.
249
+ * @returns A shallow copy of the matching TargetDefinition.
250
+ * @throws {Error} If no target with the given name is registered.
251
+ */
252
+ get(name) {
253
+ const def = this._definitions.get(name);
254
+ if (!def) {
255
+ const known = this.names().join(", ") || "(none)";
256
+ throw new Error(
257
+ `TargetRegistry: target "${name}" is not registered. Registered targets: ${known}.`
258
+ );
259
+ }
260
+ return { ...def };
261
+ }
262
+ /**
263
+ * Returns `true` if a target with the given name is registered.
264
+ *
265
+ * @param name The target name to check.
266
+ */
267
+ has(name) {
268
+ return this._definitions.has(name);
269
+ }
270
+ /**
271
+ * Returns the names of all registered targets, in registration order.
272
+ */
273
+ names() {
274
+ return Array.from(this._definitions.keys());
275
+ }
276
+ /**
277
+ * Returns all registered TargetDefinition objects, in registration order.
278
+ *
279
+ * Returns shallow copies — mutating a returned definition does not affect
280
+ * the registry's internal state.
281
+ */
282
+ allDefinitions() {
283
+ return Array.from(this._definitions.values()).map((def) => ({ ...def }));
284
+ }
285
+ /**
286
+ * Returns a new TargetRegistry pre-populated with the same definitions.
287
+ *
288
+ * Useful for test isolation: clone the `defaultRegistry` to get an
289
+ * independent copy that can be mutated without affecting the singleton.
290
+ */
291
+ clone() {
292
+ const copy = new _TargetRegistry();
293
+ for (const def of this._definitions.values()) {
294
+ copy.register({ ...def });
295
+ }
296
+ return copy;
297
+ }
298
+ };
299
+
300
+ // src/targets/built-in.ts
301
+ var defaultRegistry = new TargetRegistry();
302
+ defaultRegistry.register({
303
+ name: TARGET_VSCODE,
304
+ outputDirKey: "vscode",
305
+ filenameContextKey: "vs_file_name",
306
+ defaultFrontmatter: DEFAULT_FRONTMATTER_VSCODE,
307
+ contextFlags: { target_vscode: true },
308
+ defaultEnabled: true
309
+ });
310
+ defaultRegistry.register({
311
+ name: TARGET_CLAUDE_CODE,
312
+ outputDirKey: "claude-code",
313
+ filenameContextKey: "cc_file_name",
314
+ defaultFrontmatter: DEFAULT_FRONTMATTER_CLAUDE_CODE,
315
+ contextFlags: { target_claude_code: true },
316
+ defaultEnabled: true
317
+ });
318
+ defaultRegistry.register({
319
+ name: TARGET_DEEP_AGENTS,
320
+ outputDirKey: "deep-agents",
321
+ filenameContextKey: "da_file_name",
322
+ defaultFrontmatter: DEFAULT_FRONTMATTER_DEEP_AGENTS,
323
+ contextFlags: { target_deep_agents: true },
324
+ defaultEnabled: false
325
+ });
326
+
327
+ // src/builders/persona-builder.ts
179
328
  async function discoverSuitePersonaYamls(suiteConfig) {
180
329
  const metaSubdir = suiteConfig.metaSubdir ?? "meta";
181
330
  const metaDir = path4.join(suiteConfig.srcDir, metaSubdir);
@@ -202,6 +351,18 @@ async function loadPersonaYaml(yamlPath) {
202
351
  }
203
352
  return record;
204
353
  }
354
+ function resolveOutputDir(target, suiteConfig, definition) {
355
+ const merged = {};
356
+ if (suiteConfig.outVscode) merged["vscode"] = suiteConfig.outVscode;
357
+ if (suiteConfig.outClaudeCode) merged["claude-code"] = suiteConfig.outClaudeCode;
358
+ if (suiteConfig.outputDirs) Object.assign(merged, suiteConfig.outputDirs);
359
+ const lookupKey = definition?.outputDirKey ?? target;
360
+ const dir = merged[lookupKey];
361
+ if (dir) return dir;
362
+ throw new Error(
363
+ `buildPersona: no output directory configured for target "${target}". Add outputDirs['${lookupKey}'] to the suite config, or, for the built-in targets, provide the outVscode / outClaudeCode fields.`
364
+ );
365
+ }
205
366
  async function buildAgentNameMap(config) {
206
367
  const agentMap = {};
207
368
  for (const [, suiteConfig] of Object.entries(config.suites)) {
@@ -215,13 +376,16 @@ async function buildAgentNameMap(config) {
215
376
  const slug = typeof persona["slug"] === "string" ? persona["slug"] : path4.basename(yamlPath, ".yaml");
216
377
  const name = typeof persona["name"] === "string" ? persona["name"] : slug;
217
378
  const version = typeof persona["version"] === "string" ? persona["version"] : defaultVersion;
218
- const key = `agent_${slug.replace(/-/g, "_")}`;
379
+ const underscoredSlug = slug.replace(/-/g, "_");
380
+ const key = `agent_${underscoredSlug}`;
219
381
  agentMap[key] = `${name} v${version}`;
382
+ const slugKey = `agent_slug_${underscoredSlug}`;
383
+ agentMap[slugKey] = slug;
220
384
  }
221
385
  }
222
386
  return agentMap;
223
387
  }
224
- function buildContext(personaMeta, sharedMeta, agentMap = {}) {
388
+ function buildContext(personaMeta, sharedMeta, agentMap = {}, target, registry) {
225
389
  const version = typeof personaMeta["version"] === "string" ? personaMeta["version"] : typeof sharedMeta["default_version"] === "string" ? sharedMeta["default_version"] : "0.0.0";
226
390
  const merged = {
227
391
  ...sharedMeta,
@@ -246,19 +410,42 @@ function buildContext(personaMeta, sharedMeta, agentMap = {}) {
246
410
  const ccFileName = merged["cc_file_name"];
247
411
  merged["cc_file_name_stem"] = ccFileName.replace(/\.md$/, "");
248
412
  }
413
+ if (!("da_file_name_stem" in merged) && typeof merged["da_file_name"] === "string") {
414
+ const daFileName = merged["da_file_name"];
415
+ merged["da_file_name_stem"] = daFileName.replace(/\.md$/, "");
416
+ }
417
+ if (typeof merged["da_file_name"] === "string") {
418
+ const daTools = Array.isArray(merged["da_tools"]) ? merged["da_tools"] : tools;
419
+ if (!("da_tools_list" in merged)) {
420
+ merged["da_tools_list"] = serializeToolsList(daTools);
421
+ }
422
+ if (!("da_tools_json" in merged)) {
423
+ merged["da_tools_json"] = serializeTools(daTools);
424
+ }
425
+ }
249
426
  for (const [key, value] of Object.entries(agentMap)) {
250
427
  if (!(key in merged)) {
251
428
  merged[key] = value;
252
429
  }
253
430
  }
431
+ if (target !== void 0) {
432
+ if (registry && registry.has(target)) {
433
+ const flags = registry.get(target).contextFlags ?? {};
434
+ for (const [key, value] of Object.entries(flags)) {
435
+ merged[key] = value;
436
+ }
437
+ } else {
438
+ merged[`target_${target.replace(/-/g, "_")}`] = true;
439
+ }
440
+ }
254
441
  return merged;
255
442
  }
256
- async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta, partialsMap, config, plugins, target, agentMap = {}) {
443
+ async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta, partialsMap, config, plugins, target, agentMap = {}, registry = defaultRegistry) {
257
444
  const personaMeta = await loadPersonaYaml(personaYamlPath);
258
- let context = buildContext(personaMeta, sharedMeta, agentMap);
445
+ let context = buildContext(personaMeta, sharedMeta, agentMap, target, registry);
259
446
  const personaMetaTyped = personaMeta;
260
- context = runBuildContext(plugins, context, personaMetaTyped, suiteConfig);
261
- const fmTemplate = resolveFrontmatterTemplate(target, plugins, config.frontmatter);
447
+ context = runBuildContext(plugins, context, personaMetaTyped, suiteConfig, target);
448
+ const fmTemplate = resolveFrontmatterTemplate(target, plugins, config.frontmatter, registry);
262
449
  const contentBasename = path4.basename(personaYamlPath, ".yaml") + ".md";
263
450
  const frontmatter = renderFrontmatter(fmTemplate, context, contentBasename);
264
451
  const contentSubdir = suiteConfig.contentSubdir ?? "content";
@@ -276,15 +463,10 @@ ${body}
276
463
  `);
277
464
  output = runPostRender(plugins, output, personaMetaTyped, target);
278
465
  const validationResults = runValidate(plugins, personaMetaTyped, suiteConfig, target);
279
- const outputDir = target === "vscode" ? suiteConfig.outVscode : suiteConfig.outClaudeCode;
280
- let outputBasename;
281
- if (target === "vscode" && typeof context["vs_file_name"] === "string") {
282
- outputBasename = context["vs_file_name"];
283
- } else if (target === "claude-code" && typeof context["cc_file_name"] === "string") {
284
- outputBasename = context["cc_file_name"];
285
- } else {
286
- outputBasename = contentBasename;
287
- }
466
+ const def = registry.has(target) ? registry.get(target) : void 0;
467
+ const outputDir = resolveOutputDir(target, suiteConfig, def);
468
+ const fnKey = def?.filenameContextKey;
469
+ const outputBasename = fnKey && typeof context[fnKey] === "string" ? context[fnKey] : contentBasename;
288
470
  const outputPath = path4.join(outputDir, outputBasename);
289
471
  const check = config.check ?? false;
290
472
  let written = false;
@@ -303,7 +485,7 @@ ${body}
303
485
  written
304
486
  };
305
487
  }
306
- async function buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap = {}) {
488
+ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap = {}, registry = defaultRegistry) {
307
489
  const metaSubdir = suiteConfig.metaSubdir ?? "meta";
308
490
  const sharedYamlPath = path4.join(suiteConfig.srcDir, metaSubdir, "_shared.yaml");
309
491
  const sharedMeta = await loadRawYaml(sharedYamlPath);
@@ -329,7 +511,8 @@ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agent
329
511
  config,
330
512
  plugins,
331
513
  target,
332
- agentMap
514
+ agentMap,
515
+ registry
333
516
  );
334
517
  results.push(result);
335
518
  }
@@ -337,12 +520,13 @@ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agent
337
520
  }
338
521
  async function build(config) {
339
522
  const plugins = config.plugins ?? [];
340
- const targets = config.targets ?? ["vscode", "claude-code"];
523
+ const registry = config.targetRegistry ?? defaultRegistry;
524
+ const targets = config.targets ?? registry.names().filter((n) => registry.get(n).defaultEnabled !== false);
341
525
  const allResults = [];
342
526
  const agentMap = await buildAgentNameMap(config);
343
527
  for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {
344
528
  for (const target of targets) {
345
- const suiteResults = await buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap);
529
+ const suiteResults = await buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap, registry);
346
530
  allResults.push(...suiteResults);
347
531
  }
348
532
  }
@@ -430,6 +614,6 @@ function escapeRegExp(str) {
430
614
  var _pkgRequire = createRequire(import.meta.url);
431
615
  var VERSION = _pkgRequire("../package.json").version;
432
616
 
433
- export { DEFAULT_FRONTMATTER_CLAUDE_CODE, DEFAULT_FRONTMATTER_VSCODE, VERSION, build, buildPersona, buildSuite, collapseBlankLines, discoverPersonaYamls, ensureBlankLineBeforeHeadings, escapeRegExp, loadContent, loadMetadata, loadPartials, normalizeNewlines, renderFrontmatter, resolveConditionals, resolveFrontmatterTemplate, resolvePartials, resolveVariables, runBuildContext, runPostRender, runSuiteInit, runValidate, serializeTools, serializeToolsList, validateFileName, validateStrictMarkers };
617
+ export { DEFAULT_FRONTMATTER_CLAUDE_CODE, DEFAULT_FRONTMATTER_DEEP_AGENTS, DEFAULT_FRONTMATTER_VSCODE, TARGET_CLAUDE_CODE, TARGET_DEEP_AGENTS, TARGET_VSCODE, TargetRegistry, VERSION, build, buildPersona, buildSuite, collapseBlankLines, defaultRegistry, discoverPersonaYamls, ensureBlankLineBeforeHeadings, escapeRegExp, loadContent, loadMetadata, loadPartials, normalizeNewlines, renderFrontmatter, resolveConditionals, resolveFrontmatterTemplate, resolvePartials, resolveVariables, runBuildContext, runPostRender, runSuiteInit, runValidate, serializeTools, serializeToolsList, validateFileName, validateStrictMarkers };
434
618
  //# sourceMappingURL=index.js.map
435
619
  //# sourceMappingURL=index.js.map