@mistralys/persona-builder 0.2.0 → 2.0.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 +59 -3
- package/dist/cli.cjs +4 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +9 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -10
- package/dist/index.d.ts +34 -10
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -21,13 +21,13 @@ Define your personas once as simple YAML + Markdown sources, and the library gen
|
|
|
21
21
|
## 🚀 Quick Start
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npm install @
|
|
24
|
+
npm install @mistralys/persona-builder
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
### Programmatic API
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
|
-
import { build } from '@
|
|
30
|
+
import { build } from '@mistralys/persona-builder';
|
|
31
31
|
import path from 'node:path';
|
|
32
32
|
|
|
33
33
|
const summary = await build({
|
|
@@ -64,9 +64,65 @@ See the [CLI docs](docs/cli.md) for config file format and all flags.
|
|
|
64
64
|
| [Template Syntax](docs/template-syntax.md) | Variables, partials, conditionals, and built-in context variables |
|
|
65
65
|
| [Configuration Reference](docs/configuration.md) | `BuildConfig`, `SuiteConfig`, and `BuildSummary` fields |
|
|
66
66
|
| [CLI Reference](docs/cli.md) | Command-line flags, config file format, and common patterns |
|
|
67
|
-
| [Plugins](docs/plugins.md) | `PersonaBuildPlugin` interface and
|
|
67
|
+
| [Plugins](docs/plugins.md) | `PersonaBuildPlugin` interface, examples, and the built-in Ledger Plugin |
|
|
68
68
|
| [Public API](docs/api.md) | All exported types and functions |
|
|
69
69
|
|
|
70
|
+
## 🔌 Ledger Plugin
|
|
71
|
+
|
|
72
|
+
The ledger plugin is a first-party plugin shipped as a sub-path export. It adds ledger-specific rendering (roster table, MCP tools table) and role validation into the standard build hooks.
|
|
73
|
+
|
|
74
|
+
### Installation
|
|
75
|
+
|
|
76
|
+
The plugin ships with the library — no extra install needed.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install @mistralys/persona-builder
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Usage
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
// personas/persona-build.config.js
|
|
86
|
+
const { ledgerPlugin } = require('@mistralys/persona-builder/plugins/ledger');
|
|
87
|
+
const manifest = require('../shared/workflow-manifest.json');
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
rootDir: __dirname,
|
|
91
|
+
sharedPartialsDir: './shared/partials',
|
|
92
|
+
suites: {
|
|
93
|
+
ledger: {
|
|
94
|
+
srcDir: './ledger/src',
|
|
95
|
+
outVscode: './ledger/vs-code',
|
|
96
|
+
outClaudeCode: './ledger/claude-code',
|
|
97
|
+
personaMode: 'numbered',
|
|
98
|
+
},
|
|
99
|
+
standalone: {
|
|
100
|
+
srcDir: './standalone/src',
|
|
101
|
+
outVscode: './standalone/vs-code',
|
|
102
|
+
outClaudeCode: './standalone/claude-code',
|
|
103
|
+
personaMode: 'standalone',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
plugins: [
|
|
107
|
+
ledgerPlugin({
|
|
108
|
+
manifestRoles: manifest.roles.map(r => r.name),
|
|
109
|
+
warnOnUnknownRole: true,
|
|
110
|
+
}),
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Options — `LedgerPluginOptions`
|
|
116
|
+
|
|
117
|
+
| Option | Type | Default | Description |
|
|
118
|
+
|--------|------|---------|-------------|
|
|
119
|
+
| `manifestRoles` | `ReadonlyArray<string>` | `[]` | Canonical role names from your workflow manifest. Each persona's `role` field is validated against this list. When omitted or empty, role validation is skipped. |
|
|
120
|
+
| `warnOnUnknownRole` | `boolean` | `true` | When `true`, an unknown `role` field emits a warning-level validation result. |
|
|
121
|
+
|
|
122
|
+
See the [Plugins reference](docs/plugins.md#ledger-plugin----mistralys-persona-builderpluginsledger) for full hook documentation and exported types (`RosterEntry`, `McpToolEntry`).
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
70
126
|
## 📄 License
|
|
71
127
|
|
|
72
128
|
MIT
|
package/dist/cli.cjs
CHANGED
|
@@ -116,11 +116,11 @@ function runPostRender(plugins, rendered, persona, target) {
|
|
|
116
116
|
}
|
|
117
117
|
return output;
|
|
118
118
|
}
|
|
119
|
-
function runValidate(plugins, persona, suite) {
|
|
119
|
+
function runValidate(plugins, persona, suite, target) {
|
|
120
120
|
const results = [];
|
|
121
121
|
for (const plugin of plugins) {
|
|
122
122
|
if (typeof plugin.onValidate === "function") {
|
|
123
|
-
const pluginResults = plugin.onValidate(persona, suite);
|
|
123
|
+
const pluginResults = plugin.onValidate(persona, suite, target);
|
|
124
124
|
results.push(...pluginResults);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
@@ -235,7 +235,7 @@ async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta,
|
|
|
235
235
|
${body}
|
|
236
236
|
`);
|
|
237
237
|
output = runPostRender(plugins, output, personaMetaTyped, target);
|
|
238
|
-
const validationResults = runValidate(plugins, personaMetaTyped, suiteConfig);
|
|
238
|
+
const validationResults = runValidate(plugins, personaMetaTyped, suiteConfig, target);
|
|
239
239
|
const outputDir = target === "vscode" ? suiteConfig.outVscode : suiteConfig.outClaudeCode;
|
|
240
240
|
let outputBasename;
|
|
241
241
|
if (target === "vscode" && typeof context["vs_file_name"] === "string") {
|
|
@@ -331,7 +331,7 @@ ${messages}`
|
|
|
331
331
|
var _pkgRequire = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
|
|
332
332
|
var VERSION = _pkgRequire("../package.json").version;
|
|
333
333
|
var USAGE = `
|
|
334
|
-
@
|
|
334
|
+
@mistralys/persona-builder v${VERSION}
|
|
335
335
|
|
|
336
336
|
Build AI persona documents from YAML metadata and Markdown content templates.
|
|
337
337
|
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/engine/partials.ts","../src/engine/conditionals.ts","../src/engine/variables.ts","../src/engine/postProcessor.ts","../src/engine/serializer.ts","../src/loaders/partials-loader.ts","../src/plugins/runner.ts","../src/builders/frontmatter.ts","../src/builders/persona-builder.ts","../src/cli.ts"],"names":["readdir","path","readFile","existsSync","yaml","mkdir","writeFile","createRequire","require","pathToFileURL"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\r\n * partials.ts\r\n *\r\n * Pure template-engine function for resolving partial inclusions.\r\n * Supports {{> name}} syntax with up to depth-2 recursion to handle\r\n * partials-within-partials. No file-system I/O.\r\n */\r\n\r\n/**\r\n * Resolve partial inclusions in a template string.\r\n *\r\n * Replaces `{{> name}}` markers with the content from `partialsMap`.\r\n * Recursion is capped at depth 2 so that:\r\n * - depth 0 → 1: outer partials are expanded\r\n * - depth 1 → 2: one level of nested partials are expanded\r\n * - depth 2: recursion stops, marker is left as-is\r\n *\r\n * Each resolved partial is `trimEnd()`-ed to prevent trailing blank lines\r\n * from causing double-blank-line artefacts during concatenation.\r\n *\r\n * If a partial name is not found in `partialsMap`, the original marker is\r\n * preserved and a warning is emitted via `console.warn`.\r\n *\r\n * @param text - Template string potentially containing {{> name}} markers\r\n * @param partialsMap - Map of partial name → partial content\r\n * @param depth - Current recursion depth (callers should omit; defaults to 0)\r\n * @returns The template string with partial markers replaced\r\n */\r\nexport function resolvePartials(\r\n text: string,\r\n partialsMap: Record<string, string>,\r\n depth = 0,\r\n): string {\r\n if (depth >= 2) return text;\r\n return text.replace(/\\{\\{> ([\\w-]+)\\}\\}/g, (match, name: string) => {\r\n if (!(name in partialsMap)) {\r\n console.warn(`[WARN] Partial not found: ${match}`);\r\n return match;\r\n }\r\n // Recursively resolve nested partials (depth + 1).\r\n // trimEnd() strips trailing whitespace to avoid extra blank lines.\r\n return resolvePartials(partialsMap[name], partialsMap, depth + 1).trimEnd();\r\n });\r\n}\r\n","/**\r\n * conditionals.ts\r\n *\r\n * Pure template-engine function for resolving conditional blocks.\r\n * Handles {{#if flag}}…{{/if}} and {{#if flag}}…{{else}}…{{/if}} syntax.\r\n * No file-system I/O.\r\n */\r\n\r\n/**\r\n * Resolve conditional blocks in a template string.\r\n *\r\n * Syntax:\r\n * `{{#if flag}}content{{/if}}`\r\n * `{{#if flag}}truthy-content{{else}}falsy-content{{/if}}`\r\n *\r\n * Behaviour:\r\n * - When `context[flag]` is truthy: the delimiters are stripped and the\r\n * content before `{{else}}` (or the entire inner block if no `{{else}}`)\r\n * is kept, surrounded by single `\\n` delimiters.\r\n * - When `context[flag]` is falsy and a `{{else}}` branch exists: the\r\n * content after `{{else}}` is kept, surrounded by single `\\n` delimiters.\r\n * - When `context[flag]` is falsy and no `{{else}}` branch exists: the\r\n * entire block (including surrounding newlines) is removed, leaving a\r\n * single `\\n`.\r\n * - Unknown flags (absent from context) are treated as falsy.\r\n *\r\n * Leading and trailing newlines within the kept content are trimmed so the\r\n * output does not accumulate extra blank lines.\r\n *\r\n * @param text - Template string potentially containing {{#if}} blocks\r\n * @param context - Key-value map used to evaluate flag truthiness\r\n * @returns The template string with conditional blocks resolved\r\n */\r\nexport function resolveConditionals(\r\n text: string,\r\n context: Record<string, unknown>,\r\n): string {\r\n return text.replace(\r\n /\\n*\\{\\{#if (\\w+)\\}\\}([\\s\\S]*?)(?:\\{\\{else\\}\\}([\\s\\S]*?))?\\{\\{\\/if\\}\\}\\n*/g,\r\n (\r\n _match: string,\r\n flag: string,\r\n inner: string,\r\n elseInner: string | undefined,\r\n ) => {\r\n if (context[flag]) {\r\n // Truthy: keep content before {{else}} (or entire inner if no {{else}})\r\n return '\\n' + inner.replace(/^\\n+/, '').replace(/\\n+$/, '') + '\\n';\r\n }\r\n if (elseInner !== undefined) {\r\n // Falsy with {{else}}: keep content after {{else}}\r\n return '\\n' + elseInner.replace(/^\\n+/, '').replace(/\\n+$/, '') + '\\n';\r\n }\r\n // Falsy without {{else}}: remove entire block\r\n return '\\n';\r\n },\r\n );\r\n}\r\n","/**\r\n * variables.ts\r\n *\r\n * Pure template-engine function for resolving variable substitutions.\r\n * Handles {{varName}} syntax. No file-system I/O.\r\n */\r\n\r\n/**\r\n * Resolve variable substitutions in a template string.\r\n *\r\n * Replaces `{{varName}}` markers with `String(context[varName])`.\r\n * If a variable is not found in `context` (or its value is `undefined`),\r\n * the original marker is preserved and a warning is emitted via\r\n * `console.warn`, identifying the file by `filename` for easier debugging.\r\n *\r\n * Note: this step must run AFTER `resolvePartials` and `resolveConditionals`\r\n * so that only plain variable markers remain.\r\n *\r\n * @param text - Template string potentially containing {{varName}} markers\r\n * @param context - Key-value map of variable name → value\r\n * @param filename - Identifier used in warning messages (e.g. persona file path)\r\n * @returns The template string with variable markers substituted\r\n */\r\nexport function resolveVariables(\r\n text: string,\r\n context: Record<string, unknown>,\r\n filename: string,\r\n): string {\r\n return text.replace(/\\{\\{(\\w+)\\}\\}/g, (match, varName: string) => {\r\n if (varName in context && context[varName] !== undefined) {\r\n return String(context[varName]);\r\n }\r\n console.warn(`[WARN] Unresolved variable: ${match} in ${filename}`);\r\n return match;\r\n });\r\n}\r\n","/**\r\n * postProcessor.ts\r\n *\r\n * Pure post-processing functions for cleaning up rendered persona output.\r\n * All functions are side-effect-free and operate only on strings.\r\n * No file-system I/O.\r\n */\r\n\r\n/**\r\n * Collapse 3 or more consecutive blank lines into 2 blank lines.\r\n *\r\n * Specifically converts 4 or more consecutive `\\n` characters into `\\n\\n\\n`\r\n * (which equals 2 blank lines between paragraphs).\r\n *\r\n * @param text - Rendered output string\r\n * @returns String with excessive blank lines collapsed\r\n */\r\nexport function collapseBlankLines(text: string): string {\r\n return text.replace(/\\n{4,}/g, '\\n\\n\\n');\r\n}\r\n\r\n/**\r\n * Ensure every Markdown heading has a blank line immediately before it.\r\n *\r\n * Also ensures horizontal rules (`---`) have a blank line before and after\r\n * them. This corrects spacing gaps caused by partial concatenation where\r\n * `trimEnd()` strips trailing newlines and conditionals add only a single\r\n * `\\n` delimiter.\r\n *\r\n * @param text - Rendered output string\r\n * @returns String with blank lines inserted before headings and rules\r\n */\r\nexport function ensureBlankLineBeforeHeadings(text: string): string {\r\n // Blank line before headings\r\n let result = text.replace(/([^\\n])\\n(#{1,6} )/g, '$1\\n\\n$2');\r\n // Blank line before horizontal rules (---)\r\n result = result.replace(/([^\\n])\\n(---)\\n/g, '$1\\n\\n$2\\n');\r\n // Blank line after horizontal rules (---)\r\n result = result.replace(/\\n(---)\\n([^\\n])/g, '\\n$1\\n\\n$2');\r\n return result;\r\n}\r\n\r\n/**\r\n * Normalize line endings to LF (`\\n`) for OS-agnostic output.\r\n *\r\n * Converts CRLF (`\\r\\n`) first, then strips any remaining stray CR (`\\r`).\r\n *\r\n * @param text - String potentially containing CRLF or CR line endings\r\n * @returns String with all line endings normalized to LF\r\n */\r\nexport function normalizeNewlines(text: string): string {\r\n return text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\r\n}\r\n","/**\r\n * serializer.ts\r\n *\r\n * Pure serializer functions for converting tool lists to YAML-compatible\r\n * string representations. No file-system I/O.\r\n */\r\n\r\n/**\r\n * Serialize a tools array in YAML single-quote flow format WITH outer brackets.\r\n *\r\n * Output format: `['tool1', 'tool2', 'tool3']`\r\n * Used by the ledger suite to preserve byte-identical frontmatter output.\r\n *\r\n * @param tools - Array of tool name strings\r\n * @returns YAML flow-sequence string including outer brackets\r\n *\r\n * @example\r\n * serializeTools(['Bash', 'Read']) // => \"['Bash', 'Read']\"\r\n * serializeTools([]) // => \"[]\"\r\n */\r\nexport function serializeTools(tools: string[]): string {\r\n return '[' + tools.map((t) => `'${t}'`).join(', ') + ']';\r\n}\r\n\r\n/**\r\n * Serialize a tools array in YAML single-quote flow format WITHOUT outer brackets.\r\n *\r\n * Output format: `'tool1', 'tool2', 'tool3'`\r\n * Used inside standalone frontmatter templates which supply the surrounding `[ ]`.\r\n *\r\n * @param tools - Array of tool name strings\r\n * @returns Comma-separated quoted tool names (no outer brackets)\r\n *\r\n * @example\r\n * serializeToolsList(['Bash', 'Read']) // => \"'Bash', 'Read'\"\r\n * serializeToolsList([]) // => \"\"\r\n */\r\nexport function serializeToolsList(tools: string[]): string {\r\n return tools.map((t) => `'${t}'`).join(', ');\r\n}\r\n","/**\r\n * src/loaders/partials-loader.ts\r\n *\r\n * File-system loader for Handlebars-style partial snippets.\r\n *\r\n * Reads every `.md` file in `dir`, keys each entry by the filename stem\r\n * (i.e. the portion before the final `.md` extension), and returns the\r\n * map. Callers that need a two-layer (shared → suite-local override)\r\n * setup should call `loadPartials` twice and merge the results themselves,\r\n * with the suite-local result spreading last.\r\n *\r\n * All file reads are performed asynchronously. Path construction uses\r\n * `path.join` and `path.posix`-compatible operations so no path-separator\r\n * assumptions are baked in.\r\n */\r\n\r\nimport { readdir, readFile } from 'node:fs/promises';\r\nimport path from 'node:path';\r\n\r\n/**\r\n * Load all `.md` files in `dir` and return them as a `Record<string, string>`\r\n * keyed by filename stem.\r\n *\r\n * Files whose names do not end in `.md` are silently ignored.\r\n * The directory must exist; a missing directory throws an `ENOENT` error from\r\n * the underlying `readdir` call (let callers decide how to handle absence).\r\n *\r\n * @param dir Absolute (or relative) path to the directory to scan.\r\n * @returns A map from filename stem → file content string.\r\n *\r\n * @example\r\n * const partials = await loadPartials('/project/partials');\r\n * // { greeting: 'Hello, {{name}}!', footer: '---\\nEnd of file' }\r\n */\r\nexport async function loadPartials(dir: string): Promise<Record<string, string>> {\r\n const entries = await readdir(dir, { withFileTypes: true });\r\n\r\n const mdFiles = entries.filter(\r\n (entry) => entry.isFile() && entry.name.endsWith('.md'),\r\n );\r\n\r\n const pairs = await Promise.all(\r\n mdFiles.map(async (entry) => {\r\n const stem = entry.name.slice(0, -'.md'.length); // strip trailing \".md\"\r\n const filePath = path.join(dir, entry.name);\r\n const content = await readFile(filePath, 'utf8');\r\n return [stem, content] as [string, string];\r\n }),\r\n );\r\n\r\n return Object.fromEntries(pairs);\r\n}\r\n","/**\r\n * src/plugins/runner.ts\r\n *\r\n * Plugin runner — responsible for invoking plugin hooks in registration order.\r\n *\r\n * Each exported function corresponds to one lifecycle hook defined in\r\n * PersonaBuildPlugin. The runner:\r\n * - Skips plugins that do not implement the requested hook (hook is optional)\r\n * - Invokes hooks in the order plugins are registered (first-in first-called)\r\n * - For accumulating hooks (onBuildContext, onPostRender), each plugin\r\n * receives the output of the previous plugin as its first argument\r\n * - For collecting hooks (onValidate), results are concatenated into a\r\n * flat array\r\n *\r\n * No file-system I/O. No async operations.\r\n */\r\n\r\nimport type {\r\n PersonaBuildPlugin,\r\n PersonaMetadata,\r\n SuiteConfig,\r\n TargetType,\r\n ValidationResult,\r\n} from './types.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Suite-level hook\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Invoke the `onSuiteInit` hook on every registered plugin.\r\n *\r\n * Each plugin may optionally implement this hook. Plugins are called in\r\n * registration order. The hook receives the suite config and a mutable\r\n * `sharedMeta` object — plugins may mutate `sharedMeta` in place; the\r\n * same reference is passed to every subsequent plugin.\r\n *\r\n * @param plugins Ordered list of registered plugins\r\n * @param suite The suite configuration object\r\n * @param sharedMeta Mutable shared metadata object (mutated in place by plugins)\r\n */\r\nexport function runSuiteInit(\r\n plugins: PersonaBuildPlugin[],\r\n suite: SuiteConfig,\r\n sharedMeta: Record<string, unknown>,\r\n): void {\r\n for (const plugin of plugins) {\r\n if (typeof plugin.onSuiteInit === 'function') {\r\n plugin.onSuiteInit(suite, sharedMeta);\r\n }\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Per-persona context accumulation\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Invoke the `onBuildContext` hook on every registered plugin, accumulating\r\n * context mutations sequentially.\r\n *\r\n * Each plugin receives the context returned by the previous plugin. If a\r\n * plugin does not implement `onBuildContext`, the context passes through\r\n * unchanged. The final accumulated context is returned.\r\n *\r\n * @param plugins Ordered list of registered plugins\r\n * @param ctx Initial rendering context for this persona\r\n * @param persona Typed metadata for the persona being built\r\n * @param suite The suite configuration object\r\n * @returns Accumulated rendering context after all plugins have run\r\n */\r\nexport function runBuildContext(\r\n plugins: PersonaBuildPlugin[],\r\n ctx: Record<string, unknown>,\r\n persona: PersonaMetadata,\r\n suite: SuiteConfig,\r\n): Record<string, unknown> {\r\n let accumulated = ctx;\r\n for (const plugin of plugins) {\r\n if (typeof plugin.onBuildContext === 'function') {\r\n accumulated = plugin.onBuildContext(accumulated, persona, suite);\r\n }\r\n }\r\n return accumulated;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Per-persona post-render chain\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Invoke the `onPostRender` hook on every registered plugin, chaining the\r\n * output string sequentially.\r\n *\r\n * Each plugin receives the string returned by the previous plugin. If a\r\n * plugin does not implement `onPostRender`, the string passes through\r\n * unchanged. The final string is returned.\r\n *\r\n * @param plugins Ordered list of registered plugins\r\n * @param rendered Initial rendered output string\r\n * @param persona Typed metadata for the persona being built\r\n * @param target The current build target\r\n * @returns Final output string after all plugins have run\r\n */\r\nexport function runPostRender(\r\n plugins: PersonaBuildPlugin[],\r\n rendered: string,\r\n persona: PersonaMetadata,\r\n target: TargetType,\r\n): string {\r\n let output = rendered;\r\n for (const plugin of plugins) {\r\n if (typeof plugin.onPostRender === 'function') {\r\n output = plugin.onPostRender(output, persona, target);\r\n }\r\n }\r\n return output;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Per-persona validation collection\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Invoke the `onValidate` hook on every registered plugin and collect all\r\n * returned ValidationResult objects into a single flat array.\r\n *\r\n * Plugins that do not implement `onValidate` contribute nothing to the result.\r\n * The return value is always an array (never null/undefined).\r\n *\r\n * @param plugins Ordered list of registered plugins\r\n * @param persona Typed metadata for the persona being built\r\n * @param suite The suite configuration object\r\n * @returns Flat array of all ValidationResult objects from all plugins\r\n */\r\nexport function runValidate(\r\n plugins: PersonaBuildPlugin[],\r\n persona: PersonaMetadata,\r\n suite: SuiteConfig,\r\n): ValidationResult[] {\r\n const results: ValidationResult[] = [];\r\n for (const plugin of plugins) {\r\n if (typeof plugin.onValidate === 'function') {\r\n const pluginResults = plugin.onValidate(persona, suite);\r\n results.push(...pluginResults);\r\n }\r\n }\r\n return results;\r\n}\r\n","/**\r\n * src/builders/frontmatter.ts\r\n *\r\n * Frontmatter template registry for @smor/persona-build.\r\n *\r\n * Ships two minimal default templates — one per target — that work for the\r\n * \"standalone\" persona mode (simple personas without numbered workflows or\r\n * MCP server blocks). Projects needing richer frontmatter register custom\r\n * templates via the `PersonaBuildPlugin.frontmatterTemplates` property.\r\n *\r\n * Template rendering follows the same two-step sequence as body rendering:\r\n * 1. resolveConditionals() — resolve {{#if flag}} blocks\r\n * 2. resolveVariables() — substitute {{varName}} markers\r\n *\r\n * No partials in frontmatter — frontmatter is kept deliberately simple.\r\n */\r\n\r\nimport { resolveConditionals } from '../engine/conditionals.js';\r\nimport { resolveVariables } from '../engine/variables.js';\r\nimport type { PersonaBuildPlugin } from '../plugins/types.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Built-in default templates\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Default VS Code frontmatter template.\r\n *\r\n * Minimal fields that work for standalone personas. Projects using numbered\r\n * workflows (e.g. ledger) should inject a richer template via a plugin.\r\n */\r\nexport const DEFAULT_FRONTMATTER_VSCODE = `---\r\nname: '{{name}} v{{version}}'\r\ndescription: '{{description}}'\r\ntools: [{{tools_list}}]\r\n---`;\r\n\r\n/**\r\n * Default Claude Code frontmatter template.\r\n *\r\n * Minimal fields that work for standalone personas. Projects using numbered\r\n * workflows should inject a richer template via a plugin.\r\n */\r\nexport const DEFAULT_FRONTMATTER_CLAUDE_CODE = `---\r\nname: {{cc_file_name_stem}}\r\npermissionMode: {{cc_permission_mode}}\r\nmodel: {{cc_model}}\r\nmemory: {{cc_memory}}\r\nallowedTools: [{{cc_tools_list}}]\r\n---`;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Template resolution\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Resolve frontmatter template precedence.\r\n *\r\n * Precedence order (highest wins):\r\n * 1. Plugin `frontmatterTemplates` — the last plugin with a matching key\r\n * wins (plugins are applied in reverse-registration order so the\r\n * *first* registered plugin with a template takes precedence over later\r\n * ones, matching the general plugin-chain contract).\r\n * 2. `configTemplates` — templates passed via `BuildConfig.frontmatter`\r\n * 3. Library defaults (`DEFAULT_FRONTMATTER_VSCODE` / `DEFAULT_FRONTMATTER_CLAUDE_CODE`)\r\n *\r\n * @param target The build target ('vscode' | 'claude-code')\r\n * @param plugins Registered plugins (searched in order; first match wins)\r\n * @param configTemplates Optional caller-supplied overrides from BuildConfig\r\n * @returns The resolved template string\r\n */\r\nexport function resolveFrontmatterTemplate(\r\n target: 'vscode' | 'claude-code',\r\n plugins: PersonaBuildPlugin[],\r\n configTemplates?: Partial<Record<'vscode' | 'claude-code', string>>,\r\n): string {\r\n // Check plugins in registration order — first plugin with a matching\r\n // frontmatterTemplates entry wins.\r\n for (const plugin of plugins) {\r\n if (plugin.frontmatterTemplates && target in plugin.frontmatterTemplates) {\r\n const tpl = plugin.frontmatterTemplates[target];\r\n if (tpl !== undefined) return tpl;\r\n }\r\n }\r\n\r\n // Caller-supplied config templates\r\n if (configTemplates && target in configTemplates) {\r\n const tpl = configTemplates[target];\r\n if (tpl !== undefined) return tpl;\r\n }\r\n\r\n // Library defaults\r\n return target === 'vscode' ? DEFAULT_FRONTMATTER_VSCODE : DEFAULT_FRONTMATTER_CLAUDE_CODE;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Frontmatter rendering\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Render a frontmatter template string against the given context.\r\n *\r\n * Applies the standard two-step template resolution:\r\n * 1. `resolveConditionals` — `{{#if flag}}` blocks\r\n * 2. `resolveVariables` — `{{varName}}` substitution\r\n *\r\n * @param template The raw frontmatter template string (may contain markers)\r\n * @param context Key-value context for variable substitution\r\n * @param filename Source filename used in warning messages\r\n * @returns Rendered frontmatter string (ready to prepend to body)\r\n */\r\nexport function renderFrontmatter(\r\n template: string,\r\n context: Record<string, unknown>,\r\n filename: string,\r\n): string {\r\n let rendered = resolveConditionals(template, context);\r\n rendered = resolveVariables(rendered, context, filename);\r\n return rendered;\r\n}\r\n","/**\r\n * src/builders/persona-builder.ts\r\n *\r\n * Core build orchestrator for @smor/persona-build.\r\n *\r\n * Exports three public functions:\r\n *\r\n * 1. buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta,\r\n * partialsMap, config, plugins)\r\n * — Builds a single persona for a single target. Returns a BuildResult.\r\n *\r\n * 2. buildSuite(suiteName, suiteConfig, config, plugins)\r\n * — Discovers all persona YAMLs for a suite, fires onSuiteInit, maps\r\n * buildPersona() over each, and returns BuildResult[].\r\n *\r\n * 3. build(config)\r\n * — Top-level entry point. Iterates all suites × targets, calls\r\n * buildSuite() for each combination, and returns a BuildSummary.\r\n * Respects --check (no writes) and --strict (fail on warnings/errors).\r\n */\r\n\r\nimport { readdir, readFile, mkdir, writeFile } from 'node:fs/promises';\r\nimport { existsSync } from 'node:fs';\r\nimport path from 'node:path';\r\nimport yaml from 'js-yaml';\r\n\r\nimport { resolvePartials } from '../engine/partials.js';\r\nimport { resolveConditionals } from '../engine/conditionals.js';\r\nimport { resolveVariables } from '../engine/variables.js';\r\nimport {\r\n collapseBlankLines,\r\n ensureBlankLineBeforeHeadings,\r\n normalizeNewlines,\r\n} from '../engine/postProcessor.js';\r\nimport { serializeTools, serializeToolsList } from '../engine/serializer.js';\r\nimport { loadPartials } from '../loaders/partials-loader.js';\r\nimport {\r\n runSuiteInit,\r\n runBuildContext,\r\n runPostRender,\r\n runValidate,\r\n} from '../plugins/runner.js';\r\n\r\nimport { resolveFrontmatterTemplate, renderFrontmatter } from './frontmatter.js';\r\nimport type { BuildConfig, BuildResult, BuildSummary } from './types.js';\r\nimport type { PersonaBuildPlugin, PersonaMetadata, SuiteConfig, ValidationResult } from '../plugins/types.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Discover all persona YAML files in the `meta/` subdirectory of a suite.\r\n *\r\n * Excludes files whose names start with `_` (shared metadata files such as\r\n * `_shared.yaml`). Results are sorted lexicographically.\r\n *\r\n * @param suiteConfig Suite configuration (used to locate `metaSubdir`)\r\n * @returns Absolute paths to each persona YAML file, sorted.\r\n */\r\nasync function discoverSuitePersonaYamls(suiteConfig: SuiteConfig): Promise<string[]> {\r\n const metaSubdir = suiteConfig.metaSubdir ?? 'meta';\r\n const metaDir = path.join(suiteConfig.srcDir, metaSubdir);\r\n\r\n const entries = await readdir(metaDir, { withFileTypes: true });\r\n\r\n return entries\r\n .filter((e) => e.isFile() && e.name.endsWith('.yaml') && !e.name.startsWith('_'))\r\n .map((e) => path.join(metaDir, e.name))\r\n .sort();\r\n}\r\n\r\n/**\r\n * Load and parse a raw YAML file into a plain object.\r\n * Used for `_shared.yaml` which does not conform to PersonaMetadata's\r\n * `name` requirement.\r\n *\r\n * @param filePath Absolute path to the YAML file\r\n * @returns Parsed object, or {} when the file is empty/absent\r\n */\r\nasync function loadRawYaml(filePath: string): Promise<Record<string, unknown>> {\r\n if (!existsSync(filePath)) return {};\r\n const raw = await readFile(filePath, 'utf8');\r\n const parsed: unknown = yaml.load(raw);\r\n if (parsed === null || parsed === undefined) return {};\r\n if (typeof parsed !== 'object' || Array.isArray(parsed)) return {};\r\n return parsed as Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Load a persona YAML file and return it as a plain metadata record.\r\n * The `name` field is derived from the filename stem when absent.\r\n *\r\n * @param yamlPath Absolute path to the persona YAML file\r\n * @returns Merged metadata record ready for context building\r\n */\r\nasync function loadPersonaYaml(yamlPath: string): Promise<Record<string, unknown>> {\r\n const raw = await readFile(yamlPath, 'utf8');\r\n const parsed: unknown = yaml.load(raw);\r\n\r\n if (parsed === null || parsed === undefined || typeof parsed !== 'object' || Array.isArray(parsed)) {\r\n throw new Error(`buildPersona: expected a YAML object in \"${yamlPath}\"`);\r\n }\r\n\r\n const record = parsed as Record<string, unknown>;\r\n\r\n // Derive name from filename stem if not present in YAML\r\n if (!record['name']) {\r\n record['name'] = path.basename(yamlPath, '.yaml');\r\n }\r\n\r\n return record;\r\n}\r\n\r\n/**\r\n * Build the merged template context for a single persona.\r\n *\r\n * Merge order (later values win):\r\n * 1. sharedMeta (suite-level defaults)\r\n * 2. per-persona YAML fields\r\n * 3. derived/computed fields (version fallback, etc.)\r\n *\r\n * @param personaMeta Per-persona YAML as a plain record\r\n * @param sharedMeta Parsed `_shared.yaml` fields\r\n * @returns Merged rendering context\r\n */\r\nfunction buildContext(\r\n personaMeta: Record<string, unknown>,\r\n sharedMeta: Record<string, unknown>,\r\n): Record<string, unknown> {\r\n const version =\r\n typeof personaMeta['version'] === 'string'\r\n ? personaMeta['version']\r\n : typeof sharedMeta['default_version'] === 'string'\r\n ? sharedMeta['default_version']\r\n : '0.0.0';\r\n\r\n // Merge base: shared first, persona overrides\r\n const merged: Record<string, unknown> = {\r\n ...sharedMeta,\r\n ...personaMeta,\r\n version,\r\n };\r\n\r\n // ── Derived convenience fields (only set when not already provided) ───────\r\n // tools_list / tools_json — serialized from the `tools` array if present\r\n const tools = Array.isArray(merged['tools']) ? (merged['tools'] as string[]) : [];\r\n if (!('tools_list' in merged)) {\r\n merged['tools_list'] = serializeToolsList(tools);\r\n }\r\n if (!('tools_json' in merged)) {\r\n merged['tools_json'] = serializeTools(tools);\r\n }\r\n\r\n // cc_tools_list / cc_tools_json — from `cc_tools` or fall back to `tools`\r\n const ccTools = Array.isArray(merged['cc_tools']) ? (merged['cc_tools'] as string[]) : tools;\r\n if (!('cc_tools_list' in merged)) {\r\n merged['cc_tools_list'] = serializeToolsList(ccTools);\r\n }\r\n if (!('cc_tools_json' in merged)) {\r\n merged['cc_tools_json'] = serializeTools(ccTools);\r\n }\r\n\r\n // cc_file_name_stem — stem of cc_file_name (for default CC frontmatter template)\r\n if (!('cc_file_name_stem' in merged) && typeof merged['cc_file_name'] === 'string') {\r\n const ccFileName = merged['cc_file_name'] as string;\r\n merged['cc_file_name_stem'] = ccFileName.replace(/\\.md$/, '');\r\n }\r\n\r\n return merged;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// buildPersona — single persona × single target\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Build a single persona for a single output target.\r\n *\r\n * Pipeline:\r\n * 1. Load sharedMeta + personaMeta (callers supply pre-loaded values)\r\n * 2. Build merged context\r\n * 3. Run onBuildContext plugin hooks (context accumulation)\r\n * 4. Resolve frontmatter template → render frontmatter\r\n * 5. Load content template\r\n * 6. Render body: partials → conditionals → variables → post-process\r\n * 7. Assemble final output (frontmatter + body)\r\n * 8. Run onPostRender plugin hooks (output chain)\r\n * 9. Run onValidate plugin hooks (validation collection)\r\n * 10. Determine output file path\r\n * 11. Write output file (unless check mode)\r\n * 12. Return BuildResult\r\n *\r\n * @param personaYamlPath Absolute path to the persona YAML source file\r\n * @param suiteName Identifier for the suite this persona belongs to\r\n * @param suiteConfig Suite configuration object\r\n * @param sharedMeta Pre-loaded `_shared.yaml` contents\r\n * @param partialsMap Pre-loaded partials map (shared + suite-local merged)\r\n * @param config Top-level BuildConfig\r\n * @param plugins Registered plugins\r\n * @param target Target output format\r\n * @returns BuildResult for this persona × target combination\r\n */\r\nexport async function buildPersona(\r\n personaYamlPath: string,\r\n suiteName: string,\r\n suiteConfig: SuiteConfig,\r\n sharedMeta: Record<string, unknown>,\r\n partialsMap: Record<string, string>,\r\n config: BuildConfig,\r\n plugins: PersonaBuildPlugin[],\r\n target: 'vscode' | 'claude-code',\r\n): Promise<BuildResult> {\r\n // ── 1. Load persona metadata ──────────────────────────────────────────────\r\n const personaMeta = await loadPersonaYaml(personaYamlPath);\r\n\r\n // ── 2. Build merged context ───────────────────────────────────────────────\r\n let context = buildContext(personaMeta, sharedMeta);\r\n\r\n // ── 3. Plugin onBuildContext ──────────────────────────────────────────────\r\n // Cast context to PersonaMetadata for the plugin runner (it requires a\r\n // name field which is guaranteed by loadPersonaYaml above).\r\n const personaMetaTyped = personaMeta as PersonaMetadata;\r\n context = runBuildContext(plugins, context, personaMetaTyped, suiteConfig);\r\n\r\n // ── 4. Render frontmatter ─────────────────────────────────────────────────\r\n const fmTemplate = resolveFrontmatterTemplate(target, plugins, config.frontmatter);\r\n const contentBasename = path.basename(personaYamlPath, '.yaml') + '.md';\r\n const frontmatter = renderFrontmatter(fmTemplate, context, contentBasename);\r\n\r\n // ── 5. Load content template ──────────────────────────────────────────────\r\n const contentSubdir = suiteConfig.contentSubdir ?? 'content';\r\n const contentPath = path.join(suiteConfig.srcDir, contentSubdir, contentBasename);\r\n const bodyTemplate = normalizeNewlines(await readFile(contentPath, 'utf8'));\r\n\r\n // ── 6. Render body ────────────────────────────────────────────────────────\r\n let body = resolvePartials(bodyTemplate, partialsMap);\r\n body = resolveConditionals(body, context);\r\n body = resolveVariables(body, context, contentBasename);\r\n body = collapseBlankLines(body);\r\n body = ensureBlankLineBeforeHeadings(body);\r\n body = body.trimEnd();\r\n\r\n // ── 7. Assemble output ────────────────────────────────────────────────────\r\n let output = normalizeNewlines(`${frontmatter}\\n\\n${body}\\n`);\r\n\r\n // ── 8. Plugin onPostRender ────────────────────────────────────────────────\r\n output = runPostRender(plugins, output, personaMetaTyped, target);\r\n\r\n // ── 9. Plugin onValidate ──────────────────────────────────────────────────\r\n const validationResults: ValidationResult[] = runValidate(plugins, personaMetaTyped, suiteConfig);\r\n\r\n // ── 10. Determine output file path ────────────────────────────────────────\r\n const outputDir = target === 'vscode' ? suiteConfig.outVscode : suiteConfig.outClaudeCode;\r\n // Use declared output filename fields when present (vs_file_name / cc_file_name),\r\n // falling back to the content basename.\r\n let outputBasename: string;\r\n if (target === 'vscode' && typeof context['vs_file_name'] === 'string') {\r\n outputBasename = context['vs_file_name'];\r\n } else if (target === 'claude-code' && typeof context['cc_file_name'] === 'string') {\r\n outputBasename = context['cc_file_name'];\r\n } else {\r\n outputBasename = contentBasename;\r\n }\r\n const outputPath = path.join(outputDir, outputBasename);\r\n\r\n // ── 11. Write (unless check mode) ─────────────────────────────────────────\r\n const check = config.check ?? false;\r\n let written = false;\r\n\r\n if (!check) {\r\n await mkdir(outputDir, { recursive: true });\r\n await writeFile(outputPath, output, 'utf8');\r\n written = true;\r\n }\r\n\r\n return {\r\n suite: suiteName,\r\n target,\r\n personaYamlPath,\r\n outputPath,\r\n content: output,\r\n validationResults,\r\n written,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// buildSuite — all personas in one suite × one target\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Build all personas in a suite for a single output target.\r\n *\r\n * Pipeline:\r\n * 1. Load `_shared.yaml` for the suite\r\n * 2. Load merged partials (shared → suite-local)\r\n * 3. Run `onSuiteInit` on all plugins\r\n * 4. Discover all persona YAML files\r\n * 5. Call `buildPersona()` for each\r\n *\r\n * @param suiteName Identifier for this suite\r\n * @param suiteConfig Suite configuration\r\n * @param config Top-level BuildConfig\r\n * @param plugins Registered plugins\r\n * @param target Target output format\r\n * @returns Array of BuildResult objects, one per persona\r\n */\r\nexport async function buildSuite(\r\n suiteName: string,\r\n suiteConfig: SuiteConfig,\r\n config: BuildConfig,\r\n plugins: PersonaBuildPlugin[],\r\n target: 'vscode' | 'claude-code',\r\n): Promise<BuildResult[]> {\r\n // ── 1. Load shared metadata ───────────────────────────────────────────────\r\n const metaSubdir = suiteConfig.metaSubdir ?? 'meta';\r\n const sharedYamlPath = path.join(suiteConfig.srcDir, metaSubdir, '_shared.yaml');\r\n const sharedMeta = await loadRawYaml(sharedYamlPath);\r\n\r\n // ── 2. Load partials (two-layer: shared base → suite-local override) ──────\r\n let partialsMap: Record<string, string> = {};\r\n\r\n if (config.sharedPartialsDir && existsSync(config.sharedPartialsDir)) {\r\n partialsMap = { ...partialsMap, ...(await loadPartials(config.sharedPartialsDir)) };\r\n }\r\n\r\n const partialsSubdir = suiteConfig.partialsSubdir ?? 'partials';\r\n const suitePartialsDir = path.join(suiteConfig.srcDir, partialsSubdir);\r\n if (existsSync(suitePartialsDir)) {\r\n partialsMap = { ...partialsMap, ...(await loadPartials(suitePartialsDir)) };\r\n }\r\n\r\n // ── 3. Plugin onSuiteInit ─────────────────────────────────────────────────\r\n runSuiteInit(plugins, suiteConfig, sharedMeta);\r\n\r\n // ── 4. Discover persona YAML files ────────────────────────────────────────\r\n const personaYamlPaths = await discoverSuitePersonaYamls(suiteConfig);\r\n\r\n // ── 5. Build each persona ─────────────────────────────────────────────────\r\n const results: BuildResult[] = [];\r\n for (const yamlPath of personaYamlPaths) {\r\n const result = await buildPersona(\r\n yamlPath,\r\n suiteName,\r\n suiteConfig,\r\n sharedMeta,\r\n partialsMap,\r\n config,\r\n plugins,\r\n target,\r\n );\r\n results.push(result);\r\n }\r\n\r\n return results;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// build — top-level entry point\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Top-level build orchestrator.\r\n *\r\n * Iterates all `config.suites × config.targets` combinations, calls\r\n * `buildSuite()` for each, and aggregates the results into a `BuildSummary`.\r\n *\r\n * Modes:\r\n * - Normal: renders and writes all personas.\r\n * - `check: true`: renders without writing; useful for CI staleness checks.\r\n * - `strict: true`: throws when any ValidationResult has severity `'error'`\r\n * or `'warning'`. All suites are processed before the throw, so output\r\n * files **will** be written to disk even when the build ultimately fails.\r\n * **For CI usage, combine `strict: true` with `check: true`** to avoid\r\n * leaving partial artefacts on disk when validation fails.\r\n *\r\n * @param config Typed build configuration\r\n * @returns Aggregated BuildSummary\r\n * @throws `Error` when `strict: true` and validation failures exist\r\n */\r\nexport async function build(config: BuildConfig): Promise<BuildSummary> {\r\n const plugins = config.plugins ?? [];\r\n const targets = config.targets ?? ['vscode', 'claude-code'];\r\n const allResults: BuildResult[] = [];\r\n\r\n for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {\r\n for (const target of targets) {\r\n const suiteResults = await buildSuite(suiteName, suiteConfig, config, plugins, target);\r\n allResults.push(...suiteResults);\r\n }\r\n }\r\n\r\n // Collect strict failures (error + warning severity)\r\n const strictFailures: ValidationResult[] = config.strict\r\n ? allResults.flatMap((r) =>\r\n r.validationResults.filter(\r\n (v) => v.severity === 'error' || v.severity === 'warning',\r\n ),\r\n )\r\n : [];\r\n\r\n const success = !config.strict || strictFailures.length === 0;\r\n\r\n const summary: BuildSummary = {\r\n success,\r\n results: allResults,\r\n strictFailures,\r\n totalBuilt: allResults.length,\r\n totalWritten: allResults.filter((r) => r.written).length,\r\n };\r\n\r\n if (config.strict && !success) {\r\n const messages = strictFailures.map((f) => `[${f.severity}] ${f.message}`).join('\\n');\r\n throw new Error(\r\n `Build failed in strict mode — ${strictFailures.length} validation issue(s):\\n${messages}`,\r\n );\r\n }\r\n\r\n return summary;\r\n}\r\n","#!/usr/bin/env node\r\n/**\r\n * src/cli.ts — @smor/persona-build CLI entry point\r\n *\r\n * Flags:\r\n * --config <path> Path to config file (JS/CJS/JSON). Default: persona-build.config.js\r\n * --check Run the build pipeline but do not write output files.\r\n * Always exits 0 unless combined with --strict, which causes\r\n * exit 1 when any ValidationResult has severity 'error' or\r\n * 'warning'.\r\n * --strict Fail (exit 1) if any ValidationResult has severity\r\n * 'error' or 'warning'.\r\n * --help Print usage and exit 0.\r\n * --version Print package version and exit 0.\r\n *\r\n * No heavy CLI framework — args are parsed with a hand-rolled loop.\r\n */\r\n\r\nimport { createRequire } from 'node:module';\r\nimport path from 'node:path';\r\nimport { existsSync } from 'node:fs';\r\nimport { pathToFileURL } from 'node:url';\r\n\r\nimport { build } from './builders/persona-builder.js';\r\nimport type { BuildConfig, BuildSummary } from './builders/types.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Version — sourced from package.json (single source of truth).\r\n// createRequire is already imported above for config loading; reuse it here.\r\n// ---------------------------------------------------------------------------\r\n\r\nconst _pkgRequire = createRequire(import.meta.url);\r\nconst VERSION = (_pkgRequire('../package.json') as { version: string }).version;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Usage / help text\r\n// ---------------------------------------------------------------------------\r\n\r\nconst USAGE = `\r\n@smor/persona-build v${VERSION}\r\n\r\nBuild AI persona documents from YAML metadata and Markdown content templates.\r\n\r\nUSAGE\r\n persona-build [options]\r\n\r\nOPTIONS\r\n --config <path> Path to the build config file.\r\n Supports .js (ESM), .cjs, and .json formats.\r\n Default: persona-build.config.js in the current directory.\r\n --check Render personas but skip writing output files.\r\n Always exits 0 on its own. Combine with --strict to\r\n exit 1 when validators report errors or warnings.\r\n --strict Exit 1 if any validation result has severity 'error'\r\n or 'warning'.\r\n --help Show this help message and exit.\r\n --version Print the package version and exit.\r\n\r\nEXAMPLES\r\n persona-build # Build with default config\r\n persona-build --config ./my-config.js # Build with a custom config\r\n persona-build --check # CI staleness check (no file writes)\r\n persona-build --strict # Fail on warnings or errors\r\n persona-build --check --strict # Safe CI check — no writes + strict\r\n`.trim();\r\n\r\n// ---------------------------------------------------------------------------\r\n// Arg parsing\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface ParsedArgs {\r\n configPath?: string;\r\n check: boolean;\r\n strict: boolean;\r\n help: boolean;\r\n version: boolean;\r\n}\r\n\r\nfunction parseArgs(argv: string[]): ParsedArgs {\r\n const args = argv.slice(2); // strip 'node' + script path\r\n\r\n const result: ParsedArgs = {\r\n configPath: undefined,\r\n check: false,\r\n strict: false,\r\n help: false,\r\n version: false,\r\n };\r\n\r\n let i = 0;\r\n while (i < args.length) {\r\n const arg = args[i];\r\n switch (arg) {\r\n case '--help':\r\n case '-h':\r\n result.help = true;\r\n break;\r\n case '--version':\r\n case '-v':\r\n result.version = true;\r\n break;\r\n case '--check':\r\n result.check = true;\r\n break;\r\n case '--strict':\r\n result.strict = true;\r\n break;\r\n case '--config': {\r\n const next = args[i + 1];\r\n if (!next || next.startsWith('--')) {\r\n console.error('Error: --config requires a path argument.');\r\n process.exit(1);\r\n }\r\n result.configPath = next;\r\n i++; // consume the value\r\n break;\r\n }\r\n default:\r\n // Unknown flag — warn but do not exit so older configs stay forward-compatible\r\n if (arg.startsWith('--')) {\r\n console.warn(`Warning: Unknown flag \"${arg}\" — ignored.`);\r\n }\r\n }\r\n i++;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Config loading\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Resolve the config file path from the user-supplied value or the default\r\n * discovery chain.\r\n *\r\n * Discovery order (when --config is not supplied):\r\n * 1. persona-build.config.js (ESM)\r\n * 2. persona-build.config.cjs (CJS)\r\n * 3. persona-build.config.json (JSON)\r\n */\r\nfunction resolveConfigPath(cliValue?: string): string {\r\n if (cliValue) {\r\n const resolved = path.resolve(cliValue);\r\n if (!existsSync(resolved)) {\r\n console.error(`Error: Config file not found: ${resolved}`);\r\n process.exit(1);\r\n }\r\n return resolved;\r\n }\r\n\r\n const candidates = [\r\n 'persona-build.config.js',\r\n 'persona-build.config.cjs',\r\n 'persona-build.config.json',\r\n ];\r\n\r\n for (const name of candidates) {\r\n const candidate = path.resolve(name);\r\n if (existsSync(candidate)) return candidate;\r\n }\r\n\r\n console.error(\r\n 'Error: No config file found. ' +\r\n 'Create persona-build.config.js in the current directory or pass --config <path>.',\r\n );\r\n process.exit(1);\r\n}\r\n\r\n/**\r\n * Load and validate the config file.\r\n *\r\n * Supports:\r\n * - ESM .js → dynamic import()\r\n * - CJS .cjs → createRequire()\r\n * - JSON .json → createRequire()\r\n *\r\n * The config module must export a default export (or be a plain JSON object)\r\n * that conforms to BuildConfig.\r\n */\r\nasync function loadConfig(configPath: string): Promise<BuildConfig> {\r\n const ext = path.extname(configPath).toLowerCase();\r\n\r\n let rawConfig: unknown;\r\n\r\n if (ext === '.cjs' || ext === '.json') {\r\n const require = createRequire(import.meta.url);\r\n rawConfig = require(configPath);\r\n } else {\r\n // ESM default — use dynamic import with a file URL\r\n const fileUrl = pathToFileURL(configPath).href;\r\n const mod = await import(fileUrl);\r\n rawConfig = mod.default ?? mod;\r\n }\r\n\r\n if (!rawConfig || typeof rawConfig !== 'object' || Array.isArray(rawConfig)) {\r\n console.error(\r\n `Error: Config file \"${configPath}\" must export a plain object (BuildConfig).`,\r\n );\r\n process.exit(1);\r\n }\r\n\r\n const config = rawConfig as BuildConfig;\r\n\r\n if (!config.suites || typeof config.suites !== 'object') {\r\n console.error(\r\n `Error: Config file \"${configPath}\" must have a \"suites\" property (record of suite configs).`,\r\n );\r\n process.exit(1);\r\n }\r\n\r\n return config;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Output formatting\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction printSummary(summary: BuildSummary, check: boolean): void {\r\n const mode = check ? ' [check mode — no files written]' : '';\r\n const status = summary.success ? '✓ Build succeeded' : '✗ Build failed';\r\n console.log(`${status}${mode}`);\r\n console.log(` Personas processed : ${summary.totalBuilt}`);\r\n if (!check) {\r\n console.log(` Files written : ${summary.totalWritten}`);\r\n }\r\n if (summary.strictFailures.length > 0) {\r\n console.log(`\\n Validation failures (${summary.strictFailures.length}):`);\r\n for (const f of summary.strictFailures) {\r\n console.log(` [${f.severity}] ${f.message}`);\r\n }\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Main entry point\r\n// ---------------------------------------------------------------------------\r\n\r\nasync function main(): Promise<void> {\r\n const args = parseArgs(process.argv);\r\n\r\n // Short-circuit flags\r\n if (args.help) {\r\n console.log(USAGE);\r\n process.exit(0);\r\n }\r\n\r\n if (args.version) {\r\n console.log(VERSION);\r\n process.exit(0);\r\n }\r\n\r\n // Resolve and load config\r\n const configPath = resolveConfigPath(args.configPath);\r\n let config: BuildConfig;\r\n\r\n try {\r\n config = await loadConfig(configPath);\r\n } catch (err) {\r\n console.error(`Error loading config: ${err instanceof Error ? err.message : String(err)}`);\r\n process.exit(1);\r\n }\r\n\r\n // Apply CLI flag overrides (CLI flags take precedence over config-file values)\r\n if (args.check) config.check = true;\r\n if (args.strict) config.strict = true;\r\n\r\n // Run the build\r\n let summary: BuildSummary;\r\n try {\r\n summary = await build(config);\r\n } catch (err) {\r\n // build() throws in strict mode when there are validation failures\r\n if (err instanceof Error) {\r\n console.error(`\\n${err.message}`);\r\n } else {\r\n console.error('Build failed with an unexpected error:', err);\r\n }\r\n process.exit(1);\r\n }\r\n\r\n // Print results\r\n printSummary(summary, config.check ?? false);\r\n\r\n // Exit code\r\n if (!summary.success) {\r\n process.exit(1);\r\n }\r\n\r\n process.exit(0);\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error('Unexpected error:', err);\r\n process.exit(1);\r\n});\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/engine/partials.ts","../src/engine/conditionals.ts","../src/engine/variables.ts","../src/engine/postProcessor.ts","../src/engine/serializer.ts","../src/loaders/partials-loader.ts","../src/plugins/runner.ts","../src/builders/frontmatter.ts","../src/builders/persona-builder.ts","../src/cli.ts"],"names":["readdir","path","readFile","existsSync","yaml","mkdir","writeFile","createRequire","require","pathToFileURL"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\r\n * partials.ts\r\n *\r\n * Pure template-engine function for resolving partial inclusions.\r\n * Supports {{> name}} syntax with up to depth-2 recursion to handle\r\n * partials-within-partials. No file-system I/O.\r\n */\r\n\r\n/**\r\n * Resolve partial inclusions in a template string.\r\n *\r\n * Replaces `{{> name}}` markers with the content from `partialsMap`.\r\n * Recursion is capped at depth 2 so that:\r\n * - depth 0 → 1: outer partials are expanded\r\n * - depth 1 → 2: one level of nested partials are expanded\r\n * - depth 2: recursion stops, marker is left as-is\r\n *\r\n * Each resolved partial is `trimEnd()`-ed to prevent trailing blank lines\r\n * from causing double-blank-line artefacts during concatenation.\r\n *\r\n * If a partial name is not found in `partialsMap`, the original marker is\r\n * preserved and a warning is emitted via `console.warn`.\r\n *\r\n * @param text - Template string potentially containing {{> name}} markers\r\n * @param partialsMap - Map of partial name → partial content\r\n * @param depth - Current recursion depth (callers should omit; defaults to 0)\r\n * @returns The template string with partial markers replaced\r\n */\r\nexport function resolvePartials(\r\n text: string,\r\n partialsMap: Record<string, string>,\r\n depth = 0,\r\n): string {\r\n if (depth >= 2) return text;\r\n return text.replace(/\\{\\{> ([\\w-]+)\\}\\}/g, (match, name: string) => {\r\n if (!(name in partialsMap)) {\r\n console.warn(`[WARN] Partial not found: ${match}`);\r\n return match;\r\n }\r\n // Recursively resolve nested partials (depth + 1).\r\n // trimEnd() strips trailing whitespace to avoid extra blank lines.\r\n return resolvePartials(partialsMap[name], partialsMap, depth + 1).trimEnd();\r\n });\r\n}\r\n","/**\r\n * conditionals.ts\r\n *\r\n * Pure template-engine function for resolving conditional blocks.\r\n * Handles {{#if flag}}…{{/if}} and {{#if flag}}…{{else}}…{{/if}} syntax.\r\n * No file-system I/O.\r\n */\r\n\r\n/**\r\n * Resolve conditional blocks in a template string.\r\n *\r\n * Syntax:\r\n * `{{#if flag}}content{{/if}}`\r\n * `{{#if flag}}truthy-content{{else}}falsy-content{{/if}}`\r\n *\r\n * Behaviour:\r\n * - When `context[flag]` is truthy: the delimiters are stripped and the\r\n * content before `{{else}}` (or the entire inner block if no `{{else}}`)\r\n * is kept, surrounded by single `\\n` delimiters.\r\n * - When `context[flag]` is falsy and a `{{else}}` branch exists: the\r\n * content after `{{else}}` is kept, surrounded by single `\\n` delimiters.\r\n * - When `context[flag]` is falsy and no `{{else}}` branch exists: the\r\n * entire block (including surrounding newlines) is removed, leaving a\r\n * single `\\n`.\r\n * - Unknown flags (absent from context) are treated as falsy.\r\n *\r\n * Leading and trailing newlines within the kept content are trimmed so the\r\n * output does not accumulate extra blank lines.\r\n *\r\n * @param text - Template string potentially containing {{#if}} blocks\r\n * @param context - Key-value map used to evaluate flag truthiness\r\n * @returns The template string with conditional blocks resolved\r\n */\r\nexport function resolveConditionals(\r\n text: string,\r\n context: Record<string, unknown>,\r\n): string {\r\n return text.replace(\r\n /\\n*\\{\\{#if (\\w+)\\}\\}([\\s\\S]*?)(?:\\{\\{else\\}\\}([\\s\\S]*?))?\\{\\{\\/if\\}\\}\\n*/g,\r\n (\r\n _match: string,\r\n flag: string,\r\n inner: string,\r\n elseInner: string | undefined,\r\n ) => {\r\n if (context[flag]) {\r\n // Truthy: keep content before {{else}} (or entire inner if no {{else}})\r\n return '\\n' + inner.replace(/^\\n+/, '').replace(/\\n+$/, '') + '\\n';\r\n }\r\n if (elseInner !== undefined) {\r\n // Falsy with {{else}}: keep content after {{else}}\r\n return '\\n' + elseInner.replace(/^\\n+/, '').replace(/\\n+$/, '') + '\\n';\r\n }\r\n // Falsy without {{else}}: remove entire block\r\n return '\\n';\r\n },\r\n );\r\n}\r\n","/**\r\n * variables.ts\r\n *\r\n * Pure template-engine function for resolving variable substitutions.\r\n * Handles {{varName}} syntax. No file-system I/O.\r\n */\r\n\r\n/**\r\n * Resolve variable substitutions in a template string.\r\n *\r\n * Replaces `{{varName}}` markers with `String(context[varName])`.\r\n * If a variable is not found in `context` (or its value is `undefined`),\r\n * the original marker is preserved and a warning is emitted via\r\n * `console.warn`, identifying the file by `filename` for easier debugging.\r\n *\r\n * Note: this step must run AFTER `resolvePartials` and `resolveConditionals`\r\n * so that only plain variable markers remain.\r\n *\r\n * @param text - Template string potentially containing {{varName}} markers\r\n * @param context - Key-value map of variable name → value\r\n * @param filename - Identifier used in warning messages (e.g. persona file path)\r\n * @returns The template string with variable markers substituted\r\n */\r\nexport function resolveVariables(\r\n text: string,\r\n context: Record<string, unknown>,\r\n filename: string,\r\n): string {\r\n return text.replace(/\\{\\{(\\w+)\\}\\}/g, (match, varName: string) => {\r\n if (varName in context && context[varName] !== undefined) {\r\n return String(context[varName]);\r\n }\r\n console.warn(`[WARN] Unresolved variable: ${match} in ${filename}`);\r\n return match;\r\n });\r\n}\r\n","/**\r\n * postProcessor.ts\r\n *\r\n * Pure post-processing functions for cleaning up rendered persona output.\r\n * All functions are side-effect-free and operate only on strings.\r\n * No file-system I/O.\r\n */\r\n\r\n/**\r\n * Collapse 3 or more consecutive blank lines into 2 blank lines.\r\n *\r\n * Specifically converts 4 or more consecutive `\\n` characters into `\\n\\n\\n`\r\n * (which equals 2 blank lines between paragraphs).\r\n *\r\n * @param text - Rendered output string\r\n * @returns String with excessive blank lines collapsed\r\n */\r\nexport function collapseBlankLines(text: string): string {\r\n return text.replace(/\\n{4,}/g, '\\n\\n\\n');\r\n}\r\n\r\n/**\r\n * Ensure every Markdown heading has a blank line immediately before it.\r\n *\r\n * Also ensures horizontal rules (`---`) have a blank line before and after\r\n * them. This corrects spacing gaps caused by partial concatenation where\r\n * `trimEnd()` strips trailing newlines and conditionals add only a single\r\n * `\\n` delimiter.\r\n *\r\n * @param text - Rendered output string\r\n * @returns String with blank lines inserted before headings and rules\r\n */\r\nexport function ensureBlankLineBeforeHeadings(text: string): string {\r\n // Blank line before headings\r\n let result = text.replace(/([^\\n])\\n(#{1,6} )/g, '$1\\n\\n$2');\r\n // Blank line before horizontal rules (---)\r\n result = result.replace(/([^\\n])\\n(---)\\n/g, '$1\\n\\n$2\\n');\r\n // Blank line after horizontal rules (---)\r\n result = result.replace(/\\n(---)\\n([^\\n])/g, '\\n$1\\n\\n$2');\r\n return result;\r\n}\r\n\r\n/**\r\n * Normalize line endings to LF (`\\n`) for OS-agnostic output.\r\n *\r\n * Converts CRLF (`\\r\\n`) first, then strips any remaining stray CR (`\\r`).\r\n *\r\n * @param text - String potentially containing CRLF or CR line endings\r\n * @returns String with all line endings normalized to LF\r\n */\r\nexport function normalizeNewlines(text: string): string {\r\n return text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\r\n}\r\n","/**\r\n * serializer.ts\r\n *\r\n * Pure serializer functions for converting tool lists to YAML-compatible\r\n * string representations. No file-system I/O.\r\n */\r\n\r\n/**\r\n * Serialize a tools array in YAML single-quote flow format WITH outer brackets.\r\n *\r\n * Output format: `['tool1', 'tool2', 'tool3']`\r\n * Used by the ledger suite to preserve byte-identical frontmatter output.\r\n *\r\n * @param tools - Array of tool name strings\r\n * @returns YAML flow-sequence string including outer brackets\r\n *\r\n * @example\r\n * serializeTools(['Bash', 'Read']) // => \"['Bash', 'Read']\"\r\n * serializeTools([]) // => \"[]\"\r\n */\r\nexport function serializeTools(tools: string[]): string {\r\n return '[' + tools.map((t) => `'${t}'`).join(', ') + ']';\r\n}\r\n\r\n/**\r\n * Serialize a tools array in YAML single-quote flow format WITHOUT outer brackets.\r\n *\r\n * Output format: `'tool1', 'tool2', 'tool3'`\r\n * Used inside standalone frontmatter templates which supply the surrounding `[ ]`.\r\n *\r\n * @param tools - Array of tool name strings\r\n * @returns Comma-separated quoted tool names (no outer brackets)\r\n *\r\n * @example\r\n * serializeToolsList(['Bash', 'Read']) // => \"'Bash', 'Read'\"\r\n * serializeToolsList([]) // => \"\"\r\n */\r\nexport function serializeToolsList(tools: string[]): string {\r\n return tools.map((t) => `'${t}'`).join(', ');\r\n}\r\n","/**\r\n * src/loaders/partials-loader.ts\r\n *\r\n * File-system loader for Handlebars-style partial snippets.\r\n *\r\n * Reads every `.md` file in `dir`, keys each entry by the filename stem\r\n * (i.e. the portion before the final `.md` extension), and returns the\r\n * map. Callers that need a two-layer (shared → suite-local override)\r\n * setup should call `loadPartials` twice and merge the results themselves,\r\n * with the suite-local result spreading last.\r\n *\r\n * All file reads are performed asynchronously. Path construction uses\r\n * `path.join` and `path.posix`-compatible operations so no path-separator\r\n * assumptions are baked in.\r\n */\r\n\r\nimport { readdir, readFile } from 'node:fs/promises';\r\nimport path from 'node:path';\r\n\r\n/**\r\n * Load all `.md` files in `dir` and return them as a `Record<string, string>`\r\n * keyed by filename stem.\r\n *\r\n * Files whose names do not end in `.md` are silently ignored.\r\n * The directory must exist; a missing directory throws an `ENOENT` error from\r\n * the underlying `readdir` call (let callers decide how to handle absence).\r\n *\r\n * @param dir Absolute (or relative) path to the directory to scan.\r\n * @returns A map from filename stem → file content string.\r\n *\r\n * @example\r\n * const partials = await loadPartials('/project/partials');\r\n * // { greeting: 'Hello, {{name}}!', footer: '---\\nEnd of file' }\r\n */\r\nexport async function loadPartials(dir: string): Promise<Record<string, string>> {\r\n const entries = await readdir(dir, { withFileTypes: true });\r\n\r\n const mdFiles = entries.filter(\r\n (entry) => entry.isFile() && entry.name.endsWith('.md'),\r\n );\r\n\r\n const pairs = await Promise.all(\r\n mdFiles.map(async (entry) => {\r\n const stem = entry.name.slice(0, -'.md'.length); // strip trailing \".md\"\r\n const filePath = path.join(dir, entry.name);\r\n const content = await readFile(filePath, 'utf8');\r\n return [stem, content] as [string, string];\r\n }),\r\n );\r\n\r\n return Object.fromEntries(pairs);\r\n}\r\n","/**\r\n * src/plugins/runner.ts\r\n *\r\n * Plugin runner — responsible for invoking plugin hooks in registration order.\r\n *\r\n * Each exported function corresponds to one lifecycle hook defined in\r\n * PersonaBuildPlugin. The runner:\r\n * - Skips plugins that do not implement the requested hook (hook is optional)\r\n * - Invokes hooks in the order plugins are registered (first-in first-called)\r\n * - For accumulating hooks (onBuildContext, onPostRender), each plugin\r\n * receives the output of the previous plugin as its first argument\r\n * - For collecting hooks (onValidate), results are concatenated into a\r\n * flat array\r\n *\r\n * No file-system I/O. No async operations.\r\n */\r\n\r\nimport type {\r\n PersonaBuildPlugin,\r\n PersonaMetadata,\r\n SuiteConfig,\r\n TargetType,\r\n ValidationResult,\r\n} from './types.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Suite-level hook\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Invoke the `onSuiteInit` hook on every registered plugin.\r\n *\r\n * Each plugin may optionally implement this hook. Plugins are called in\r\n * registration order. The hook receives the suite config and a mutable\r\n * `sharedMeta` object — plugins may mutate `sharedMeta` in place; the\r\n * same reference is passed to every subsequent plugin.\r\n *\r\n * @param plugins Ordered list of registered plugins\r\n * @param suite The suite configuration object\r\n * @param sharedMeta Mutable shared metadata object (mutated in place by plugins)\r\n */\r\nexport function runSuiteInit(\r\n plugins: PersonaBuildPlugin[],\r\n suite: SuiteConfig,\r\n sharedMeta: Record<string, unknown>,\r\n): void {\r\n for (const plugin of plugins) {\r\n if (typeof plugin.onSuiteInit === 'function') {\r\n plugin.onSuiteInit(suite, sharedMeta);\r\n }\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Per-persona context accumulation\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Invoke the `onBuildContext` hook on every registered plugin, accumulating\r\n * context mutations sequentially.\r\n *\r\n * Each plugin receives the context returned by the previous plugin. If a\r\n * plugin does not implement `onBuildContext`, the context passes through\r\n * unchanged. The final accumulated context is returned.\r\n *\r\n * @param plugins Ordered list of registered plugins\r\n * @param ctx Initial rendering context for this persona\r\n * @param persona Typed metadata for the persona being built\r\n * @param suite The suite configuration object\r\n * @returns Accumulated rendering context after all plugins have run\r\n */\r\nexport function runBuildContext(\r\n plugins: PersonaBuildPlugin[],\r\n ctx: Record<string, unknown>,\r\n persona: PersonaMetadata,\r\n suite: SuiteConfig,\r\n): Record<string, unknown> {\r\n let accumulated = ctx;\r\n for (const plugin of plugins) {\r\n if (typeof plugin.onBuildContext === 'function') {\r\n accumulated = plugin.onBuildContext(accumulated, persona, suite);\r\n }\r\n }\r\n return accumulated;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Per-persona post-render chain\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Invoke the `onPostRender` hook on every registered plugin, chaining the\r\n * output string sequentially.\r\n *\r\n * Each plugin receives the string returned by the previous plugin. If a\r\n * plugin does not implement `onPostRender`, the string passes through\r\n * unchanged. The final string is returned.\r\n *\r\n * @param plugins Ordered list of registered plugins\r\n * @param rendered Initial rendered output string\r\n * @param persona Typed metadata for the persona being built\r\n * @param target The current build target\r\n * @returns Final output string after all plugins have run\r\n */\r\nexport function runPostRender(\r\n plugins: PersonaBuildPlugin[],\r\n rendered: string,\r\n persona: PersonaMetadata,\r\n target: TargetType,\r\n): string {\r\n let output = rendered;\r\n for (const plugin of plugins) {\r\n if (typeof plugin.onPostRender === 'function') {\r\n output = plugin.onPostRender(output, persona, target);\r\n }\r\n }\r\n return output;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Per-persona validation collection\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Invoke the `onValidate` hook on every registered plugin and collect all\r\n * returned ValidationResult objects into a single flat array.\r\n *\r\n * Plugins that do not implement `onValidate` contribute nothing to the result.\r\n * The return value is always an array (never null/undefined).\r\n *\r\n * @param plugins Ordered list of registered plugins\r\n * @param persona Typed metadata for the persona being built\r\n * @param suite The suite configuration object\r\n * @param target The current build target (optional — forwarded to each plugin)\r\n * @returns Flat array of all ValidationResult objects from all plugins\r\n */\r\nexport function runValidate(\r\n plugins: PersonaBuildPlugin[],\r\n persona: PersonaMetadata,\r\n suite: SuiteConfig,\r\n target?: TargetType,\r\n): ValidationResult[] {\r\n const results: ValidationResult[] = [];\r\n for (const plugin of plugins) {\r\n if (typeof plugin.onValidate === 'function') {\r\n const pluginResults = plugin.onValidate(persona, suite, target);\r\n results.push(...pluginResults);\r\n }\r\n }\r\n return results;\r\n}\r\n","/**\r\n * src/builders/frontmatter.ts\r\n *\r\n * Frontmatter template registry for @mistralys/persona-builder.\r\n *\r\n * Ships two minimal default templates — one per target — that work for the\r\n * \"standalone\" persona mode (simple personas without numbered workflows or\r\n * MCP server blocks). Projects needing richer frontmatter register custom\r\n * templates via the `PersonaBuildPlugin.frontmatterTemplates` property.\r\n *\r\n * Template rendering follows the same two-step sequence as body rendering:\r\n * 1. resolveConditionals() — resolve {{#if flag}} blocks\r\n * 2. resolveVariables() — substitute {{varName}} markers\r\n *\r\n * No partials in frontmatter — frontmatter is kept deliberately simple.\r\n */\r\n\r\nimport { resolveConditionals } from '../engine/conditionals.js';\r\nimport { resolveVariables } from '../engine/variables.js';\r\nimport type { PersonaBuildPlugin } from '../plugins/types.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Built-in default templates\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Default VS Code frontmatter template.\r\n *\r\n * Minimal fields that work for standalone personas. Projects using numbered\r\n * workflows (e.g. ledger) should inject a richer template via a plugin.\r\n */\r\nexport const DEFAULT_FRONTMATTER_VSCODE = `---\r\nname: '{{name}} v{{version}}'\r\ndescription: '{{description}}'\r\ntools: [{{tools_list}}]\r\n---`;\r\n\r\n/**\r\n * Default Claude Code frontmatter template.\r\n *\r\n * Minimal fields that work for standalone personas. Projects using numbered\r\n * workflows should inject a richer template via a plugin.\r\n */\r\nexport const DEFAULT_FRONTMATTER_CLAUDE_CODE = `---\r\nname: {{cc_file_name_stem}}\r\npermissionMode: {{cc_permission_mode}}\r\nmodel: {{cc_model}}\r\nmemory: {{cc_memory}}\r\nallowedTools: [{{cc_tools_list}}]\r\n---`;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Template resolution\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Resolve frontmatter template precedence.\r\n *\r\n * Precedence order (highest wins):\r\n * 1. Plugin `frontmatterTemplates` — the last plugin with a matching key\r\n * wins (plugins are applied in reverse-registration order so the\r\n * *first* registered plugin with a template takes precedence over later\r\n * ones, matching the general plugin-chain contract).\r\n * 2. `configTemplates` — templates passed via `BuildConfig.frontmatter`\r\n * 3. Library defaults (`DEFAULT_FRONTMATTER_VSCODE` / `DEFAULT_FRONTMATTER_CLAUDE_CODE`)\r\n *\r\n * @param target The build target ('vscode' | 'claude-code')\r\n * @param plugins Registered plugins (searched in order; first match wins)\r\n * @param configTemplates Optional caller-supplied overrides from BuildConfig\r\n * @returns The resolved template string\r\n */\r\nexport function resolveFrontmatterTemplate(\r\n target: 'vscode' | 'claude-code',\r\n plugins: PersonaBuildPlugin[],\r\n configTemplates?: Partial<Record<'vscode' | 'claude-code', string>>,\r\n): string {\r\n // Check plugins in registration order — first plugin with a matching\r\n // frontmatterTemplates entry wins.\r\n for (const plugin of plugins) {\r\n if (plugin.frontmatterTemplates && target in plugin.frontmatterTemplates) {\r\n const tpl = plugin.frontmatterTemplates[target];\r\n if (tpl !== undefined) return tpl;\r\n }\r\n }\r\n\r\n // Caller-supplied config templates\r\n if (configTemplates && target in configTemplates) {\r\n const tpl = configTemplates[target];\r\n if (tpl !== undefined) return tpl;\r\n }\r\n\r\n // Library defaults\r\n return target === 'vscode' ? DEFAULT_FRONTMATTER_VSCODE : DEFAULT_FRONTMATTER_CLAUDE_CODE;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Frontmatter rendering\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Render a frontmatter template string against the given context.\r\n *\r\n * Applies the standard two-step template resolution:\r\n * 1. `resolveConditionals` — `{{#if flag}}` blocks\r\n * 2. `resolveVariables` — `{{varName}}` substitution\r\n *\r\n * @param template The raw frontmatter template string (may contain markers)\r\n * @param context Key-value context for variable substitution\r\n * @param filename Source filename used in warning messages\r\n * @returns Rendered frontmatter string (ready to prepend to body)\r\n */\r\nexport function renderFrontmatter(\r\n template: string,\r\n context: Record<string, unknown>,\r\n filename: string,\r\n): string {\r\n let rendered = resolveConditionals(template, context);\r\n rendered = resolveVariables(rendered, context, filename);\r\n return rendered;\r\n}\r\n","/**\r\n * src/builders/persona-builder.ts\r\n *\r\n * Core build orchestrator for @mistralys/persona-builder.\r\n *\r\n * Exports three public functions:\r\n *\r\n * 1. buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta,\r\n * partialsMap, config, plugins)\r\n * — Builds a single persona for a single target. Returns a BuildResult.\r\n *\r\n * 2. buildSuite(suiteName, suiteConfig, config, plugins)\r\n * — Discovers all persona YAMLs for a suite, fires onSuiteInit, maps\r\n * buildPersona() over each, and returns BuildResult[].\r\n *\r\n * 3. build(config)\r\n * — Top-level entry point. Iterates all suites × targets, calls\r\n * buildSuite() for each combination, and returns a BuildSummary.\r\n * Respects --check (no writes) and --strict (fail on warnings/errors).\r\n */\r\n\r\nimport { readdir, readFile, mkdir, writeFile } from 'node:fs/promises';\r\nimport { existsSync } from 'node:fs';\r\nimport path from 'node:path';\r\nimport yaml from 'js-yaml';\r\n\r\nimport { resolvePartials } from '../engine/partials.js';\r\nimport { resolveConditionals } from '../engine/conditionals.js';\r\nimport { resolveVariables } from '../engine/variables.js';\r\nimport {\r\n collapseBlankLines,\r\n ensureBlankLineBeforeHeadings,\r\n normalizeNewlines,\r\n} from '../engine/postProcessor.js';\r\nimport { serializeTools, serializeToolsList } from '../engine/serializer.js';\r\nimport { loadPartials } from '../loaders/partials-loader.js';\r\nimport {\r\n runSuiteInit,\r\n runBuildContext,\r\n runPostRender,\r\n runValidate,\r\n} from '../plugins/runner.js';\r\n\r\nimport { resolveFrontmatterTemplate, renderFrontmatter } from './frontmatter.js';\r\nimport type { BuildConfig, BuildResult, BuildSummary } from './types.js';\r\nimport type { PersonaBuildPlugin, PersonaMetadata, SuiteConfig, ValidationResult } from '../plugins/types.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Discover all persona YAML files in the `meta/` subdirectory of a suite.\r\n *\r\n * Excludes files whose names start with `_` (shared metadata files such as\r\n * `_shared.yaml`). Results are sorted lexicographically.\r\n *\r\n * @param suiteConfig Suite configuration (used to locate `metaSubdir`)\r\n * @returns Absolute paths to each persona YAML file, sorted.\r\n */\r\nasync function discoverSuitePersonaYamls(suiteConfig: SuiteConfig): Promise<string[]> {\r\n const metaSubdir = suiteConfig.metaSubdir ?? 'meta';\r\n const metaDir = path.join(suiteConfig.srcDir, metaSubdir);\r\n\r\n const entries = await readdir(metaDir, { withFileTypes: true });\r\n\r\n return entries\r\n .filter((e) => e.isFile() && e.name.endsWith('.yaml') && !e.name.startsWith('_'))\r\n .map((e) => path.join(metaDir, e.name))\r\n .sort();\r\n}\r\n\r\n/**\r\n * Load and parse a raw YAML file into a plain object.\r\n * Used for `_shared.yaml` which does not conform to PersonaMetadata's\r\n * `name` requirement.\r\n *\r\n * @param filePath Absolute path to the YAML file\r\n * @returns Parsed object, or {} when the file is empty/absent\r\n */\r\nasync function loadRawYaml(filePath: string): Promise<Record<string, unknown>> {\r\n if (!existsSync(filePath)) return {};\r\n const raw = await readFile(filePath, 'utf8');\r\n const parsed: unknown = yaml.load(raw);\r\n if (parsed === null || parsed === undefined) return {};\r\n if (typeof parsed !== 'object' || Array.isArray(parsed)) return {};\r\n return parsed as Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Load a persona YAML file and return it as a plain metadata record.\r\n * The `name` field is derived from the filename stem when absent.\r\n *\r\n * @param yamlPath Absolute path to the persona YAML file\r\n * @returns Merged metadata record ready for context building\r\n */\r\nasync function loadPersonaYaml(yamlPath: string): Promise<Record<string, unknown>> {\r\n const raw = await readFile(yamlPath, 'utf8');\r\n const parsed: unknown = yaml.load(raw);\r\n\r\n if (parsed === null || parsed === undefined || typeof parsed !== 'object' || Array.isArray(parsed)) {\r\n throw new Error(`buildPersona: expected a YAML object in \"${yamlPath}\"`);\r\n }\r\n\r\n const record = parsed as Record<string, unknown>;\r\n\r\n // Derive name from filename stem if not present in YAML\r\n if (!record['name']) {\r\n record['name'] = path.basename(yamlPath, '.yaml');\r\n }\r\n\r\n return record;\r\n}\r\n\r\n/**\r\n * Build the merged template context for a single persona.\r\n *\r\n * Merge order (later values win):\r\n * 1. sharedMeta (suite-level defaults)\r\n * 2. per-persona YAML fields\r\n * 3. derived/computed fields (version fallback, etc.)\r\n *\r\n * @param personaMeta Per-persona YAML as a plain record\r\n * @param sharedMeta Parsed `_shared.yaml` fields\r\n * @returns Merged rendering context\r\n */\r\nfunction buildContext(\r\n personaMeta: Record<string, unknown>,\r\n sharedMeta: Record<string, unknown>,\r\n): Record<string, unknown> {\r\n const version =\r\n typeof personaMeta['version'] === 'string'\r\n ? personaMeta['version']\r\n : typeof sharedMeta['default_version'] === 'string'\r\n ? sharedMeta['default_version']\r\n : '0.0.0';\r\n\r\n // Merge base: shared first, persona overrides\r\n const merged: Record<string, unknown> = {\r\n ...sharedMeta,\r\n ...personaMeta,\r\n version,\r\n };\r\n\r\n // ── Derived convenience fields (only set when not already provided) ───────\r\n // tools_list / tools_json — serialized from the `tools` array if present\r\n const tools = Array.isArray(merged['tools']) ? (merged['tools'] as string[]) : [];\r\n if (!('tools_list' in merged)) {\r\n merged['tools_list'] = serializeToolsList(tools);\r\n }\r\n if (!('tools_json' in merged)) {\r\n merged['tools_json'] = serializeTools(tools);\r\n }\r\n\r\n // cc_tools_list / cc_tools_json — from `cc_tools` or fall back to `tools`\r\n const ccTools = Array.isArray(merged['cc_tools']) ? (merged['cc_tools'] as string[]) : tools;\r\n if (!('cc_tools_list' in merged)) {\r\n merged['cc_tools_list'] = serializeToolsList(ccTools);\r\n }\r\n if (!('cc_tools_json' in merged)) {\r\n merged['cc_tools_json'] = serializeTools(ccTools);\r\n }\r\n\r\n // cc_file_name_stem — stem of cc_file_name (for default CC frontmatter template)\r\n if (!('cc_file_name_stem' in merged) && typeof merged['cc_file_name'] === 'string') {\r\n const ccFileName = merged['cc_file_name'] as string;\r\n merged['cc_file_name_stem'] = ccFileName.replace(/\\.md$/, '');\r\n }\r\n\r\n return merged;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// buildPersona — single persona × single target\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Build a single persona for a single output target.\r\n *\r\n * Pipeline:\r\n * 1. Load sharedMeta + personaMeta (callers supply pre-loaded values)\r\n * 2. Build merged context\r\n * 3. Run onBuildContext plugin hooks (context accumulation)\r\n * 4. Resolve frontmatter template → render frontmatter\r\n * 5. Load content template\r\n * 6. Render body: partials → conditionals → variables → post-process\r\n * 7. Assemble final output (frontmatter + body)\r\n * 8. Run onPostRender plugin hooks (output chain)\r\n * 9. Run onValidate plugin hooks (validation collection)\r\n * 10. Determine output file path\r\n * 11. Write output file (unless check mode)\r\n * 12. Return BuildResult\r\n *\r\n * @param personaYamlPath Absolute path to the persona YAML source file\r\n * @param suiteName Identifier for the suite this persona belongs to\r\n * @param suiteConfig Suite configuration object\r\n * @param sharedMeta Pre-loaded `_shared.yaml` contents\r\n * @param partialsMap Pre-loaded partials map (shared + suite-local merged)\r\n * @param config Top-level BuildConfig\r\n * @param plugins Registered plugins\r\n * @param target Target output format\r\n * @returns BuildResult for this persona × target combination\r\n */\r\nexport async function buildPersona(\r\n personaYamlPath: string,\r\n suiteName: string,\r\n suiteConfig: SuiteConfig,\r\n sharedMeta: Record<string, unknown>,\r\n partialsMap: Record<string, string>,\r\n config: BuildConfig,\r\n plugins: PersonaBuildPlugin[],\r\n target: 'vscode' | 'claude-code',\r\n): Promise<BuildResult> {\r\n // ── 1. Load persona metadata ──────────────────────────────────────────────\r\n const personaMeta = await loadPersonaYaml(personaYamlPath);\r\n\r\n // ── 2. Build merged context ───────────────────────────────────────────────\r\n let context = buildContext(personaMeta, sharedMeta);\r\n\r\n // ── 3. Plugin onBuildContext ──────────────────────────────────────────────\r\n // Cast context to PersonaMetadata for the plugin runner (it requires a\r\n // name field which is guaranteed by loadPersonaYaml above).\r\n const personaMetaTyped = personaMeta as PersonaMetadata;\r\n context = runBuildContext(plugins, context, personaMetaTyped, suiteConfig);\r\n\r\n // ── 4. Render frontmatter ─────────────────────────────────────────────────\r\n const fmTemplate = resolveFrontmatterTemplate(target, plugins, config.frontmatter);\r\n const contentBasename = path.basename(personaYamlPath, '.yaml') + '.md';\r\n const frontmatter = renderFrontmatter(fmTemplate, context, contentBasename);\r\n\r\n // ── 5. Load content template ──────────────────────────────────────────────\r\n const contentSubdir = suiteConfig.contentSubdir ?? 'content';\r\n const contentPath = path.join(suiteConfig.srcDir, contentSubdir, contentBasename);\r\n const bodyTemplate = normalizeNewlines(await readFile(contentPath, 'utf8'));\r\n\r\n // ── 6. Render body ────────────────────────────────────────────────────────\r\n let body = resolvePartials(bodyTemplate, partialsMap);\r\n body = resolveConditionals(body, context);\r\n body = resolveVariables(body, context, contentBasename);\r\n body = collapseBlankLines(body);\r\n body = ensureBlankLineBeforeHeadings(body);\r\n body = body.trimEnd();\r\n\r\n // ── 7. Assemble output ────────────────────────────────────────────────────\r\n let output = normalizeNewlines(`${frontmatter}\\n\\n${body}\\n`);\r\n\r\n // ── 8. Plugin onPostRender ────────────────────────────────────────────────\r\n output = runPostRender(plugins, output, personaMetaTyped, target);\r\n\r\n // ── 9. Plugin onValidate ──────────────────────────────────────────────────\r\n const validationResults: ValidationResult[] = runValidate(plugins, personaMetaTyped, suiteConfig, target);\r\n\r\n // ── 10. Determine output file path ────────────────────────────────────────\r\n const outputDir = target === 'vscode' ? suiteConfig.outVscode : suiteConfig.outClaudeCode;\r\n // Use declared output filename fields when present (vs_file_name / cc_file_name),\r\n // falling back to the content basename.\r\n let outputBasename: string;\r\n if (target === 'vscode' && typeof context['vs_file_name'] === 'string') {\r\n outputBasename = context['vs_file_name'];\r\n } else if (target === 'claude-code' && typeof context['cc_file_name'] === 'string') {\r\n outputBasename = context['cc_file_name'];\r\n } else {\r\n outputBasename = contentBasename;\r\n }\r\n const outputPath = path.join(outputDir, outputBasename);\r\n\r\n // ── 11. Write (unless check mode) ─────────────────────────────────────────\r\n const check = config.check ?? false;\r\n let written = false;\r\n\r\n if (!check) {\r\n await mkdir(outputDir, { recursive: true });\r\n await writeFile(outputPath, output, 'utf8');\r\n written = true;\r\n }\r\n\r\n return {\r\n suite: suiteName,\r\n target,\r\n personaYamlPath,\r\n outputPath,\r\n content: output,\r\n validationResults,\r\n written,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// buildSuite — all personas in one suite × one target\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Build all personas in a suite for a single output target.\r\n *\r\n * Pipeline:\r\n * 1. Load `_shared.yaml` for the suite\r\n * 2. Load merged partials (shared → suite-local)\r\n * 3. Run `onSuiteInit` on all plugins\r\n * 4. Discover all persona YAML files\r\n * 5. Call `buildPersona()` for each\r\n *\r\n * @param suiteName Identifier for this suite\r\n * @param suiteConfig Suite configuration\r\n * @param config Top-level BuildConfig\r\n * @param plugins Registered plugins\r\n * @param target Target output format\r\n * @returns Array of BuildResult objects, one per persona\r\n */\r\nexport async function buildSuite(\r\n suiteName: string,\r\n suiteConfig: SuiteConfig,\r\n config: BuildConfig,\r\n plugins: PersonaBuildPlugin[],\r\n target: 'vscode' | 'claude-code',\r\n): Promise<BuildResult[]> {\r\n // ── 1. Load shared metadata ───────────────────────────────────────────────\r\n const metaSubdir = suiteConfig.metaSubdir ?? 'meta';\r\n const sharedYamlPath = path.join(suiteConfig.srcDir, metaSubdir, '_shared.yaml');\r\n const sharedMeta = await loadRawYaml(sharedYamlPath);\r\n\r\n // ── 2. Load partials (two-layer: shared base → suite-local override) ──────\r\n let partialsMap: Record<string, string> = {};\r\n\r\n if (config.sharedPartialsDir && existsSync(config.sharedPartialsDir)) {\r\n partialsMap = { ...partialsMap, ...(await loadPartials(config.sharedPartialsDir)) };\r\n }\r\n\r\n const partialsSubdir = suiteConfig.partialsSubdir ?? 'partials';\r\n const suitePartialsDir = path.join(suiteConfig.srcDir, partialsSubdir);\r\n if (existsSync(suitePartialsDir)) {\r\n partialsMap = { ...partialsMap, ...(await loadPartials(suitePartialsDir)) };\r\n }\r\n\r\n // ── 3. Plugin onSuiteInit ─────────────────────────────────────────────────\r\n runSuiteInit(plugins, suiteConfig, sharedMeta);\r\n\r\n // ── 4. Discover persona YAML files ────────────────────────────────────────\r\n const personaYamlPaths = await discoverSuitePersonaYamls(suiteConfig);\r\n\r\n // ── 5. Build each persona ─────────────────────────────────────────────────\r\n const results: BuildResult[] = [];\r\n for (const yamlPath of personaYamlPaths) {\r\n const result = await buildPersona(\r\n yamlPath,\r\n suiteName,\r\n suiteConfig,\r\n sharedMeta,\r\n partialsMap,\r\n config,\r\n plugins,\r\n target,\r\n );\r\n results.push(result);\r\n }\r\n\r\n return results;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// build — top-level entry point\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Top-level build orchestrator.\r\n *\r\n * Iterates all `config.suites × config.targets` combinations, calls\r\n * `buildSuite()` for each, and aggregates the results into a `BuildSummary`.\r\n *\r\n * Modes:\r\n * - Normal: renders and writes all personas.\r\n * - `check: true`: renders without writing; useful for CI staleness checks.\r\n * - `strict: true`: throws when any ValidationResult has severity `'error'`\r\n * or `'warning'`. All suites are processed before the throw, so output\r\n * files **will** be written to disk even when the build ultimately fails.\r\n * **For CI usage, combine `strict: true` with `check: true`** to avoid\r\n * leaving partial artefacts on disk when validation fails.\r\n *\r\n * @param config Typed build configuration\r\n * @returns Aggregated BuildSummary\r\n * @throws `Error` when `strict: true` and validation failures exist\r\n */\r\nexport async function build(config: BuildConfig): Promise<BuildSummary> {\r\n const plugins = config.plugins ?? [];\r\n const targets = config.targets ?? ['vscode', 'claude-code'];\r\n const allResults: BuildResult[] = [];\r\n\r\n for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {\r\n for (const target of targets) {\r\n const suiteResults = await buildSuite(suiteName, suiteConfig, config, plugins, target);\r\n allResults.push(...suiteResults);\r\n }\r\n }\r\n\r\n // Collect strict failures (error + warning severity)\r\n const strictFailures: ValidationResult[] = config.strict\r\n ? allResults.flatMap((r) =>\r\n r.validationResults.filter(\r\n (v) => v.severity === 'error' || v.severity === 'warning',\r\n ),\r\n )\r\n : [];\r\n\r\n const success = !config.strict || strictFailures.length === 0;\r\n\r\n const summary: BuildSummary = {\r\n success,\r\n results: allResults,\r\n strictFailures,\r\n totalBuilt: allResults.length,\r\n totalWritten: allResults.filter((r) => r.written).length,\r\n };\r\n\r\n if (config.strict && !success) {\r\n const messages = strictFailures.map((f) => `[${f.severity}] ${f.message}`).join('\\n');\r\n throw new Error(\r\n `Build failed in strict mode — ${strictFailures.length} validation issue(s):\\n${messages}`,\r\n );\r\n }\r\n\r\n return summary;\r\n}\r\n","#!/usr/bin/env node\r\n/**\r\n * src/cli.ts — @mistralys/persona-builder CLI entry point\r\n *\r\n * Flags:\r\n * --config <path> Path to config file (JS/CJS/JSON). Default: persona-build.config.js\r\n * --check Run the build pipeline but do not write output files.\r\n * Always exits 0 unless combined with --strict, which causes\r\n * exit 1 when any ValidationResult has severity 'error' or\r\n * 'warning'.\r\n * --strict Fail (exit 1) if any ValidationResult has severity\r\n * 'error' or 'warning'.\r\n * --help Print usage and exit 0.\r\n * --version Print package version and exit 0.\r\n *\r\n * No heavy CLI framework — args are parsed with a hand-rolled loop.\r\n */\r\n\r\nimport { createRequire } from 'node:module';\r\nimport path from 'node:path';\r\nimport { existsSync } from 'node:fs';\r\nimport { pathToFileURL } from 'node:url';\r\n\r\nimport { build } from './builders/persona-builder.js';\r\nimport type { BuildConfig, BuildSummary } from './builders/types.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Version — sourced from package.json (single source of truth).\r\n// createRequire is already imported above for config loading; reuse it here.\r\n// ---------------------------------------------------------------------------\r\n\r\nconst _pkgRequire = createRequire(import.meta.url);\r\nconst VERSION = (_pkgRequire('../package.json') as { version: string }).version;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Usage / help text\r\n// ---------------------------------------------------------------------------\r\n\r\nconst USAGE = `\r\n@mistralys/persona-builder v${VERSION}\r\n\r\nBuild AI persona documents from YAML metadata and Markdown content templates.\r\n\r\nUSAGE\r\n persona-build [options]\r\n\r\nOPTIONS\r\n --config <path> Path to the build config file.\r\n Supports .js (ESM), .cjs, and .json formats.\r\n Default: persona-build.config.js in the current directory.\r\n --check Render personas but skip writing output files.\r\n Always exits 0 on its own. Combine with --strict to\r\n exit 1 when validators report errors or warnings.\r\n --strict Exit 1 if any validation result has severity 'error'\r\n or 'warning'.\r\n --help Show this help message and exit.\r\n --version Print the package version and exit.\r\n\r\nEXAMPLES\r\n persona-build # Build with default config\r\n persona-build --config ./my-config.js # Build with a custom config\r\n persona-build --check # CI staleness check (no file writes)\r\n persona-build --strict # Fail on warnings or errors\r\n persona-build --check --strict # Safe CI check — no writes + strict\r\n`.trim();\r\n\r\n// ---------------------------------------------------------------------------\r\n// Arg parsing\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface ParsedArgs {\r\n configPath?: string;\r\n check: boolean;\r\n strict: boolean;\r\n help: boolean;\r\n version: boolean;\r\n}\r\n\r\nfunction parseArgs(argv: string[]): ParsedArgs {\r\n const args = argv.slice(2); // strip 'node' + script path\r\n\r\n const result: ParsedArgs = {\r\n configPath: undefined,\r\n check: false,\r\n strict: false,\r\n help: false,\r\n version: false,\r\n };\r\n\r\n let i = 0;\r\n while (i < args.length) {\r\n const arg = args[i];\r\n switch (arg) {\r\n case '--help':\r\n case '-h':\r\n result.help = true;\r\n break;\r\n case '--version':\r\n case '-v':\r\n result.version = true;\r\n break;\r\n case '--check':\r\n result.check = true;\r\n break;\r\n case '--strict':\r\n result.strict = true;\r\n break;\r\n case '--config': {\r\n const next = args[i + 1];\r\n if (!next || next.startsWith('--')) {\r\n console.error('Error: --config requires a path argument.');\r\n process.exit(1);\r\n }\r\n result.configPath = next;\r\n i++; // consume the value\r\n break;\r\n }\r\n default:\r\n // Unknown flag — warn but do not exit so older configs stay forward-compatible\r\n if (arg.startsWith('--')) {\r\n console.warn(`Warning: Unknown flag \"${arg}\" — ignored.`);\r\n }\r\n }\r\n i++;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Config loading\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Resolve the config file path from the user-supplied value or the default\r\n * discovery chain.\r\n *\r\n * Discovery order (when --config is not supplied):\r\n * 1. persona-build.config.js (ESM)\r\n * 2. persona-build.config.cjs (CJS)\r\n * 3. persona-build.config.json (JSON)\r\n */\r\nfunction resolveConfigPath(cliValue?: string): string {\r\n if (cliValue) {\r\n const resolved = path.resolve(cliValue);\r\n if (!existsSync(resolved)) {\r\n console.error(`Error: Config file not found: ${resolved}`);\r\n process.exit(1);\r\n }\r\n return resolved;\r\n }\r\n\r\n const candidates = [\r\n 'persona-build.config.js',\r\n 'persona-build.config.cjs',\r\n 'persona-build.config.json',\r\n ];\r\n\r\n for (const name of candidates) {\r\n const candidate = path.resolve(name);\r\n if (existsSync(candidate)) return candidate;\r\n }\r\n\r\n console.error(\r\n 'Error: No config file found. ' +\r\n 'Create persona-build.config.js in the current directory or pass --config <path>.',\r\n );\r\n process.exit(1);\r\n}\r\n\r\n/**\r\n * Load and validate the config file.\r\n *\r\n * Supports:\r\n * - ESM .js → dynamic import()\r\n * - CJS .cjs → createRequire()\r\n * - JSON .json → createRequire()\r\n *\r\n * The config module must export a default export (or be a plain JSON object)\r\n * that conforms to BuildConfig.\r\n */\r\nasync function loadConfig(configPath: string): Promise<BuildConfig> {\r\n const ext = path.extname(configPath).toLowerCase();\r\n\r\n let rawConfig: unknown;\r\n\r\n if (ext === '.cjs' || ext === '.json') {\r\n const require = createRequire(import.meta.url);\r\n rawConfig = require(configPath);\r\n } else {\r\n // ESM default — use dynamic import with a file URL\r\n const fileUrl = pathToFileURL(configPath).href;\r\n const mod = await import(fileUrl);\r\n rawConfig = mod.default ?? mod;\r\n }\r\n\r\n if (!rawConfig || typeof rawConfig !== 'object' || Array.isArray(rawConfig)) {\r\n console.error(\r\n `Error: Config file \"${configPath}\" must export a plain object (BuildConfig).`,\r\n );\r\n process.exit(1);\r\n }\r\n\r\n const config = rawConfig as BuildConfig;\r\n\r\n if (!config.suites || typeof config.suites !== 'object') {\r\n console.error(\r\n `Error: Config file \"${configPath}\" must have a \"suites\" property (record of suite configs).`,\r\n );\r\n process.exit(1);\r\n }\r\n\r\n return config;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Output formatting\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction printSummary(summary: BuildSummary, check: boolean): void {\r\n const mode = check ? ' [check mode — no files written]' : '';\r\n const status = summary.success ? '✓ Build succeeded' : '✗ Build failed';\r\n console.log(`${status}${mode}`);\r\n console.log(` Personas processed : ${summary.totalBuilt}`);\r\n if (!check) {\r\n console.log(` Files written : ${summary.totalWritten}`);\r\n }\r\n if (summary.strictFailures.length > 0) {\r\n console.log(`\\n Validation failures (${summary.strictFailures.length}):`);\r\n for (const f of summary.strictFailures) {\r\n console.log(` [${f.severity}] ${f.message}`);\r\n }\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Main entry point\r\n// ---------------------------------------------------------------------------\r\n\r\nasync function main(): Promise<void> {\r\n const args = parseArgs(process.argv);\r\n\r\n // Short-circuit flags\r\n if (args.help) {\r\n console.log(USAGE);\r\n process.exit(0);\r\n }\r\n\r\n if (args.version) {\r\n console.log(VERSION);\r\n process.exit(0);\r\n }\r\n\r\n // Resolve and load config\r\n const configPath = resolveConfigPath(args.configPath);\r\n let config: BuildConfig;\r\n\r\n try {\r\n config = await loadConfig(configPath);\r\n } catch (err) {\r\n console.error(`Error loading config: ${err instanceof Error ? err.message : String(err)}`);\r\n process.exit(1);\r\n }\r\n\r\n // Apply CLI flag overrides (CLI flags take precedence over config-file values)\r\n if (args.check) config.check = true;\r\n if (args.strict) config.strict = true;\r\n\r\n // Run the build\r\n let summary: BuildSummary;\r\n try {\r\n summary = await build(config);\r\n } catch (err) {\r\n // build() throws in strict mode when there are validation failures\r\n if (err instanceof Error) {\r\n console.error(`\\n${err.message}`);\r\n } else {\r\n console.error('Build failed with an unexpected error:', err);\r\n }\r\n process.exit(1);\r\n }\r\n\r\n // Print results\r\n printSummary(summary, config.check ?? false);\r\n\r\n // Exit code\r\n if (!summary.success) {\r\n process.exit(1);\r\n }\r\n\r\n process.exit(0);\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error('Unexpected error:', err);\r\n process.exit(1);\r\n});\r\n"]}
|
package/dist/cli.js
CHANGED
|
@@ -108,11 +108,11 @@ function runPostRender(plugins, rendered, persona, target) {
|
|
|
108
108
|
}
|
|
109
109
|
return output;
|
|
110
110
|
}
|
|
111
|
-
function runValidate(plugins, persona, suite) {
|
|
111
|
+
function runValidate(plugins, persona, suite, target) {
|
|
112
112
|
const results = [];
|
|
113
113
|
for (const plugin of plugins) {
|
|
114
114
|
if (typeof plugin.onValidate === "function") {
|
|
115
|
-
const pluginResults = plugin.onValidate(persona, suite);
|
|
115
|
+
const pluginResults = plugin.onValidate(persona, suite, target);
|
|
116
116
|
results.push(...pluginResults);
|
|
117
117
|
}
|
|
118
118
|
}
|
|
@@ -227,7 +227,7 @@ async function buildPersona(personaYamlPath, suiteName, suiteConfig, sharedMeta,
|
|
|
227
227
|
${body}
|
|
228
228
|
`);
|
|
229
229
|
output = runPostRender(plugins, output, personaMetaTyped, target);
|
|
230
|
-
const validationResults = runValidate(plugins, personaMetaTyped, suiteConfig);
|
|
230
|
+
const validationResults = runValidate(plugins, personaMetaTyped, suiteConfig, target);
|
|
231
231
|
const outputDir = target === "vscode" ? suiteConfig.outVscode : suiteConfig.outClaudeCode;
|
|
232
232
|
let outputBasename;
|
|
233
233
|
if (target === "vscode" && typeof context["vs_file_name"] === "string") {
|
|
@@ -323,7 +323,7 @@ ${messages}`
|
|
|
323
323
|
var _pkgRequire = createRequire(import.meta.url);
|
|
324
324
|
var VERSION = _pkgRequire("../package.json").version;
|
|
325
325
|
var USAGE = `
|
|
326
|
-
@
|
|
326
|
+
@mistralys/persona-builder v${VERSION}
|
|
327
327
|
|
|
328
328
|
Build AI persona documents from YAML metadata and Markdown content templates.
|
|
329
329
|
|