@mistralys/persona-builder 2.1.3 → 2.2.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/cli.js CHANGED
@@ -20,18 +20,27 @@ function resolvePartials(text, partialsMap, depth = 0) {
20
20
 
21
21
  // src/engine/conditionals.ts
22
22
  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
- }
23
+ const noNestedIf = String.raw`(?:(?!\{\{#if\b)[\s\S])*?`;
24
+ const pattern = new RegExp(
25
+ String.raw`\n*\{\{#if (\w+)\}\}(${noNestedIf})` + String.raw`(?:\{\{else\}\}(${noNestedIf}))?\{\{\/if\}\}\n*`,
26
+ "g"
34
27
  );
28
+ const resolve = (_match, flag, inner, elseInner) => {
29
+ if (context[flag]) {
30
+ return "\n" + inner.replace(/^\n+/, "").replace(/\n+$/, "") + "\n";
31
+ }
32
+ if (elseInner !== void 0) {
33
+ return "\n" + elseInner.replace(/^\n+/, "").replace(/\n+$/, "") + "\n";
34
+ }
35
+ return "\n";
36
+ };
37
+ let result = text;
38
+ let prev;
39
+ do {
40
+ prev = result;
41
+ result = result.replace(pattern, resolve);
42
+ } while (result !== prev);
43
+ return result;
35
44
  }
36
45
 
37
46
  // src/engine/variables.ts
@@ -90,11 +99,11 @@ function runSuiteInit(plugins, suite, sharedMeta) {
90
99
  }
91
100
  }
92
101
  }
93
- function runBuildContext(plugins, ctx, persona, suite) {
102
+ function runBuildContext(plugins, ctx, persona, suite, target) {
94
103
  let accumulated = ctx;
95
104
  for (const plugin of plugins) {
96
105
  if (typeof plugin.onBuildContext === "function") {
97
- accumulated = plugin.onBuildContext(accumulated, persona, suite);
106
+ accumulated = plugin.onBuildContext(accumulated, persona, suite, target);
98
107
  }
99
108
  }
100
109
  return accumulated;
@@ -119,7 +128,10 @@ function runValidate(plugins, persona, suite, target) {
119
128
  return results;
120
129
  }
121
130
 
122
- // src/builders/frontmatter.ts
131
+ // src/targets/types.ts
132
+ var TARGET_VSCODE = "vscode";
133
+ var TARGET_CLAUDE_CODE = "claude-code";
134
+ var TARGET_DEEP_AGENTS = "deep-agents";
123
135
  var DEFAULT_FRONTMATTER_VSCODE = `---
124
136
  name: '{{name}} v{{version}}'
125
137
  description: '{{description}}'
@@ -132,7 +144,13 @@ model: {{cc_model}}
132
144
  memory: {{cc_memory}}
133
145
  allowedTools: [{{cc_tools_list}}]
134
146
  ---`;
135
- function resolveFrontmatterTemplate(target, plugins, configTemplates) {
147
+ var DEFAULT_FRONTMATTER_DEEP_AGENTS = `---
148
+ name: {{name}}
149
+ description: {{description}}
150
+ ---`;
151
+
152
+ // src/builders/frontmatter.ts
153
+ function resolveFrontmatterTemplate(target, plugins, configTemplates, registry) {
136
154
  for (const plugin of plugins) {
137
155
  if (plugin.frontmatterTemplates && target in plugin.frontmatterTemplates) {
138
156
  const tpl = plugin.frontmatterTemplates[target];
@@ -143,7 +161,10 @@ function resolveFrontmatterTemplate(target, plugins, configTemplates) {
143
161
  const tpl = configTemplates[target];
144
162
  if (tpl !== void 0) return tpl;
145
163
  }
146
- return target === "vscode" ? DEFAULT_FRONTMATTER_VSCODE : DEFAULT_FRONTMATTER_CLAUDE_CODE;
164
+ if (registry && registry.has(target)) {
165
+ return registry.get(target).defaultFrontmatter;
166
+ }
167
+ return DEFAULT_FRONTMATTER_VSCODE;
147
168
  }
148
169
  function renderFrontmatter(template, context, filename) {
149
170
  let rendered = resolveConditionals(template, context);
@@ -151,6 +172,112 @@ function renderFrontmatter(template, context, filename) {
151
172
  return rendered;
152
173
  }
153
174
 
175
+ // src/targets/registry.ts
176
+ var TargetRegistry = class _TargetRegistry {
177
+ // Map preserves insertion order — names() and allDefinitions() are
178
+ // therefore deterministic and match registration sequence. This is
179
+ // intentional: the built-in registry guarantees ['vscode', 'claude-code']
180
+ // ordering for the default targets (AC-2).
181
+ _definitions = /* @__PURE__ */ new Map();
182
+ /**
183
+ * Register a new target definition.
184
+ *
185
+ * @param definition The target descriptor to register.
186
+ * @throws {Error} If a target with the same `name` is already registered.
187
+ */
188
+ register(definition) {
189
+ if (this._definitions.has(definition.name)) {
190
+ throw new Error(
191
+ `TargetRegistry: target "${definition.name}" is already registered. Use a unique name or remove the existing registration first.`
192
+ );
193
+ }
194
+ this._definitions.set(definition.name, definition);
195
+ }
196
+ /**
197
+ * Retrieve a registered target definition by name.
198
+ *
199
+ * Returns a shallow copy — mutating the returned object does not affect
200
+ * the registry's internal state.
201
+ *
202
+ * @param name The target name to look up.
203
+ * @returns A shallow copy of the matching TargetDefinition.
204
+ * @throws {Error} If no target with the given name is registered.
205
+ */
206
+ get(name) {
207
+ const def = this._definitions.get(name);
208
+ if (!def) {
209
+ const known = this.names().join(", ") || "(none)";
210
+ throw new Error(
211
+ `TargetRegistry: target "${name}" is not registered. Registered targets: ${known}.`
212
+ );
213
+ }
214
+ return { ...def };
215
+ }
216
+ /**
217
+ * Returns `true` if a target with the given name is registered.
218
+ *
219
+ * @param name The target name to check.
220
+ */
221
+ has(name) {
222
+ return this._definitions.has(name);
223
+ }
224
+ /**
225
+ * Returns the names of all registered targets, in registration order.
226
+ */
227
+ names() {
228
+ return Array.from(this._definitions.keys());
229
+ }
230
+ /**
231
+ * Returns all registered TargetDefinition objects, in registration order.
232
+ *
233
+ * Returns shallow copies — mutating a returned definition does not affect
234
+ * the registry's internal state.
235
+ */
236
+ allDefinitions() {
237
+ return Array.from(this._definitions.values()).map((def) => ({ ...def }));
238
+ }
239
+ /**
240
+ * Returns a new TargetRegistry pre-populated with the same definitions.
241
+ *
242
+ * Useful for test isolation: clone the `defaultRegistry` to get an
243
+ * independent copy that can be mutated without affecting the singleton.
244
+ */
245
+ clone() {
246
+ const copy = new _TargetRegistry();
247
+ for (const def of this._definitions.values()) {
248
+ copy.register({ ...def });
249
+ }
250
+ return copy;
251
+ }
252
+ };
253
+
254
+ // src/targets/built-in.ts
255
+ var defaultRegistry = new TargetRegistry();
256
+ defaultRegistry.register({
257
+ name: TARGET_VSCODE,
258
+ outputDirKey: "vscode",
259
+ filenameContextKey: "vs_file_name",
260
+ defaultFrontmatter: DEFAULT_FRONTMATTER_VSCODE,
261
+ contextFlags: { target_vscode: true },
262
+ defaultEnabled: true
263
+ });
264
+ defaultRegistry.register({
265
+ name: TARGET_CLAUDE_CODE,
266
+ outputDirKey: "claude-code",
267
+ filenameContextKey: "cc_file_name",
268
+ defaultFrontmatter: DEFAULT_FRONTMATTER_CLAUDE_CODE,
269
+ contextFlags: { target_claude_code: true },
270
+ defaultEnabled: true
271
+ });
272
+ defaultRegistry.register({
273
+ name: TARGET_DEEP_AGENTS,
274
+ outputDirKey: "deep-agents",
275
+ filenameContextKey: "da_file_name",
276
+ defaultFrontmatter: DEFAULT_FRONTMATTER_DEEP_AGENTS,
277
+ contextFlags: { target_deep_agents: true },
278
+ defaultEnabled: false
279
+ });
280
+
154
281
  // src/builders/persona-builder.ts
155
282
  async function discoverSuitePersonaYamls(suiteConfig) {
156
283
  const metaSubdir = suiteConfig.metaSubdir ?? "meta";
@@ -178,6 +305,18 @@ async function loadPersonaYaml(yamlPath) {
178
305
  }
179
306
  return record;
180
307
  }
308
+ function resolveOutputDir(target, suiteConfig, definition) {
309
+ const merged = {};
310
+ if (suiteConfig.outVscode) merged["vscode"] = suiteConfig.outVscode;
311
+ if (suiteConfig.outClaudeCode) merged["claude-code"] = suiteConfig.outClaudeCode;
312
+ if (suiteConfig.outputDirs) Object.assign(merged, suiteConfig.outputDirs);
313
+ const lookupKey = definition?.outputDirKey ?? target;
314
+ const dir = merged[lookupKey];
315
+ if (dir) return dir;
316
+ throw new Error(
317
+ `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.`
318
+ );
319
+ }
181
320
  async function buildAgentNameMap(config) {
182
321
  const agentMap = {};
183
322
  for (const [, suiteConfig] of Object.entries(config.suites)) {
@@ -191,13 +330,16 @@ async function buildAgentNameMap(config) {
191
330
  const slug = typeof persona["slug"] === "string" ? persona["slug"] : path2.basename(yamlPath, ".yaml");
192
331
  const name = typeof persona["name"] === "string" ? persona["name"] : slug;
193
332
  const version = typeof persona["version"] === "string" ? persona["version"] : defaultVersion;
194
- const key = `agent_${slug.replace(/-/g, "_")}`;
333
+ const underscoredSlug = slug.replace(/-/g, "_");
334
+ const key = `agent_${underscoredSlug}`;
195
335
  agentMap[key] = `${name} v${version}`;
336
+ const slugKey = `agent_slug_${underscoredSlug}`;
337
+ agentMap[slugKey] = slug;
196
338
  }
197
339
  }
198
340
  return agentMap;
199
341
  }
200
- function buildContext(personaMeta, sharedMeta, agentMap = {}) {
342
+ function buildContext(personaMeta, sharedMeta, agentMap = {}, target, registry) {
201
343
  const version = typeof personaMeta["version"] === "string" ? personaMeta["version"] : typeof sharedMeta["default_version"] === "string" ? sharedMeta["default_version"] : "0.0.0";
202
344
  const merged = {
203
345
  ...sharedMeta,
@@ -222,19 +364,42 @@ function buildContext(personaMeta, sharedMeta, agentMap = {}) {
222
364
  const ccFileName = merged["cc_file_name"];
223
365
  merged["cc_file_name_stem"] = ccFileName.replace(/\.md$/, "");
224
366
  }
367
+ if (!("da_file_name_stem" in merged) && typeof merged["da_file_name"] === "string") {
368
+ const daFileName = merged["da_file_name"];
369
+ merged["da_file_name_stem"] = daFileName.replace(/\.md$/, "");
370
+ }
371
+ if (typeof merged["da_file_name"] === "string") {
372
+ const daTools = Array.isArray(merged["da_tools"]) ? merged["da_tools"] : tools;
373
+ if (!("da_tools_list" in merged)) {
374
+ merged["da_tools_list"] = serializeToolsList(daTools);
375
+ }
376
+ if (!("da_tools_json" in merged)) {
377
+ merged["da_tools_json"] = serializeTools(daTools);
378
+ }
379
+ }
225
380
  for (const [key, value] of Object.entries(agentMap)) {
226
381
  if (!(key in merged)) {
227
382
  merged[key] = value;
228
383
  }
229
384
  }
385
+ if (target !== void 0) {
386
+ if (registry && registry.has(target)) {
387
+ const flags = registry.get(target).contextFlags ?? {};
388
+ for (const [key, value] of Object.entries(flags)) {
389
+ merged[key] = value;
390
+ }
391
+ } else {
392
+ merged[`target_${target.replace(/-/g, "_")}`] = true;
393
+ }
394
+ }
230
395
  return merged;
231
396
  }
232
- async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta, partialsMap, config, plugins, target, agentMap = {}) {
397
+ async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta, partialsMap, config, plugins, target, agentMap = {}, registry = defaultRegistry) {
233
398
  const personaMeta = await loadPersonaYaml(personaYamlPath);
234
- let context = buildContext(personaMeta, sharedMeta, agentMap);
399
+ let context = buildContext(personaMeta, sharedMeta, agentMap, target, registry);
235
400
  const personaMetaTyped = personaMeta;
236
- context = runBuildContext(plugins, context, personaMetaTyped, suiteConfig);
237
- const fmTemplate = resolveFrontmatterTemplate(target, plugins, config.frontmatter);
401
+ context = runBuildContext(plugins, context, personaMetaTyped, suiteConfig, target);
402
+ const fmTemplate = resolveFrontmatterTemplate(target, plugins, config.frontmatter, registry);
238
403
  const contentBasename = path2.basename(personaYamlPath, ".yaml") + ".md";
239
404
  const frontmatter = renderFrontmatter(fmTemplate, context, contentBasename);
240
405
  const contentSubdir = suiteConfig.contentSubdir ?? "content";
@@ -252,15 +417,10 @@ ${body}
252
417
  `);
253
418
  output = runPostRender(plugins, output, personaMetaTyped, target);
254
419
  const validationResults = runValidate(plugins, personaMetaTyped, suiteConfig, target);
255
- const outputDir = target === "vscode" ? suiteConfig.outVscode : suiteConfig.outClaudeCode;
256
- let outputBasename;
257
- if (target === "vscode" && typeof context["vs_file_name"] === "string") {
258
- outputBasename = context["vs_file_name"];
259
- } else if (target === "claude-code" && typeof context["cc_file_name"] === "string") {
260
- outputBasename = context["cc_file_name"];
261
- } else {
262
- outputBasename = contentBasename;
263
- }
420
+ const def = registry.has(target) ? registry.get(target) : void 0;
421
+ const outputDir = resolveOutputDir(target, suiteConfig, def);
422
+ const fnKey = def?.filenameContextKey;
423
+ const outputBasename = fnKey && typeof context[fnKey] === "string" ? context[fnKey] : contentBasename;
264
424
  const outputPath = path2.join(outputDir, outputBasename);
265
425
  const check = config.check ?? false;
266
426
  let written = false;
@@ -279,7 +439,7 @@ ${body}
279
439
  written
280
440
  };
281
441
  }
282
- async function buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap = {}) {
442
+ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap = {}, registry = defaultRegistry) {
283
443
  const metaSubdir = suiteConfig.metaSubdir ?? "meta";
284
444
  const sharedYamlPath = path2.join(suiteConfig.srcDir, metaSubdir, "_shared.yaml");
285
445
  const sharedMeta = await loadRawYaml(sharedYamlPath);
@@ -305,7 +465,8 @@ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agent
305
465
  config,
306
466
  plugins,
307
467
  target,
308
- agentMap
468
+ agentMap,
469
+ registry
309
470
  );
310
471
  results.push(result);
311
472
  }
@@ -313,12 +474,13 @@ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agent
313
474
  }
314
475
  async function build(config) {
315
476
  const plugins = config.plugins ?? [];
316
- const targets = config.targets ?? ["vscode", "claude-code"];
477
+ const registry = config.targetRegistry ?? defaultRegistry;
478
+ const targets = config.targets ?? registry.names().filter((n) => registry.get(n).defaultEnabled !== false);
317
479
  const allResults = [];
318
480
  const agentMap = await buildAgentNameMap(config);
319
481
  for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {
320
482
  for (const target of targets) {
321
- const suiteResults = await buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap);
483
+ const suiteResults = await buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap, registry);
322
484
  allResults.push(...suiteResults);
323
485
  }
324
486
  }