@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/README.md +7 -3
- package/dist/cli.cjs +197 -35
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +197 -35
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +205 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +307 -45
- package/dist/index.d.ts +307 -45
- package/dist/index.js +200 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ Define your personas once as simple YAML + Markdown sources, and the library gen
|
|
|
6
6
|
|
|
7
7
|
## ✨ Features
|
|
8
8
|
|
|
9
|
-
- **
|
|
9
|
+
- **Multi-target output** — generates VS Code `.agent.md`, Claude Code `.md`, Deep Agents `.md`, and any custom format from a single source
|
|
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
|
|
10
11
|
- **YAML + Markdown templating** — separate metadata from content; merge them at build time with `{{variables}}`, `{{> partials}}`, and `{{#if}}` conditionals
|
|
11
12
|
- **Shared + per-suite partials** — reuse content fragments across personas with local overrides
|
|
12
13
|
- **Plugin architecture** — hook into context building, post-rendering, validation, and frontmatter generation
|
|
@@ -34,8 +35,10 @@ const summary = await build({
|
|
|
34
35
|
suites: {
|
|
35
36
|
'my-suite': {
|
|
36
37
|
srcDir: path.resolve('./personas/my-suite'),
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
outputDirs: {
|
|
39
|
+
vscode: path.resolve('./dist/vscode'),
|
|
40
|
+
'claude-code': path.resolve('./dist/claude-code'),
|
|
41
|
+
},
|
|
39
42
|
},
|
|
40
43
|
},
|
|
41
44
|
sharedPartialsDir: path.resolve('./personas/shared/partials'),
|
|
@@ -75,6 +78,7 @@ See the [CLI docs](docs/cli.md) for config file format and all flags.
|
|
|
75
78
|
| [Configuration Reference](docs/configuration.md) | `BuildConfig`, `SuiteConfig`, and `BuildSummary` fields |
|
|
76
79
|
| [CLI Reference](docs/cli.md) | Command-line flags, config file format, and common patterns |
|
|
77
80
|
| [Public API](docs/api.md) | All exported types and functions |
|
|
81
|
+
| [Project Manifest](docs/agents/project-manifest/README.md) | Canonical documentation for AI agent sessions |
|
|
78
82
|
|
|
79
83
|
## 🔌 Plugins
|
|
80
84
|
|
package/dist/cli.cjs
CHANGED
|
@@ -28,18 +28,27 @@ function resolvePartials(text, partialsMap, depth = 0) {
|
|
|
28
28
|
|
|
29
29
|
// src/engine/conditionals.ts
|
|
30
30
|
function resolveConditionals(text, context) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
(
|
|
34
|
-
|
|
35
|
-
return "\n" + inner.replace(/^\n+/, "").replace(/\n+$/, "") + "\n";
|
|
36
|
-
}
|
|
37
|
-
if (elseInner !== void 0) {
|
|
38
|
-
return "\n" + elseInner.replace(/^\n+/, "").replace(/\n+$/, "") + "\n";
|
|
39
|
-
}
|
|
40
|
-
return "\n";
|
|
41
|
-
}
|
|
31
|
+
const noNestedIf = String.raw`(?:(?!\{\{#if\b)[\s\S])*?`;
|
|
32
|
+
const pattern = new RegExp(
|
|
33
|
+
String.raw`\n*\{\{#if (\w+)\}\}(${noNestedIf})` + String.raw`(?:\{\{else\}\}(${noNestedIf}))?\{\{\/if\}\}\n*`,
|
|
34
|
+
"g"
|
|
42
35
|
);
|
|
36
|
+
const resolve = (_match, flag, inner, elseInner) => {
|
|
37
|
+
if (context[flag]) {
|
|
38
|
+
return "\n" + inner.replace(/^\n+/, "").replace(/\n+$/, "") + "\n";
|
|
39
|
+
}
|
|
40
|
+
if (elseInner !== void 0) {
|
|
41
|
+
return "\n" + elseInner.replace(/^\n+/, "").replace(/\n+$/, "") + "\n";
|
|
42
|
+
}
|
|
43
|
+
return "\n";
|
|
44
|
+
};
|
|
45
|
+
let result = text;
|
|
46
|
+
let prev;
|
|
47
|
+
do {
|
|
48
|
+
prev = result;
|
|
49
|
+
result = result.replace(pattern, resolve);
|
|
50
|
+
} while (result !== prev);
|
|
51
|
+
return result;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
// src/engine/variables.ts
|
|
@@ -98,11 +107,11 @@ function runSuiteInit(plugins, suite, sharedMeta) {
|
|
|
98
107
|
}
|
|
99
108
|
}
|
|
100
109
|
}
|
|
101
|
-
function runBuildContext(plugins, ctx, persona, suite) {
|
|
110
|
+
function runBuildContext(plugins, ctx, persona, suite, target) {
|
|
102
111
|
let accumulated = ctx;
|
|
103
112
|
for (const plugin of plugins) {
|
|
104
113
|
if (typeof plugin.onBuildContext === "function") {
|
|
105
|
-
accumulated = plugin.onBuildContext(accumulated, persona, suite);
|
|
114
|
+
accumulated = plugin.onBuildContext(accumulated, persona, suite, target);
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
return accumulated;
|
|
@@ -127,7 +136,10 @@ function runValidate(plugins, persona, suite, target) {
|
|
|
127
136
|
return results;
|
|
128
137
|
}
|
|
129
138
|
|
|
130
|
-
// src/
|
|
139
|
+
// src/targets/types.ts
|
|
140
|
+
var TARGET_VSCODE = "vscode";
|
|
141
|
+
var TARGET_CLAUDE_CODE = "claude-code";
|
|
142
|
+
var TARGET_DEEP_AGENTS = "deep-agents";
|
|
131
143
|
var DEFAULT_FRONTMATTER_VSCODE = `---
|
|
132
144
|
name: '{{name}} v{{version}}'
|
|
133
145
|
description: '{{description}}'
|
|
@@ -140,7 +152,13 @@ model: {{cc_model}}
|
|
|
140
152
|
memory: {{cc_memory}}
|
|
141
153
|
allowedTools: [{{cc_tools_list}}]
|
|
142
154
|
---`;
|
|
143
|
-
|
|
155
|
+
var DEFAULT_FRONTMATTER_DEEP_AGENTS = `---
|
|
156
|
+
name: {{name}}
|
|
157
|
+
description: {{description}}
|
|
158
|
+
---`;
|
|
159
|
+
|
|
160
|
+
// src/builders/frontmatter.ts
|
|
161
|
+
function resolveFrontmatterTemplate(target, plugins, configTemplates, registry) {
|
|
144
162
|
for (const plugin of plugins) {
|
|
145
163
|
if (plugin.frontmatterTemplates && target in plugin.frontmatterTemplates) {
|
|
146
164
|
const tpl = plugin.frontmatterTemplates[target];
|
|
@@ -151,7 +169,10 @@ function resolveFrontmatterTemplate(target, plugins, configTemplates) {
|
|
|
151
169
|
const tpl = configTemplates[target];
|
|
152
170
|
if (tpl !== void 0) return tpl;
|
|
153
171
|
}
|
|
154
|
-
|
|
172
|
+
if (registry && registry.has(target)) {
|
|
173
|
+
return registry.get(target).defaultFrontmatter;
|
|
174
|
+
}
|
|
175
|
+
return DEFAULT_FRONTMATTER_VSCODE;
|
|
155
176
|
}
|
|
156
177
|
function renderFrontmatter(template, context, filename) {
|
|
157
178
|
let rendered = resolveConditionals(template, context);
|
|
@@ -159,6 +180,112 @@ function renderFrontmatter(template, context, filename) {
|
|
|
159
180
|
return rendered;
|
|
160
181
|
}
|
|
161
182
|
|
|
183
|
+
// src/targets/registry.ts
|
|
184
|
+
var TargetRegistry = class _TargetRegistry {
|
|
185
|
+
// Map preserves insertion order — names() and allDefinitions() are
|
|
186
|
+
// therefore deterministic and match registration sequence. This is
|
|
187
|
+
// intentional: the built-in registry guarantees ['vscode', 'claude-code']
|
|
188
|
+
// ordering for the default targets (AC-2).
|
|
189
|
+
_definitions = /* @__PURE__ */ new Map();
|
|
190
|
+
/**
|
|
191
|
+
* Register a new target definition.
|
|
192
|
+
*
|
|
193
|
+
* @param definition The target descriptor to register.
|
|
194
|
+
* @throws {Error} If a target with the same `name` is already registered.
|
|
195
|
+
*/
|
|
196
|
+
register(definition) {
|
|
197
|
+
if (this._definitions.has(definition.name)) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`TargetRegistry: target "${definition.name}" is already registered. Use a unique name or remove the existing registration first.`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
this._definitions.set(definition.name, definition);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Retrieve a registered target definition by name.
|
|
206
|
+
*
|
|
207
|
+
* Returns a shallow copy — mutating the returned object does not affect
|
|
208
|
+
* the registry's internal state.
|
|
209
|
+
*
|
|
210
|
+
* @param name The target name to look up.
|
|
211
|
+
* @returns A shallow copy of the matching TargetDefinition.
|
|
212
|
+
* @throws {Error} If no target with the given name is registered.
|
|
213
|
+
*/
|
|
214
|
+
get(name) {
|
|
215
|
+
const def = this._definitions.get(name);
|
|
216
|
+
if (!def) {
|
|
217
|
+
const known = this.names().join(", ") || "(none)";
|
|
218
|
+
throw new Error(
|
|
219
|
+
`TargetRegistry: target "${name}" is not registered. Registered targets: ${known}.`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return { ...def };
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Returns `true` if a target with the given name is registered.
|
|
226
|
+
*
|
|
227
|
+
* @param name The target name to check.
|
|
228
|
+
*/
|
|
229
|
+
has(name) {
|
|
230
|
+
return this._definitions.has(name);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Returns the names of all registered targets, in registration order.
|
|
234
|
+
*/
|
|
235
|
+
names() {
|
|
236
|
+
return Array.from(this._definitions.keys());
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Returns all registered TargetDefinition objects, in registration order.
|
|
240
|
+
*
|
|
241
|
+
* Returns shallow copies — mutating a returned definition does not affect
|
|
242
|
+
* the registry's internal state.
|
|
243
|
+
*/
|
|
244
|
+
allDefinitions() {
|
|
245
|
+
return Array.from(this._definitions.values()).map((def) => ({ ...def }));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Returns a new TargetRegistry pre-populated with the same definitions.
|
|
249
|
+
*
|
|
250
|
+
* Useful for test isolation: clone the `defaultRegistry` to get an
|
|
251
|
+
* independent copy that can be mutated without affecting the singleton.
|
|
252
|
+
*/
|
|
253
|
+
clone() {
|
|
254
|
+
const copy = new _TargetRegistry();
|
|
255
|
+
for (const def of this._definitions.values()) {
|
|
256
|
+
copy.register({ ...def });
|
|
257
|
+
}
|
|
258
|
+
return copy;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// src/targets/built-in.ts
|
|
263
|
+
var defaultRegistry = new TargetRegistry();
|
|
264
|
+
defaultRegistry.register({
|
|
265
|
+
name: TARGET_VSCODE,
|
|
266
|
+
outputDirKey: "vscode",
|
|
267
|
+
filenameContextKey: "vs_file_name",
|
|
268
|
+
defaultFrontmatter: DEFAULT_FRONTMATTER_VSCODE,
|
|
269
|
+
contextFlags: { target_vscode: true },
|
|
270
|
+
defaultEnabled: true
|
|
271
|
+
});
|
|
272
|
+
defaultRegistry.register({
|
|
273
|
+
name: TARGET_CLAUDE_CODE,
|
|
274
|
+
outputDirKey: "claude-code",
|
|
275
|
+
filenameContextKey: "cc_file_name",
|
|
276
|
+
defaultFrontmatter: DEFAULT_FRONTMATTER_CLAUDE_CODE,
|
|
277
|
+
contextFlags: { target_claude_code: true },
|
|
278
|
+
defaultEnabled: true
|
|
279
|
+
});
|
|
280
|
+
defaultRegistry.register({
|
|
281
|
+
name: TARGET_DEEP_AGENTS,
|
|
282
|
+
outputDirKey: "deep-agents",
|
|
283
|
+
filenameContextKey: "da_file_name",
|
|
284
|
+
defaultFrontmatter: DEFAULT_FRONTMATTER_DEEP_AGENTS,
|
|
285
|
+
contextFlags: { target_deep_agents: true },
|
|
286
|
+
defaultEnabled: false
|
|
287
|
+
});
|
|
288
|
+
|
|
162
289
|
// src/builders/persona-builder.ts
|
|
163
290
|
async function discoverSuitePersonaYamls(suiteConfig) {
|
|
164
291
|
const metaSubdir = suiteConfig.metaSubdir ?? "meta";
|
|
@@ -186,6 +313,18 @@ async function loadPersonaYaml(yamlPath) {
|
|
|
186
313
|
}
|
|
187
314
|
return record;
|
|
188
315
|
}
|
|
316
|
+
function resolveOutputDir(target, suiteConfig, definition) {
|
|
317
|
+
const merged = {};
|
|
318
|
+
if (suiteConfig.outVscode) merged["vscode"] = suiteConfig.outVscode;
|
|
319
|
+
if (suiteConfig.outClaudeCode) merged["claude-code"] = suiteConfig.outClaudeCode;
|
|
320
|
+
if (suiteConfig.outputDirs) Object.assign(merged, suiteConfig.outputDirs);
|
|
321
|
+
const lookupKey = definition?.outputDirKey ?? target;
|
|
322
|
+
const dir = merged[lookupKey];
|
|
323
|
+
if (dir) return dir;
|
|
324
|
+
throw new Error(
|
|
325
|
+
`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.`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
189
328
|
async function buildAgentNameMap(config) {
|
|
190
329
|
const agentMap = {};
|
|
191
330
|
for (const [, suiteConfig] of Object.entries(config.suites)) {
|
|
@@ -199,13 +338,16 @@ async function buildAgentNameMap(config) {
|
|
|
199
338
|
const slug = typeof persona["slug"] === "string" ? persona["slug"] : path2__default.default.basename(yamlPath, ".yaml");
|
|
200
339
|
const name = typeof persona["name"] === "string" ? persona["name"] : slug;
|
|
201
340
|
const version = typeof persona["version"] === "string" ? persona["version"] : defaultVersion;
|
|
202
|
-
const
|
|
341
|
+
const underscoredSlug = slug.replace(/-/g, "_");
|
|
342
|
+
const key = `agent_${underscoredSlug}`;
|
|
203
343
|
agentMap[key] = `${name} v${version}`;
|
|
344
|
+
const slugKey = `agent_slug_${underscoredSlug}`;
|
|
345
|
+
agentMap[slugKey] = slug;
|
|
204
346
|
}
|
|
205
347
|
}
|
|
206
348
|
return agentMap;
|
|
207
349
|
}
|
|
208
|
-
function buildContext(personaMeta, sharedMeta, agentMap = {}) {
|
|
350
|
+
function buildContext(personaMeta, sharedMeta, agentMap = {}, target, registry) {
|
|
209
351
|
const version = typeof personaMeta["version"] === "string" ? personaMeta["version"] : typeof sharedMeta["default_version"] === "string" ? sharedMeta["default_version"] : "0.0.0";
|
|
210
352
|
const merged = {
|
|
211
353
|
...sharedMeta,
|
|
@@ -230,19 +372,42 @@ function buildContext(personaMeta, sharedMeta, agentMap = {}) {
|
|
|
230
372
|
const ccFileName = merged["cc_file_name"];
|
|
231
373
|
merged["cc_file_name_stem"] = ccFileName.replace(/\.md$/, "");
|
|
232
374
|
}
|
|
375
|
+
if (!("da_file_name_stem" in merged) && typeof merged["da_file_name"] === "string") {
|
|
376
|
+
const daFileName = merged["da_file_name"];
|
|
377
|
+
merged["da_file_name_stem"] = daFileName.replace(/\.md$/, "");
|
|
378
|
+
}
|
|
379
|
+
if (typeof merged["da_file_name"] === "string") {
|
|
380
|
+
const daTools = Array.isArray(merged["da_tools"]) ? merged["da_tools"] : tools;
|
|
381
|
+
if (!("da_tools_list" in merged)) {
|
|
382
|
+
merged["da_tools_list"] = serializeToolsList(daTools);
|
|
383
|
+
}
|
|
384
|
+
if (!("da_tools_json" in merged)) {
|
|
385
|
+
merged["da_tools_json"] = serializeTools(daTools);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
233
388
|
for (const [key, value] of Object.entries(agentMap)) {
|
|
234
389
|
if (!(key in merged)) {
|
|
235
390
|
merged[key] = value;
|
|
236
391
|
}
|
|
237
392
|
}
|
|
393
|
+
if (target !== void 0) {
|
|
394
|
+
if (registry && registry.has(target)) {
|
|
395
|
+
const flags = registry.get(target).contextFlags ?? {};
|
|
396
|
+
for (const [key, value] of Object.entries(flags)) {
|
|
397
|
+
merged[key] = value;
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
merged[`target_${target.replace(/-/g, "_")}`] = true;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
238
403
|
return merged;
|
|
239
404
|
}
|
|
240
|
-
async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta, partialsMap, config, plugins, target, agentMap = {}) {
|
|
405
|
+
async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta, partialsMap, config, plugins, target, agentMap = {}, registry = defaultRegistry) {
|
|
241
406
|
const personaMeta = await loadPersonaYaml(personaYamlPath);
|
|
242
|
-
let context = buildContext(personaMeta, sharedMeta, agentMap);
|
|
407
|
+
let context = buildContext(personaMeta, sharedMeta, agentMap, target, registry);
|
|
243
408
|
const personaMetaTyped = personaMeta;
|
|
244
|
-
context = runBuildContext(plugins, context, personaMetaTyped, suiteConfig);
|
|
245
|
-
const fmTemplate = resolveFrontmatterTemplate(target, plugins, config.frontmatter);
|
|
409
|
+
context = runBuildContext(plugins, context, personaMetaTyped, suiteConfig, target);
|
|
410
|
+
const fmTemplate = resolveFrontmatterTemplate(target, plugins, config.frontmatter, registry);
|
|
246
411
|
const contentBasename = path2__default.default.basename(personaYamlPath, ".yaml") + ".md";
|
|
247
412
|
const frontmatter = renderFrontmatter(fmTemplate, context, contentBasename);
|
|
248
413
|
const contentSubdir = suiteConfig.contentSubdir ?? "content";
|
|
@@ -260,15 +425,10 @@ ${body}
|
|
|
260
425
|
`);
|
|
261
426
|
output = runPostRender(plugins, output, personaMetaTyped, target);
|
|
262
427
|
const validationResults = runValidate(plugins, personaMetaTyped, suiteConfig, target);
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
} else if (target === "claude-code" && typeof context["cc_file_name"] === "string") {
|
|
268
|
-
outputBasename = context["cc_file_name"];
|
|
269
|
-
} else {
|
|
270
|
-
outputBasename = contentBasename;
|
|
271
|
-
}
|
|
428
|
+
const def = registry.has(target) ? registry.get(target) : void 0;
|
|
429
|
+
const outputDir = resolveOutputDir(target, suiteConfig, def);
|
|
430
|
+
const fnKey = def?.filenameContextKey;
|
|
431
|
+
const outputBasename = fnKey && typeof context[fnKey] === "string" ? context[fnKey] : contentBasename;
|
|
272
432
|
const outputPath = path2__default.default.join(outputDir, outputBasename);
|
|
273
433
|
const check = config.check ?? false;
|
|
274
434
|
let written = false;
|
|
@@ -287,7 +447,7 @@ ${body}
|
|
|
287
447
|
written
|
|
288
448
|
};
|
|
289
449
|
}
|
|
290
|
-
async function buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap = {}) {
|
|
450
|
+
async function buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap = {}, registry = defaultRegistry) {
|
|
291
451
|
const metaSubdir = suiteConfig.metaSubdir ?? "meta";
|
|
292
452
|
const sharedYamlPath = path2__default.default.join(suiteConfig.srcDir, metaSubdir, "_shared.yaml");
|
|
293
453
|
const sharedMeta = await loadRawYaml(sharedYamlPath);
|
|
@@ -313,7 +473,8 @@ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agent
|
|
|
313
473
|
config,
|
|
314
474
|
plugins,
|
|
315
475
|
target,
|
|
316
|
-
agentMap
|
|
476
|
+
agentMap,
|
|
477
|
+
registry
|
|
317
478
|
);
|
|
318
479
|
results.push(result);
|
|
319
480
|
}
|
|
@@ -321,12 +482,13 @@ async function buildSuite(suiteName, suiteConfig, config, plugins, target, agent
|
|
|
321
482
|
}
|
|
322
483
|
async function build(config) {
|
|
323
484
|
const plugins = config.plugins ?? [];
|
|
324
|
-
const
|
|
485
|
+
const registry = config.targetRegistry ?? defaultRegistry;
|
|
486
|
+
const targets = config.targets ?? registry.names().filter((n) => registry.get(n).defaultEnabled !== false);
|
|
325
487
|
const allResults = [];
|
|
326
488
|
const agentMap = await buildAgentNameMap(config);
|
|
327
489
|
for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {
|
|
328
490
|
for (const target of targets) {
|
|
329
|
-
const suiteResults = await buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap);
|
|
491
|
+
const suiteResults = await buildSuite(suiteName, suiteConfig, config, plugins, target, agentMap, registry);
|
|
330
492
|
allResults.push(...suiteResults);
|
|
331
493
|
}
|
|
332
494
|
}
|