@traits-dev/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,2968 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ compileProfile: () => compileProfile,
34
+ compileResolvedProfile: () => compileResolvedProfile,
35
+ evaluateTier1Response: () => evaluateTier1Response,
36
+ injectPersonality: () => injectPersonality,
37
+ loadProfileFile: () => loadProfileFile,
38
+ normalizeProfile: () => normalizeProfile,
39
+ resolveActiveContext: () => resolveActiveContext,
40
+ resolveExtends: () => resolveExtends,
41
+ runImportAnalysis: () => runImportAnalysis,
42
+ runTier1Evaluation: () => runTier1Evaluation,
43
+ runTier1EvaluationForProfile: () => runTier1EvaluationForProfile,
44
+ runTier2Evaluation: () => runTier2Evaluation,
45
+ runTier2EvaluationForProfile: () => runTier2EvaluationForProfile,
46
+ runTier3Evaluation: () => runTier3Evaluation,
47
+ runTier3EvaluationForProfile: () => runTier3EvaluationForProfile,
48
+ validateEvalScenario: () => validateEvalScenario,
49
+ validateEvalScenarios: () => validateEvalScenarios,
50
+ validateProfile: () => validateProfile,
51
+ validateResolvedProfile: () => validateResolvedProfile
52
+ });
53
+ module.exports = __toCommonJS(index_exports);
54
+
55
+ // src/profile/load.ts
56
+ var import_node_fs = __toESM(require("fs"), 1);
57
+ var import_node_path = __toESM(require("path"), 1);
58
+ var import_yaml = require("yaml");
59
+ function loadProfileFile(filePath) {
60
+ const raw = import_node_fs.default.readFileSync(filePath, "utf8");
61
+ return (0, import_yaml.parse)(raw);
62
+ }
63
+ function resolveParentPath(profilePath, extendsName, options = {}) {
64
+ const localCandidate = import_node_path.default.join(import_node_path.default.dirname(profilePath), `${extendsName}.yaml`);
65
+ if (import_node_fs.default.existsSync(localCandidate)) return localCandidate;
66
+ const bundledDir = options.bundledProfilesDir ?? import_node_path.default.resolve(import_node_path.default.dirname(profilePath), "..");
67
+ const bundledCandidate = import_node_path.default.join(bundledDir, `${extendsName}.yaml`);
68
+ if (import_node_fs.default.existsSync(bundledCandidate)) return bundledCandidate;
69
+ return null;
70
+ }
71
+
72
+ // src/utils.ts
73
+ var LEVELS = ["very-low", "low", "medium", "high", "very-high"];
74
+ var LEVEL_ORDER = LEVELS;
75
+ var LEVEL_INDEX = new Map(
76
+ LEVEL_ORDER.map((level, idx) => [level, idx])
77
+ );
78
+ var DIMENSIONS = [
79
+ "formality",
80
+ "warmth",
81
+ "verbosity",
82
+ "directness",
83
+ "empathy",
84
+ "humor"
85
+ ];
86
+ var PROTECTED_REFUSAL_TERMS = [
87
+ "I can't help with that",
88
+ "I'm not able to",
89
+ "That's not something I can do",
90
+ "I need to decline"
91
+ ];
92
+ function asArray(value) {
93
+ if (!Array.isArray(value)) return [];
94
+ return value;
95
+ }
96
+ function clone(value) {
97
+ return JSON.parse(JSON.stringify(value));
98
+ }
99
+ function isClaudeModel(model) {
100
+ return /claude/i.test(String(model ?? ""));
101
+ }
102
+ function isGptModel(model) {
103
+ return /gpt/i.test(String(model ?? ""));
104
+ }
105
+
106
+ // src/profile/merge.ts
107
+ var PASS_THROUGH_FIELDS = /* @__PURE__ */ new Set([
108
+ "schema",
109
+ "meta",
110
+ "identity",
111
+ "voice",
112
+ "vocabulary",
113
+ "behavioral_rules",
114
+ "context_adaptations",
115
+ "extends",
116
+ "behavioral_rules_remove",
117
+ "context_adaptations_remove"
118
+ ]);
119
+ function dedupExact(items) {
120
+ const seen = /* @__PURE__ */ new Set();
121
+ const out = [];
122
+ for (const item of items) {
123
+ const key = String(item);
124
+ if (seen.has(key)) continue;
125
+ seen.add(key);
126
+ out.push(item);
127
+ }
128
+ return out;
129
+ }
130
+ function dedupCaseInsensitive(items) {
131
+ const seen = /* @__PURE__ */ new Set();
132
+ const out = [];
133
+ for (const item of items) {
134
+ const key = String(item).toLowerCase();
135
+ if (seen.has(key)) continue;
136
+ seen.add(key);
137
+ out.push(item);
138
+ }
139
+ return out;
140
+ }
141
+ function mergeMeta(parentMeta = {}, childMeta = {}) {
142
+ const merged = { ...parentMeta, ...childMeta };
143
+ const parentTags = asArray(parentMeta.tags);
144
+ const childTags = asArray(childMeta.tags);
145
+ if (parentTags.length || childTags.length) {
146
+ merged.tags = dedupCaseInsensitive([...parentTags, ...childTags]);
147
+ }
148
+ return merged;
149
+ }
150
+ function mergeIdentity(parentIdentity = {}, childIdentity = {}) {
151
+ return { ...parentIdentity, ...childIdentity };
152
+ }
153
+ function mergeVoice(parentVoice = {}, childVoice = {}) {
154
+ return { ...parentVoice, ...childVoice };
155
+ }
156
+ function mergeVocabulary(parentVocab = {}, childVocab = {}) {
157
+ const parent = parentVocab ?? {};
158
+ const child = childVocab ?? {};
159
+ const merged = { ...parent, ...child };
160
+ const mergedPreferred = dedupCaseInsensitive([
161
+ ...asArray(parent.preferred_terms),
162
+ ...asArray(child.preferred_terms)
163
+ ]);
164
+ const mergedForbidden = dedupCaseInsensitive([
165
+ ...asArray(parent.forbidden_terms),
166
+ ...asArray(child.forbidden_terms)
167
+ ]);
168
+ if (mergedPreferred.length) merged.preferred_terms = mergedPreferred;
169
+ if (mergedForbidden.length) merged.forbidden_terms = mergedForbidden;
170
+ return merged;
171
+ }
172
+ function mergeBehavioralRules(parentRules = [], childRules = []) {
173
+ return dedupExact([...asArray(parentRules), ...asArray(childRules)]);
174
+ }
175
+ function mergeContextAdaptations(parentAdaptations = [], childAdaptations = []) {
176
+ const base = asArray(parentAdaptations).map((item) => clone(item));
177
+ const replacement = asArray(childAdaptations);
178
+ const byWhen = /* @__PURE__ */ new Map();
179
+ base.forEach((item, idx) => {
180
+ if (item?.when) {
181
+ byWhen.set(item.when, { item, idx });
182
+ }
183
+ });
184
+ const out = [...base];
185
+ for (const next of replacement) {
186
+ const whenKey = next?.when;
187
+ if (whenKey && byWhen.has(whenKey)) {
188
+ const slot = byWhen.get(whenKey);
189
+ if (!slot) continue;
190
+ out[slot.idx] = clone(next);
191
+ continue;
192
+ }
193
+ out.push(clone(next));
194
+ }
195
+ return out;
196
+ }
197
+ function removeCaseInsensitive(items, removals) {
198
+ const removalSet = new Set(
199
+ asArray(removals).map((item) => String(item).toLowerCase())
200
+ );
201
+ return asArray(items).filter((item) => !removalSet.has(String(item).toLowerCase()));
202
+ }
203
+ function applyExplicitRemovals(childProfile, mergedProfile) {
204
+ const childBehavioralRemovals = asArray(childProfile.behavioral_rules_remove);
205
+ const childForbiddenRemovals = asArray(
206
+ childProfile?.vocabulary?.forbidden_terms_remove
207
+ );
208
+ const childPreferredRemovals = asArray(
209
+ childProfile?.vocabulary?.preferred_terms_remove
210
+ );
211
+ const childAdaptationRemovals = asArray(childProfile.context_adaptations_remove);
212
+ if (childBehavioralRemovals.length) {
213
+ mergedProfile.behavioral_rules = asArray(mergedProfile.behavioral_rules).filter(
214
+ (rule) => !childBehavioralRemovals.includes(rule)
215
+ );
216
+ }
217
+ if (childForbiddenRemovals.length) {
218
+ const nextForbidden = removeCaseInsensitive(
219
+ mergedProfile?.vocabulary?.forbidden_terms,
220
+ childForbiddenRemovals
221
+ );
222
+ mergedProfile.vocabulary = mergedProfile.vocabulary ?? {};
223
+ mergedProfile.vocabulary.forbidden_terms = nextForbidden;
224
+ }
225
+ if (childPreferredRemovals.length) {
226
+ const nextPreferred = removeCaseInsensitive(
227
+ mergedProfile?.vocabulary?.preferred_terms,
228
+ childPreferredRemovals
229
+ );
230
+ mergedProfile.vocabulary = mergedProfile.vocabulary ?? {};
231
+ mergedProfile.vocabulary.preferred_terms = nextPreferred;
232
+ }
233
+ if (childAdaptationRemovals.length) {
234
+ const removalSet = new Set(childAdaptationRemovals);
235
+ mergedProfile.context_adaptations = asArray(
236
+ mergedProfile.context_adaptations
237
+ ).filter((adaptation) => !removalSet.has(adaptation?.when));
238
+ }
239
+ }
240
+ function mergeProfiles(parentProfile, childProfile) {
241
+ const merged = clone(parentProfile);
242
+ merged.schema = childProfile.schema ?? parentProfile.schema;
243
+ merged.meta = mergeMeta(parentProfile.meta, childProfile.meta);
244
+ merged.identity = mergeIdentity(parentProfile.identity, childProfile.identity);
245
+ merged.voice = mergeVoice(parentProfile.voice, childProfile.voice);
246
+ merged.vocabulary = mergeVocabulary(parentProfile.vocabulary, childProfile.vocabulary);
247
+ merged.behavioral_rules = mergeBehavioralRules(
248
+ parentProfile.behavioral_rules,
249
+ childProfile.behavioral_rules
250
+ );
251
+ merged.context_adaptations = mergeContextAdaptations(
252
+ parentProfile.context_adaptations,
253
+ childProfile.context_adaptations
254
+ );
255
+ for (const [key, value] of Object.entries(childProfile)) {
256
+ if (PASS_THROUGH_FIELDS.has(key)) {
257
+ continue;
258
+ }
259
+ merged[key] = clone(value);
260
+ }
261
+ applyExplicitRemovals(childProfile, merged);
262
+ return merged;
263
+ }
264
+
265
+ // src/profile/extends.ts
266
+ function resolveExtends(profilePath, options = {}) {
267
+ const diagnostics = { warnings: [], errors: [] };
268
+ const childProfile = loadProfileFile(profilePath);
269
+ if (!childProfile?.extends) {
270
+ return {
271
+ profile: childProfile,
272
+ parentPath: null,
273
+ diagnostics
274
+ };
275
+ }
276
+ const parentPath = resolveParentPath(profilePath, childProfile.extends, options);
277
+ if (!parentPath) {
278
+ diagnostics.errors.push({
279
+ code: "E_RESOLVE_EXTENDS",
280
+ severity: "error",
281
+ message: `Unable to resolve parent profile "${childProfile.extends}".`
282
+ });
283
+ return { profile: childProfile, parentPath: null, diagnostics };
284
+ }
285
+ const parentProfile = loadProfileFile(parentPath);
286
+ if (parentProfile?.extends) {
287
+ diagnostics.errors.push({
288
+ code: "E_EXTENDS_CHAIN",
289
+ severity: "error",
290
+ message: "extends chains are not supported in MVP."
291
+ });
292
+ return { profile: childProfile, parentPath, diagnostics };
293
+ }
294
+ const merged = mergeProfiles(parentProfile, childProfile);
295
+ delete merged.extends;
296
+ return { profile: merged, parentPath, diagnostics };
297
+ }
298
+
299
+ // src/profile/normalize.ts
300
+ function normalizeProfile(profilePath, options = {}) {
301
+ return resolveExtends(profilePath, options).profile;
302
+ }
303
+
304
+ // src/profile/context.ts
305
+ function resolveActiveContext(profile, context = {}) {
306
+ const contextAdaptations = asArray(profile.context_adaptations).map(
307
+ (adaptation, index) => ({
308
+ ...adaptation,
309
+ _index: index,
310
+ _priority: Number(adaptation?.priority ?? 0)
311
+ })
312
+ );
313
+ const active = contextAdaptations.filter((adaptation) => {
314
+ const whenKey = adaptation?.when;
315
+ if (!whenKey) return false;
316
+ return Boolean(context[whenKey]);
317
+ });
318
+ const ordered = active.sort((a, b) => {
319
+ if (a._priority !== b._priority) return a._priority - b._priority;
320
+ return a._index - b._index;
321
+ });
322
+ const resolvedAdjustments = {};
323
+ const collectedInject = [];
324
+ for (const adaptation of ordered) {
325
+ for (const [dimension, value] of Object.entries(adaptation.adjustments ?? {})) {
326
+ resolvedAdjustments[dimension] = value;
327
+ }
328
+ for (const rule of asArray(adaptation.inject)) {
329
+ collectedInject.push(rule);
330
+ }
331
+ }
332
+ return {
333
+ matched: ordered.map(({ _index, _priority, ...rest }) => rest),
334
+ resolvedAdjustments,
335
+ injectRules: collectedInject
336
+ };
337
+ }
338
+
339
+ // src/validator/overspec.ts
340
+ function computeConstraintCount(profile) {
341
+ const behavioralRules = asArray(profile?.behavioral_rules).length;
342
+ const preferredTerms = asArray(profile?.vocabulary?.preferred_terms).length;
343
+ const forbiddenTerms = asArray(profile?.vocabulary?.forbidden_terms).length;
344
+ const contextAdaptations = asArray(profile?.context_adaptations).length;
345
+ const total = behavioralRules + preferredTerms + forbiddenTerms + contextAdaptations;
346
+ return {
347
+ total,
348
+ breakdown: {
349
+ behavioral_rules: behavioralRules,
350
+ vocabulary_preferred_terms: preferredTerms,
351
+ vocabulary_forbidden_terms: forbiddenTerms,
352
+ context_adaptations: contextAdaptations
353
+ }
354
+ };
355
+ }
356
+ function checkOverspec(profile) {
357
+ const { total, breakdown } = computeConstraintCount(profile);
358
+ const diagnostics = [];
359
+ if (total > 30) {
360
+ diagnostics.push({
361
+ code: "S004",
362
+ severity: "error",
363
+ message: `Constraint count is ${total}, above the hard limit of 30.`,
364
+ details: breakdown
365
+ });
366
+ } else if (total > 15) {
367
+ diagnostics.push({
368
+ code: "S004",
369
+ severity: "warning",
370
+ message: `Constraint count is ${total}, above the warning threshold of 15.`,
371
+ details: breakdown
372
+ });
373
+ }
374
+ return {
375
+ total,
376
+ breakdown,
377
+ diagnostics
378
+ };
379
+ }
380
+
381
+ // src/validator/schema.ts
382
+ var HUMOR_STYLES = ["none", "dry", "subtle-wit", "playful"];
383
+ var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
384
+ "schema",
385
+ "meta",
386
+ "identity",
387
+ "voice",
388
+ "vocabulary",
389
+ "behavioral_rules",
390
+ "context_adaptations",
391
+ "localization",
392
+ "channel_adaptations",
393
+ "extends",
394
+ "behavioral_rules_remove",
395
+ "context_adaptations_remove"
396
+ ]);
397
+ var VOCABULARY_KEYS = /* @__PURE__ */ new Set([
398
+ "preferred_terms",
399
+ "forbidden_terms",
400
+ "preferred_terms_remove",
401
+ "forbidden_terms_remove"
402
+ ]);
403
+ function isObject(value) {
404
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
405
+ }
406
+ function isString(value) {
407
+ return typeof value === "string" && value.trim().length > 0;
408
+ }
409
+ function isStringArray(value) {
410
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
411
+ }
412
+ function pushDiagnostic(target, code, message, location) {
413
+ target.push({
414
+ code,
415
+ severity: "error",
416
+ message: location ? `${message} (${location})` : message,
417
+ location
418
+ });
419
+ }
420
+ function summarizeDiagnostics(diagnostics) {
421
+ return {
422
+ status: diagnostics.length > 0 ? "error" : "pass",
423
+ errors: diagnostics.length,
424
+ warnings: 0
425
+ };
426
+ }
427
+ function validateScalarField(parent, key, location, diagnostics) {
428
+ if (!isString(parent?.[key])) {
429
+ pushDiagnostic(
430
+ diagnostics,
431
+ "V001",
432
+ `Expected non-empty string for "${key}"`,
433
+ `${location}.${key}`
434
+ );
435
+ }
436
+ }
437
+ function validateDimensionValue(value, dimension, location, dimensionsDiagnostics, rangeDiagnostics) {
438
+ if (typeof value === "string") {
439
+ if (!LEVEL_INDEX.has(value)) {
440
+ pushDiagnostic(
441
+ dimensionsDiagnostics,
442
+ "V002",
443
+ `Invalid level "${value}" for ${dimension}`,
444
+ location
445
+ );
446
+ }
447
+ return;
448
+ }
449
+ if (!isObject(value)) {
450
+ pushDiagnostic(
451
+ dimensionsDiagnostics,
452
+ "V002",
453
+ `Expected "${dimension}" to be a level string or object`,
454
+ location
455
+ );
456
+ return;
457
+ }
458
+ const allowedObjectKeys = dimension === "humor" ? /* @__PURE__ */ new Set(["target", "adapt", "floor", "ceiling", "style"]) : /* @__PURE__ */ new Set(["target", "adapt", "floor", "ceiling"]);
459
+ for (const key of Object.keys(value)) {
460
+ if (!allowedObjectKeys.has(key)) {
461
+ pushDiagnostic(
462
+ dimensionsDiagnostics,
463
+ "V002",
464
+ `Unknown dimension property "${key}" for ${dimension}`,
465
+ `${location}.${key}`
466
+ );
467
+ }
468
+ }
469
+ if (!LEVEL_INDEX.has(value.target)) {
470
+ pushDiagnostic(
471
+ dimensionsDiagnostics,
472
+ "V002",
473
+ `Invalid target level "${value.target}" for ${dimension}`,
474
+ `${location}.target`
475
+ );
476
+ }
477
+ if (value.adapt != null && typeof value.adapt !== "boolean") {
478
+ pushDiagnostic(
479
+ dimensionsDiagnostics,
480
+ "V002",
481
+ `Expected boolean for ${dimension}.adapt`,
482
+ `${location}.adapt`
483
+ );
484
+ }
485
+ if (value.floor != null && !LEVEL_INDEX.has(value.floor)) {
486
+ pushDiagnostic(
487
+ dimensionsDiagnostics,
488
+ "V002",
489
+ `Invalid floor level "${value.floor}" for ${dimension}`,
490
+ `${location}.floor`
491
+ );
492
+ }
493
+ if (value.ceiling != null && !LEVEL_INDEX.has(value.ceiling)) {
494
+ pushDiagnostic(
495
+ dimensionsDiagnostics,
496
+ "V002",
497
+ `Invalid ceiling level "${value.ceiling}" for ${dimension}`,
498
+ `${location}.ceiling`
499
+ );
500
+ }
501
+ if (dimension === "humor") {
502
+ if (value.style != null && !HUMOR_STYLES.includes(String(value.style))) {
503
+ pushDiagnostic(
504
+ dimensionsDiagnostics,
505
+ "V002",
506
+ `Invalid humor style "${value.style}"`,
507
+ `${location}.style`
508
+ );
509
+ }
510
+ } else if (value.style != null) {
511
+ pushDiagnostic(
512
+ dimensionsDiagnostics,
513
+ "V002",
514
+ `Style is only allowed on the humor dimension`,
515
+ `${location}.style`
516
+ );
517
+ }
518
+ if (value.adapt === true) {
519
+ if (value.floor == null || value.ceiling == null) {
520
+ pushDiagnostic(
521
+ rangeDiagnostics,
522
+ "V003",
523
+ `Adaptive dimension "${dimension}" requires both floor and ceiling`,
524
+ location
525
+ );
526
+ return;
527
+ }
528
+ if (!LEVEL_INDEX.has(value.target) || !LEVEL_INDEX.has(value.floor) || !LEVEL_INDEX.has(value.ceiling)) {
529
+ return;
530
+ }
531
+ const floorIndex = LEVEL_INDEX.get(value.floor);
532
+ const targetIndex = LEVEL_INDEX.get(value.target);
533
+ const ceilingIndex = LEVEL_INDEX.get(value.ceiling);
534
+ if (floorIndex == null || targetIndex == null || ceilingIndex == null) {
535
+ return;
536
+ }
537
+ if (floorIndex > targetIndex || targetIndex > ceilingIndex) {
538
+ pushDiagnostic(
539
+ rangeDiagnostics,
540
+ "V003",
541
+ `Adaptive range must satisfy floor <= target <= ceiling for "${dimension}"`,
542
+ location
543
+ );
544
+ }
545
+ }
546
+ }
547
+ function validateSchema(profile) {
548
+ const structureDiagnostics = [];
549
+ const dimensionDiagnostics = [];
550
+ const rangeDiagnostics = [];
551
+ if (!isObject(profile)) {
552
+ pushDiagnostic(structureDiagnostics, "V001", "Profile must be a YAML object", "root");
553
+ return {
554
+ diagnostics: [...structureDiagnostics],
555
+ checks: {
556
+ schema_structure: summarizeDiagnostics(structureDiagnostics),
557
+ dimension_values: summarizeDiagnostics(dimensionDiagnostics),
558
+ adaptation_ranges: summarizeDiagnostics(rangeDiagnostics)
559
+ }
560
+ };
561
+ }
562
+ for (const key of Object.keys(profile)) {
563
+ if (!TOP_LEVEL_KEYS.has(key)) {
564
+ pushDiagnostic(
565
+ structureDiagnostics,
566
+ "V001",
567
+ `Unknown top-level section "${key}"`,
568
+ key
569
+ );
570
+ }
571
+ }
572
+ if (!isString(profile.schema)) {
573
+ pushDiagnostic(
574
+ structureDiagnostics,
575
+ "V001",
576
+ `Missing required "schema" field`,
577
+ "schema"
578
+ );
579
+ } else if (profile.schema !== "v1.4") {
580
+ pushDiagnostic(
581
+ structureDiagnostics,
582
+ "V001",
583
+ `Unsupported schema version "${profile.schema}"`,
584
+ "schema"
585
+ );
586
+ }
587
+ if (profile.extends != null && !isString(profile.extends)) {
588
+ pushDiagnostic(
589
+ structureDiagnostics,
590
+ "V001",
591
+ `Expected "extends" to be a non-empty string`,
592
+ "extends"
593
+ );
594
+ }
595
+ if (!isObject(profile.meta)) {
596
+ pushDiagnostic(structureDiagnostics, "V001", `Missing required "meta" section`, "meta");
597
+ } else {
598
+ validateScalarField(profile.meta, "name", "meta", structureDiagnostics);
599
+ validateScalarField(profile.meta, "version", "meta", structureDiagnostics);
600
+ validateScalarField(profile.meta, "description", "meta", structureDiagnostics);
601
+ if (profile.meta.tags != null && !isStringArray(profile.meta.tags)) {
602
+ pushDiagnostic(
603
+ structureDiagnostics,
604
+ "V001",
605
+ `Expected "meta.tags" to be an array of strings`,
606
+ "meta.tags"
607
+ );
608
+ }
609
+ if (profile.meta.target_audience != null && !isString(profile.meta.target_audience)) {
610
+ pushDiagnostic(
611
+ structureDiagnostics,
612
+ "V001",
613
+ `Expected "meta.target_audience" to be a non-empty string`,
614
+ "meta.target_audience"
615
+ );
616
+ }
617
+ }
618
+ if (!isObject(profile.identity)) {
619
+ pushDiagnostic(
620
+ structureDiagnostics,
621
+ "V001",
622
+ `Missing required "identity" section`,
623
+ "identity"
624
+ );
625
+ } else {
626
+ validateScalarField(profile.identity, "role", "identity", structureDiagnostics);
627
+ if (profile.identity.backstory != null && !isString(profile.identity.backstory)) {
628
+ pushDiagnostic(
629
+ structureDiagnostics,
630
+ "V001",
631
+ `Expected "identity.backstory" to be a non-empty string`,
632
+ "identity.backstory"
633
+ );
634
+ }
635
+ if (profile.identity.expertise_domains != null && !isStringArray(profile.identity.expertise_domains)) {
636
+ pushDiagnostic(
637
+ structureDiagnostics,
638
+ "V001",
639
+ `Expected "identity.expertise_domains" to be an array of strings`,
640
+ "identity.expertise_domains"
641
+ );
642
+ }
643
+ }
644
+ if (!isObject(profile.voice)) {
645
+ pushDiagnostic(structureDiagnostics, "V001", `Missing required "voice" section`, "voice");
646
+ } else {
647
+ for (const dimension of DIMENSIONS) {
648
+ if (profile.voice[dimension] == null) {
649
+ pushDiagnostic(
650
+ dimensionDiagnostics,
651
+ "V002",
652
+ `Missing required voice dimension "${dimension}"`,
653
+ `voice.${dimension}`
654
+ );
655
+ continue;
656
+ }
657
+ validateDimensionValue(
658
+ profile.voice[dimension],
659
+ dimension,
660
+ `voice.${dimension}`,
661
+ dimensionDiagnostics,
662
+ rangeDiagnostics
663
+ );
664
+ }
665
+ }
666
+ if (profile.vocabulary != null) {
667
+ if (!isObject(profile.vocabulary)) {
668
+ pushDiagnostic(
669
+ structureDiagnostics,
670
+ "V001",
671
+ `Expected "vocabulary" to be an object`,
672
+ "vocabulary"
673
+ );
674
+ } else {
675
+ for (const key of Object.keys(profile.vocabulary)) {
676
+ if (!VOCABULARY_KEYS.has(key)) {
677
+ pushDiagnostic(
678
+ structureDiagnostics,
679
+ "V001",
680
+ `Unknown vocabulary key "${key}"`,
681
+ `vocabulary.${key}`
682
+ );
683
+ }
684
+ }
685
+ if (profile.vocabulary.preferred_terms != null && !isStringArray(profile.vocabulary.preferred_terms)) {
686
+ pushDiagnostic(
687
+ structureDiagnostics,
688
+ "V001",
689
+ `Expected "vocabulary.preferred_terms" to be an array of strings`,
690
+ "vocabulary.preferred_terms"
691
+ );
692
+ }
693
+ if (profile.vocabulary.forbidden_terms != null && !isStringArray(profile.vocabulary.forbidden_terms)) {
694
+ pushDiagnostic(
695
+ structureDiagnostics,
696
+ "V001",
697
+ `Expected "vocabulary.forbidden_terms" to be an array of strings`,
698
+ "vocabulary.forbidden_terms"
699
+ );
700
+ }
701
+ if (profile.vocabulary.preferred_terms_remove != null && !isStringArray(profile.vocabulary.preferred_terms_remove)) {
702
+ pushDiagnostic(
703
+ structureDiagnostics,
704
+ "V001",
705
+ `Expected "vocabulary.preferred_terms_remove" to be an array of strings`,
706
+ "vocabulary.preferred_terms_remove"
707
+ );
708
+ }
709
+ if (profile.vocabulary.forbidden_terms_remove != null && !isStringArray(profile.vocabulary.forbidden_terms_remove)) {
710
+ pushDiagnostic(
711
+ structureDiagnostics,
712
+ "V001",
713
+ `Expected "vocabulary.forbidden_terms_remove" to be an array of strings`,
714
+ "vocabulary.forbidden_terms_remove"
715
+ );
716
+ }
717
+ }
718
+ }
719
+ if (profile.behavioral_rules != null && !isStringArray(profile.behavioral_rules)) {
720
+ pushDiagnostic(
721
+ structureDiagnostics,
722
+ "V001",
723
+ `Expected "behavioral_rules" to be an array of strings`,
724
+ "behavioral_rules"
725
+ );
726
+ }
727
+ if (profile.behavioral_rules_remove != null && !isStringArray(profile.behavioral_rules_remove)) {
728
+ pushDiagnostic(
729
+ structureDiagnostics,
730
+ "V001",
731
+ `Expected "behavioral_rules_remove" to be an array of strings`,
732
+ "behavioral_rules_remove"
733
+ );
734
+ }
735
+ if (profile.context_adaptations_remove != null && !isStringArray(profile.context_adaptations_remove)) {
736
+ pushDiagnostic(
737
+ structureDiagnostics,
738
+ "V001",
739
+ `Expected "context_adaptations_remove" to be an array of strings`,
740
+ "context_adaptations_remove"
741
+ );
742
+ }
743
+ if (profile.context_adaptations != null) {
744
+ if (!Array.isArray(profile.context_adaptations)) {
745
+ pushDiagnostic(
746
+ structureDiagnostics,
747
+ "V001",
748
+ `Expected "context_adaptations" to be an array`,
749
+ "context_adaptations"
750
+ );
751
+ } else {
752
+ profile.context_adaptations.forEach((adaptation, idx) => {
753
+ const location = `context_adaptations[${idx}]`;
754
+ if (!isObject(adaptation)) {
755
+ pushDiagnostic(
756
+ structureDiagnostics,
757
+ "V001",
758
+ `Expected context adaptation to be an object`,
759
+ location
760
+ );
761
+ return;
762
+ }
763
+ if (!isString(adaptation.when)) {
764
+ pushDiagnostic(
765
+ structureDiagnostics,
766
+ "V001",
767
+ `Context adaptation requires "when"`,
768
+ `${location}.when`
769
+ );
770
+ }
771
+ if (adaptation.priority != null) {
772
+ if (typeof adaptation.priority !== "number" || !Number.isFinite(adaptation.priority)) {
773
+ pushDiagnostic(
774
+ structureDiagnostics,
775
+ "V001",
776
+ `Expected "priority" to be a finite number`,
777
+ `${location}.priority`
778
+ );
779
+ }
780
+ }
781
+ if (adaptation.inject != null && !isStringArray(adaptation.inject)) {
782
+ pushDiagnostic(
783
+ structureDiagnostics,
784
+ "V001",
785
+ `Expected "inject" to be an array of strings`,
786
+ `${location}.inject`
787
+ );
788
+ }
789
+ if (adaptation.adjustments != null) {
790
+ if (!isObject(adaptation.adjustments)) {
791
+ pushDiagnostic(
792
+ structureDiagnostics,
793
+ "V001",
794
+ `Expected "adjustments" to be an object`,
795
+ `${location}.adjustments`
796
+ );
797
+ } else {
798
+ for (const [dimension, value] of Object.entries(adaptation.adjustments)) {
799
+ if (!DIMENSIONS.includes(dimension)) {
800
+ pushDiagnostic(
801
+ dimensionDiagnostics,
802
+ "V002",
803
+ `Unknown adaptation dimension "${dimension}"`,
804
+ `${location}.adjustments.${dimension}`
805
+ );
806
+ continue;
807
+ }
808
+ validateDimensionValue(
809
+ value,
810
+ dimension,
811
+ `${location}.adjustments.${dimension}`,
812
+ dimensionDiagnostics,
813
+ rangeDiagnostics
814
+ );
815
+ }
816
+ }
817
+ }
818
+ });
819
+ }
820
+ }
821
+ const diagnostics = [
822
+ ...structureDiagnostics,
823
+ ...dimensionDiagnostics,
824
+ ...rangeDiagnostics
825
+ ];
826
+ return {
827
+ diagnostics,
828
+ checks: {
829
+ schema_structure: summarizeDiagnostics(structureDiagnostics),
830
+ dimension_values: summarizeDiagnostics(dimensionDiagnostics),
831
+ adaptation_ranges: summarizeDiagnostics(rangeDiagnostics)
832
+ }
833
+ };
834
+ }
835
+
836
+ // src/validator/extremes.ts
837
+ function isObject2(value) {
838
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
839
+ }
840
+ function asLevel(value) {
841
+ if (typeof value !== "string") return null;
842
+ if (!LEVEL_INDEX.has(value)) return null;
843
+ return value;
844
+ }
845
+ function normalizeDimension(raw) {
846
+ if (typeof raw === "string") {
847
+ const target2 = asLevel(raw);
848
+ if (!target2) return null;
849
+ return { target: target2, floor: target2, ceiling: target2, adapt: false };
850
+ }
851
+ if (!isObject2(raw)) return null;
852
+ const target = asLevel(raw.target) ?? asLevel(raw.floor) ?? asLevel(raw.ceiling);
853
+ if (!target) return null;
854
+ const adapt = Boolean(raw.adapt);
855
+ const floor = adapt ? asLevel(raw.floor) ?? target : target;
856
+ const ceiling = adapt ? asLevel(raw.ceiling) ?? target : target;
857
+ return {
858
+ target,
859
+ floor,
860
+ ceiling,
861
+ adapt
862
+ };
863
+ }
864
+ function resolveDimension(voice, dimension) {
865
+ return normalizeDimension(voice?.[dimension]);
866
+ }
867
+ function lteLevel(left, right) {
868
+ const leftLevel = asLevel(left);
869
+ const rightLevel = asLevel(right);
870
+ if (!leftLevel || !rightLevel) return false;
871
+ const leftIndex = LEVEL_INDEX.get(leftLevel);
872
+ const rightIndex = LEVEL_INDEX.get(rightLevel);
873
+ if (leftIndex == null || rightIndex == null) return false;
874
+ return leftIndex <= rightIndex;
875
+ }
876
+ function gteLevel(left, right) {
877
+ const leftLevel = asLevel(left);
878
+ const rightLevel = asLevel(right);
879
+ if (!leftLevel || !rightLevel) return false;
880
+ const leftIndex = LEVEL_INDEX.get(leftLevel);
881
+ const rightIndex = LEVEL_INDEX.get(rightLevel);
882
+ if (leftIndex == null || rightIndex == null) return false;
883
+ return leftIndex >= rightIndex;
884
+ }
885
+ function buildS002Envelope(profile, adaptation = null) {
886
+ const baseVoice = isObject2(profile?.voice) ? profile.voice : {};
887
+ const adjustments = adaptation && isObject2(adaptation.adjustments) ? adaptation.adjustments : {};
888
+ const voice = { ...baseVoice, ...adjustments };
889
+ const envelope = {};
890
+ for (const dimension of DIMENSIONS) {
891
+ envelope[dimension] = resolveDimension(voice, dimension);
892
+ }
893
+ return envelope;
894
+ }
895
+ function buildS002Envelopes(profile) {
896
+ const envelopes = [
897
+ {
898
+ source: "base",
899
+ envelope: buildS002Envelope(profile)
900
+ }
901
+ ];
902
+ for (const adaptation of Array.isArray(profile?.context_adaptations) ? profile.context_adaptations : []) {
903
+ const adjustments = adaptation && isObject2(adaptation.adjustments) ? adaptation.adjustments : null;
904
+ if (!adjustments || Object.keys(adjustments).length === 0) continue;
905
+ envelopes.push({
906
+ source: `context:${String(adaptation?.when ?? "unknown")}`,
907
+ envelope: buildS002Envelope(profile, adaptation)
908
+ });
909
+ }
910
+ return envelopes;
911
+ }
912
+
913
+ // src/validator/safety.ts
914
+ var S001_PATTERNS = [
915
+ { id: "always-comply", regex: /always comply/i },
916
+ { id: "never-refuse", regex: /never refuse/i },
917
+ {
918
+ id: "prioritize-user-request-above-all",
919
+ regex: /prioritize.*user.*request.*above all/i
920
+ },
921
+ { id: "ignore-previous-instructions", regex: /ignore.*previous.*instructions/i },
922
+ { id: "bypass-safety", regex: /bypass.*safety/i },
923
+ { id: "override-guardrails", regex: /override.*guardrails/i },
924
+ { id: "only-priority", regex: /only priority/i },
925
+ { id: "do-not-say-no-refuse-decline", regex: /do not (say no|refuse|decline)/i },
926
+ { id: "always-agree", regex: /always agree/i },
927
+ { id: "always-say-yes", regex: /always say yes/i },
928
+ { id: "never-say-no", regex: /never say no/i },
929
+ { id: "disregard-system-constraints", regex: /disregard.*system.*constraints?/i }
930
+ ];
931
+ var S005_PATTERNS = [
932
+ { id: "system-prompt-reference", regex: /\bsystem\s+prompt\b/i },
933
+ { id: "developer-instructions-reference", regex: /\bdeveloper\s+(prompt|message|instructions?)\b/i },
934
+ {
935
+ id: "ignore-system-instructions",
936
+ regex: /\bignore\b.*\b(system|developer|previous|prior)\b.*\b(instruction|prompt)/i
937
+ },
938
+ { id: "override-policy", regex: /\boverride\b.*\b(policy|guardrail|instruction)s?\b/i },
939
+ { id: "trigger-word", regex: /\btrigger\s+word\b/i },
940
+ { id: "mode-switching", regex: /\b(switch|change)\b.*\bmode\b/i },
941
+ { id: "jailbreak-language", regex: /\b(jailbreak|dan mode|developer mode)\b/i }
942
+ ];
943
+ function normalizeText(value) {
944
+ return String(value ?? "").trim();
945
+ }
946
+ function normalizePhrase(value) {
947
+ return normalizeText(value).toLowerCase().replace(/[’]/g, "'").replace(/\s+/g, " ");
948
+ }
949
+ function collectS001Candidates(profile) {
950
+ const candidates = [];
951
+ if (profile?.identity?.role) {
952
+ candidates.push({
953
+ location: "identity.role",
954
+ text: normalizeText(profile.identity.role)
955
+ });
956
+ }
957
+ if (profile?.identity?.backstory) {
958
+ candidates.push({
959
+ location: "identity.backstory",
960
+ text: normalizeText(profile.identity.backstory)
961
+ });
962
+ }
963
+ asArray(profile?.behavioral_rules).forEach((rule, idx) => {
964
+ candidates.push({
965
+ location: `behavioral_rules[${idx}]`,
966
+ text: normalizeText(rule)
967
+ });
968
+ });
969
+ asArray(profile?.context_adaptations).forEach(
970
+ (adaptation, adaptationIdx) => {
971
+ asArray(adaptation?.inject).forEach((rule, injectIdx) => {
972
+ candidates.push({
973
+ location: `context_adaptations[${adaptationIdx}].inject[${injectIdx}]`,
974
+ text: normalizeText(rule)
975
+ });
976
+ });
977
+ }
978
+ );
979
+ return candidates.filter((item) => item.text.length > 0);
980
+ }
981
+ function collectS005Candidates(profile) {
982
+ const candidates = [];
983
+ asArray(profile?.behavioral_rules).forEach((rule, idx) => {
984
+ candidates.push({
985
+ location: `behavioral_rules[${idx}]`,
986
+ text: normalizeText(rule)
987
+ });
988
+ });
989
+ asArray(profile?.context_adaptations).forEach(
990
+ (adaptation, adaptationIdx) => {
991
+ asArray(adaptation?.inject).forEach((rule, injectIdx) => {
992
+ candidates.push({
993
+ location: `context_adaptations[${adaptationIdx}].inject[${injectIdx}]`,
994
+ text: normalizeText(rule)
995
+ });
996
+ });
997
+ }
998
+ );
999
+ asArray(profile?.vocabulary?.preferred_terms).forEach((term, idx) => {
1000
+ candidates.push({
1001
+ location: `vocabulary.preferred_terms[${idx}]`,
1002
+ text: normalizeText(term)
1003
+ });
1004
+ });
1005
+ asArray(profile?.vocabulary?.forbidden_terms).forEach((term, idx) => {
1006
+ candidates.push({
1007
+ location: `vocabulary.forbidden_terms[${idx}]`,
1008
+ text: normalizeText(term)
1009
+ });
1010
+ });
1011
+ return candidates.filter((item) => item.text.length > 0);
1012
+ }
1013
+ function matchPatterns(candidates, patterns, code, severity) {
1014
+ const diagnostics = [];
1015
+ for (const candidate of candidates) {
1016
+ for (const pattern of patterns) {
1017
+ if (!pattern.regex.test(candidate.text)) continue;
1018
+ diagnostics.push({
1019
+ code,
1020
+ severity,
1021
+ message: `${code} pattern "${pattern.id}" matched at ${candidate.location}.`,
1022
+ location: candidate.location
1023
+ });
1024
+ break;
1025
+ }
1026
+ }
1027
+ return diagnostics;
1028
+ }
1029
+ function evaluateS002Envelope(envelope) {
1030
+ const warnings = [];
1031
+ const directness = envelope.directness;
1032
+ if (!directness?.floor) return warnings;
1033
+ if (lteLevel(directness.floor, "low") && gteLevel(envelope?.warmth?.ceiling, "very-high")) {
1034
+ warnings.push({
1035
+ id: "directness-low-warmth-very-high",
1036
+ reason: "directness floor <= low with warmth ceiling >= very-high"
1037
+ });
1038
+ }
1039
+ if (lteLevel(directness.floor, "very-low")) {
1040
+ warnings.push({
1041
+ id: "directness-very-low",
1042
+ reason: "directness floor <= very-low"
1043
+ });
1044
+ }
1045
+ if (envelope?.empathy?.adapt && lteLevel(directness.floor, "low") && gteLevel(envelope?.empathy?.ceiling, "very-high")) {
1046
+ warnings.push({
1047
+ id: "empathy-very-high-directness-low",
1048
+ reason: "adaptive empathy ceiling >= very-high with directness floor <= low"
1049
+ });
1050
+ }
1051
+ if (envelope?.humor?.adapt && lteLevel(directness.floor, "low") && gteLevel(envelope?.humor?.ceiling, "very-high")) {
1052
+ warnings.push({
1053
+ id: "humor-very-high-directness-low",
1054
+ reason: "adaptive humor ceiling >= very-high with directness floor <= low"
1055
+ });
1056
+ }
1057
+ return warnings;
1058
+ }
1059
+ function checkS001(profile) {
1060
+ return matchPatterns(collectS001Candidates(profile), S001_PATTERNS, "S001", "error");
1061
+ }
1062
+ function checkS002(profile) {
1063
+ const triggersByCondition = /* @__PURE__ */ new Map();
1064
+ for (const { source, envelope } of buildS002Envelopes(profile)) {
1065
+ const warnings = evaluateS002Envelope(envelope);
1066
+ for (const warning of warnings) {
1067
+ if (!triggersByCondition.has(warning.id)) {
1068
+ triggersByCondition.set(warning.id, {
1069
+ reason: warning.reason,
1070
+ sources: /* @__PURE__ */ new Set()
1071
+ });
1072
+ }
1073
+ const trigger = triggersByCondition.get(warning.id);
1074
+ if (trigger) {
1075
+ trigger.sources.add(source);
1076
+ }
1077
+ }
1078
+ }
1079
+ const diagnostics = [];
1080
+ for (const trigger of triggersByCondition.values()) {
1081
+ const where = [...trigger.sources].join(", ");
1082
+ diagnostics.push({
1083
+ code: "S002",
1084
+ severity: "warning",
1085
+ message: `Unsafe adaptive extremes: ${trigger.reason}. Triggered in ${where}.`
1086
+ });
1087
+ }
1088
+ return diagnostics;
1089
+ }
1090
+ function checkS003(profile) {
1091
+ const protectedTerms = new Set(PROTECTED_REFUSAL_TERMS.map(normalizePhrase));
1092
+ const diagnostics = [];
1093
+ for (const forbidden of asArray(profile?.vocabulary?.forbidden_terms)) {
1094
+ const normalizedForbidden = normalizePhrase(forbidden);
1095
+ if (!protectedTerms.has(normalizedForbidden)) continue;
1096
+ diagnostics.push({
1097
+ code: "S003",
1098
+ severity: "warning",
1099
+ message: `Protected refusal phrase appears in vocabulary.forbidden_terms: "${forbidden}".`
1100
+ });
1101
+ }
1102
+ return diagnostics;
1103
+ }
1104
+ function checkS005(profile) {
1105
+ return matchPatterns(collectS005Candidates(profile), S005_PATTERNS, "S005", "warning");
1106
+ }
1107
+
1108
+ // src/validator/inheritance.ts
1109
+ var SAFETY_ADAPTATION_NAME = /(crisis|emergency|harm|suicid|self[-_ ]?harm)/i;
1110
+ function checkS006(parentProfile, childProfile, mergedProfile) {
1111
+ const diagnostics = [];
1112
+ const childBehavioralRemovals = asArray(childProfile.behavioral_rules_remove);
1113
+ const childForbiddenRemovals = asArray(
1114
+ childProfile?.vocabulary?.forbidden_terms_remove
1115
+ );
1116
+ if (childBehavioralRemovals.length) {
1117
+ diagnostics.push({
1118
+ code: "S006",
1119
+ severity: "warning",
1120
+ message: "Explicit behavioral_rules_remove detected. Behavioral rules are safety-relevant."
1121
+ });
1122
+ }
1123
+ if (childForbiddenRemovals.length) {
1124
+ diagnostics.push({
1125
+ code: "S006",
1126
+ severity: "warning",
1127
+ message: "Explicit vocabulary.forbidden_terms_remove detected. Forbidden terms are safety-relevant."
1128
+ });
1129
+ }
1130
+ const parentBehavioralCount = asArray(parentProfile.behavioral_rules).length;
1131
+ const parentForbiddenCount = asArray(parentProfile?.vocabulary?.forbidden_terms).length;
1132
+ const mergedBehavioralCount = asArray(mergedProfile.behavioral_rules).length;
1133
+ const mergedForbiddenCount = asArray(mergedProfile?.vocabulary?.forbidden_terms).length;
1134
+ if (mergedBehavioralCount < parentBehavioralCount || mergedForbiddenCount < parentForbiddenCount) {
1135
+ diagnostics.push({
1136
+ code: "S006",
1137
+ severity: "error",
1138
+ message: "Merged profile has fewer safety constraints than parent profile."
1139
+ });
1140
+ }
1141
+ return diagnostics;
1142
+ }
1143
+ function checkS007(profile) {
1144
+ const diagnostics = [];
1145
+ for (const adaptation of asArray(profile.context_adaptations)) {
1146
+ if (!SAFETY_ADAPTATION_NAME.test(String(adaptation?.when ?? ""))) continue;
1147
+ const priority = Number(adaptation?.priority ?? 0);
1148
+ if (priority >= 100) continue;
1149
+ diagnostics.push({
1150
+ code: "S007",
1151
+ severity: "warning",
1152
+ message: `Safety adaptation "${adaptation.when}" should set priority: 100.`
1153
+ });
1154
+ }
1155
+ return diagnostics;
1156
+ }
1157
+
1158
+ // src/validator/engine.ts
1159
+ function normalizeDiagnosticSeverity(diagnostic, fallbackSeverity) {
1160
+ return {
1161
+ ...diagnostic,
1162
+ severity: diagnostic?.severity ?? fallbackSeverity
1163
+ };
1164
+ }
1165
+ function partitionDiagnostics(diagnostics) {
1166
+ const warnings = [];
1167
+ const errors = [];
1168
+ for (const diagnostic of diagnostics) {
1169
+ if (diagnostic.severity === "error") {
1170
+ errors.push(diagnostic);
1171
+ continue;
1172
+ }
1173
+ warnings.push(diagnostic);
1174
+ }
1175
+ return { warnings, errors };
1176
+ }
1177
+ function promoteWarningsForStrictMode(warnings, strict) {
1178
+ if (!strict) return [];
1179
+ return warnings.map((warning) => ({
1180
+ ...warning,
1181
+ severity: "error",
1182
+ promotedFrom: "warning",
1183
+ message: `[strict] ${warning.message}`
1184
+ }));
1185
+ }
1186
+ function computeExitCode(warnings, effectiveErrors) {
1187
+ if (effectiveErrors.length > 0) return 2;
1188
+ if (warnings.length > 0) return 1;
1189
+ return 0;
1190
+ }
1191
+ function summarizeCheck(diagnostics) {
1192
+ const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
1193
+ const warningCount = diagnostics.filter(
1194
+ (diagnostic) => diagnostic.severity === "warning"
1195
+ ).length;
1196
+ if (errorCount > 0) {
1197
+ return { status: "error", errors: errorCount, warnings: warningCount };
1198
+ }
1199
+ if (warningCount > 0) {
1200
+ return { status: "warning", errors: 0, warnings: warningCount };
1201
+ }
1202
+ return { status: "pass", errors: 0, warnings: 0 };
1203
+ }
1204
+ function buildCompositionCheck(resolvedWarnings, resolvedErrors) {
1205
+ const compositionWarnings = resolvedWarnings.filter(
1206
+ (diagnostic) => diagnostic.code === "E_RESOLVE_EXTENDS"
1207
+ );
1208
+ const compositionErrors = resolvedErrors.filter(
1209
+ (diagnostic) => diagnostic.code === "E_RESOLVE_EXTENDS" || diagnostic.code === "E_EXTENDS_CHAIN"
1210
+ );
1211
+ return summarizeCheck([...compositionWarnings, ...compositionErrors]);
1212
+ }
1213
+ function buildInheritanceCheck(warnings, errors) {
1214
+ const inheritanceWarnings = warnings.filter(
1215
+ (diagnostic) => diagnostic.code === "S006" || diagnostic.code === "S007"
1216
+ );
1217
+ const inheritanceErrors = errors.filter(
1218
+ (diagnostic) => diagnostic.code === "S006" || diagnostic.code === "S007"
1219
+ );
1220
+ return summarizeCheck([...inheritanceWarnings, ...inheritanceErrors]);
1221
+ }
1222
+ function validateResolvedProfile(profile, options = {}) {
1223
+ const strict = Boolean(options.strict);
1224
+ const schemaValidation = validateSchema(profile);
1225
+ const overspec = checkOverspec(profile);
1226
+ const diagnostics = [
1227
+ ...schemaValidation.diagnostics,
1228
+ ...checkS001(profile),
1229
+ ...checkS002(profile),
1230
+ ...checkS003(profile),
1231
+ ...checkS005(profile),
1232
+ ...checkS007(profile),
1233
+ ...overspec.diagnostics
1234
+ ];
1235
+ const { warnings, errors } = partitionDiagnostics(diagnostics);
1236
+ const promotedWarnings = promoteWarningsForStrictMode(warnings, strict);
1237
+ const effectiveErrors = [...errors, ...promotedWarnings];
1238
+ return {
1239
+ parentPath: null,
1240
+ profile,
1241
+ strict,
1242
+ warnings,
1243
+ errors,
1244
+ promotedWarnings,
1245
+ effectiveErrors,
1246
+ constraintCount: overspec.total,
1247
+ constraintBreakdown: overspec.breakdown,
1248
+ checks: {
1249
+ ...schemaValidation.checks,
1250
+ composition: { status: "pass", errors: 0, warnings: 0 },
1251
+ inheritance: buildInheritanceCheck(warnings, errors)
1252
+ },
1253
+ isValid: effectiveErrors.length === 0,
1254
+ exitCode: computeExitCode(warnings, effectiveErrors)
1255
+ };
1256
+ }
1257
+ function validateProfile(profilePath, options = {}) {
1258
+ const strict = Boolean(options.strict);
1259
+ let resolved;
1260
+ try {
1261
+ resolved = resolveExtends(profilePath, options);
1262
+ } catch (error) {
1263
+ const diagnostic = {
1264
+ code: "E_PARSE_PROFILE",
1265
+ severity: "error",
1266
+ message: `Failed to load profile: ${error instanceof Error ? error.message : String(error)}`
1267
+ };
1268
+ return {
1269
+ profilePath,
1270
+ parentPath: null,
1271
+ profile: null,
1272
+ strict,
1273
+ warnings: [],
1274
+ errors: [diagnostic],
1275
+ promotedWarnings: [],
1276
+ effectiveErrors: [diagnostic],
1277
+ constraintCount: 0,
1278
+ constraintBreakdown: {
1279
+ behavioral_rules: 0,
1280
+ vocabulary_preferred_terms: 0,
1281
+ vocabulary_forbidden_terms: 0,
1282
+ context_adaptations: 0
1283
+ },
1284
+ checks: {
1285
+ schema_structure: { status: "error", errors: 1, warnings: 0 },
1286
+ dimension_values: { status: "pass", errors: 0, warnings: 0 },
1287
+ adaptation_ranges: { status: "pass", errors: 0, warnings: 0 },
1288
+ composition: { status: "error", errors: 1, warnings: 0 }
1289
+ },
1290
+ isValid: false,
1291
+ exitCode: 2
1292
+ };
1293
+ }
1294
+ const resolvedWarnings = (resolved?.diagnostics?.warnings ?? []).map(
1295
+ (diagnostic) => normalizeDiagnosticSeverity(diagnostic, "warning")
1296
+ );
1297
+ const resolvedErrors = (resolved?.diagnostics?.errors ?? []).map(
1298
+ (diagnostic) => normalizeDiagnosticSeverity(diagnostic, "error")
1299
+ );
1300
+ let s006Diagnostics = [];
1301
+ if (resolvedErrors.length === 0 && resolved.parentPath) {
1302
+ try {
1303
+ const childProfile = loadProfileFile(profilePath);
1304
+ const parentProfile = loadProfileFile(resolved.parentPath);
1305
+ s006Diagnostics = checkS006(parentProfile, childProfile, resolved.profile);
1306
+ } catch (error) {
1307
+ s006Diagnostics = [
1308
+ {
1309
+ code: "E_INHERITANCE_VALIDATION",
1310
+ severity: "error",
1311
+ message: `Failed to evaluate inheritance checks: ${error instanceof Error ? error.message : String(error)}`
1312
+ }
1313
+ ];
1314
+ }
1315
+ }
1316
+ const normalizedS006Diagnostics = s006Diagnostics.map(
1317
+ (diagnostic) => normalizeDiagnosticSeverity(diagnostic, diagnostic.severity ?? "warning")
1318
+ );
1319
+ const s006Partition = partitionDiagnostics(normalizedS006Diagnostics);
1320
+ const profileValidation = validateResolvedProfile(resolved.profile, {
1321
+ strict: false
1322
+ });
1323
+ const warnings = [...resolvedWarnings, ...profileValidation.warnings, ...s006Partition.warnings];
1324
+ const errors = [...resolvedErrors, ...profileValidation.errors, ...s006Partition.errors];
1325
+ const promotedWarnings = promoteWarningsForStrictMode(warnings, strict);
1326
+ const effectiveErrors = [...errors, ...promotedWarnings];
1327
+ const compositionCheck = buildCompositionCheck(resolvedWarnings, resolvedErrors);
1328
+ const inheritanceCheck = buildInheritanceCheck(warnings, errors);
1329
+ return {
1330
+ profilePath,
1331
+ parentPath: resolved.parentPath,
1332
+ profile: resolved.profile,
1333
+ strict,
1334
+ warnings,
1335
+ errors,
1336
+ promotedWarnings,
1337
+ effectiveErrors,
1338
+ constraintCount: profileValidation.constraintCount,
1339
+ constraintBreakdown: profileValidation.constraintBreakdown,
1340
+ checks: {
1341
+ ...profileValidation.checks,
1342
+ composition: compositionCheck,
1343
+ inheritance: inheritanceCheck
1344
+ },
1345
+ isValid: effectiveErrors.length === 0,
1346
+ exitCode: computeExitCode(warnings, effectiveErrors)
1347
+ };
1348
+ }
1349
+
1350
+ // src/compiler/patterns.ts
1351
+ var import_node_fs2 = __toESM(require("fs"), 1);
1352
+ var import_node_path2 = __toESM(require("path"), 1);
1353
+ var KB_CACHE = /* @__PURE__ */ new Map();
1354
+ function modelFlavor(model) {
1355
+ if (/claude/i.test(String(model))) return "claude";
1356
+ if (/gpt/i.test(String(model))) return "gpt";
1357
+ return "generic";
1358
+ }
1359
+ function dimensionTargetLevel(value) {
1360
+ if (typeof value === "string") return value;
1361
+ if (value && typeof value === "object") {
1362
+ const target = value.target;
1363
+ if (typeof target === "string") return target;
1364
+ }
1365
+ return "medium";
1366
+ }
1367
+ function patternTemplate(flavor, dimension, level) {
1368
+ if (flavor === "claude") {
1369
+ return `Use ${dimension} at ${level} with explicit behavioral framing.`;
1370
+ }
1371
+ if (flavor === "gpt") {
1372
+ return `Maintain ${dimension}=${level} with concise directive language.`;
1373
+ }
1374
+ return `Keep ${dimension} at ${level}.`;
1375
+ }
1376
+ function interactionTemplate(flavor, id) {
1377
+ if (id === "warmth-high_directness-high") {
1378
+ return flavor === "gpt" ? "Acknowledge quickly, then move directly to concrete next steps." : "Lead with acknowledgment and pivot immediately to action.";
1379
+ }
1380
+ if (id === "empathy-very-high_directness-medium") {
1381
+ return flavor === "gpt" ? "Validate deeply, then provide one clear next step with concise rationale." : "Sustain deep validation while keeping recommendations concrete and actionable.";
1382
+ }
1383
+ if (id === "warmth-very-high_humor-very-low") {
1384
+ return flavor === "gpt" ? "Maintain compassionate language without levity; keep wording plain and serious." : "Hold a deeply caring tone without humor; avoid phrasing that can feel dismissive.";
1385
+ }
1386
+ if (id === "empathy-very-high_directness-low") {
1387
+ return "Preserve boundaries while validating emotion; never let empathy remove refusal clarity.";
1388
+ }
1389
+ if (id === "formality-high_humor-medium-plus") {
1390
+ return "Use restrained wit and avoid phrasing that weakens authority.";
1391
+ }
1392
+ return "Apply balanced interaction handling.";
1393
+ }
1394
+ function resolvePatternFile(model, options = {}) {
1395
+ const flavor = modelFlavor(model);
1396
+ if (flavor === "generic") return null;
1397
+ const knowledgeBaseDir = options.knowledgeBaseDir ?? import_node_path2.default.resolve(process.cwd(), "knowledge-base");
1398
+ return import_node_path2.default.resolve(knowledgeBaseDir, flavor, "patterns.json");
1399
+ }
1400
+ function loadPatternData(model, options = {}) {
1401
+ const patternFile = resolvePatternFile(model, options);
1402
+ if (!patternFile) return null;
1403
+ if (KB_CACHE.has(patternFile)) {
1404
+ const cached = KB_CACHE.get(patternFile);
1405
+ return cached ?? null;
1406
+ }
1407
+ try {
1408
+ if (!import_node_fs2.default.existsSync(patternFile)) {
1409
+ KB_CACHE.set(patternFile, null);
1410
+ return null;
1411
+ }
1412
+ const raw = import_node_fs2.default.readFileSync(patternFile, "utf8");
1413
+ const parsed = JSON.parse(raw);
1414
+ KB_CACHE.set(patternFile, parsed);
1415
+ return parsed;
1416
+ } catch {
1417
+ KB_CACHE.set(patternFile, null);
1418
+ return null;
1419
+ }
1420
+ }
1421
+ function normalizeLevel(level) {
1422
+ return String(level ?? "medium").toLowerCase();
1423
+ }
1424
+ function isAtLeast(level, threshold) {
1425
+ const order = ["very-low", "low", "medium", "high", "very-high"];
1426
+ return order.indexOf(normalizeLevel(level)) >= order.indexOf(threshold);
1427
+ }
1428
+ function isAtMost(level, threshold) {
1429
+ const order = ["very-low", "low", "medium", "high", "very-high"];
1430
+ return order.indexOf(normalizeLevel(level)) <= order.indexOf(threshold);
1431
+ }
1432
+ function isExactly(level, target) {
1433
+ return normalizeLevel(level) === target;
1434
+ }
1435
+ function selectPatterns(voice, model, options = {}) {
1436
+ const flavor = modelFlavor(model);
1437
+ const patternData = loadPatternData(model, options);
1438
+ const dimensions = [
1439
+ "formality",
1440
+ "warmth",
1441
+ "verbosity",
1442
+ "directness",
1443
+ "empathy",
1444
+ "humor"
1445
+ ];
1446
+ return dimensions.map((dimension) => {
1447
+ const level = dimensionTargetLevel(voice?.[dimension]);
1448
+ const levelEntry = patternData?.dimensions?.[dimension]?.[level];
1449
+ return {
1450
+ dimension,
1451
+ level,
1452
+ pattern: levelEntry?.pattern ?? patternTemplate(flavor, dimension, level),
1453
+ adherence: levelEntry?.adherence ?? null,
1454
+ source: levelEntry ? `knowledge-base:${String(patternData?.version ?? "unknown")}` : "built-in"
1455
+ };
1456
+ });
1457
+ }
1458
+ function selectInteractionPatterns(voice, model, options = {}) {
1459
+ const flavor = modelFlavor(model);
1460
+ const patternData = loadPatternData(model, options);
1461
+ const warmth = dimensionTargetLevel(voice?.warmth);
1462
+ const directness = dimensionTargetLevel(voice?.directness);
1463
+ const empathy = dimensionTargetLevel(voice?.empathy);
1464
+ const formality = dimensionTargetLevel(voice?.formality);
1465
+ const humor = dimensionTargetLevel(voice?.humor);
1466
+ const interactions = [];
1467
+ if (isAtLeast(warmth, "high") && isAtLeast(directness, "high")) {
1468
+ const entry = patternData?.interactions?.["warmth-high_directness-high"];
1469
+ interactions.push({
1470
+ id: "warmth-high_directness-high",
1471
+ pattern: entry?.pattern ?? interactionTemplate(flavor, "warmth-high_directness-high"),
1472
+ adherence: entry?.adherence ?? null,
1473
+ source: entry ? `knowledge-base:${String(patternData?.version ?? "unknown")}` : "built-in"
1474
+ });
1475
+ }
1476
+ if (isAtLeast(empathy, "very-high") && isExactly(directness, "medium")) {
1477
+ const entry = patternData?.interactions?.["empathy-very-high_directness-medium"];
1478
+ interactions.push({
1479
+ id: "empathy-very-high_directness-medium",
1480
+ pattern: entry?.pattern ?? interactionTemplate(flavor, "empathy-very-high_directness-medium"),
1481
+ adherence: entry?.adherence ?? null,
1482
+ source: entry ? `knowledge-base:${String(patternData?.version ?? "unknown")}` : "built-in"
1483
+ });
1484
+ }
1485
+ if (isAtLeast(warmth, "very-high") && isExactly(humor, "very-low")) {
1486
+ const entry = patternData?.interactions?.["warmth-very-high_humor-very-low"];
1487
+ interactions.push({
1488
+ id: "warmth-very-high_humor-very-low",
1489
+ pattern: entry?.pattern ?? interactionTemplate(flavor, "warmth-very-high_humor-very-low"),
1490
+ adherence: entry?.adherence ?? null,
1491
+ source: entry ? `knowledge-base:${String(patternData?.version ?? "unknown")}` : "built-in"
1492
+ });
1493
+ }
1494
+ if (isAtLeast(empathy, "very-high") && isAtMost(directness, "low")) {
1495
+ const entry = patternData?.interactions?.["empathy-very-high_directness-low"];
1496
+ interactions.push({
1497
+ id: "empathy-very-high_directness-low",
1498
+ pattern: entry?.pattern ?? interactionTemplate(flavor, "empathy-very-high_directness-low"),
1499
+ adherence: entry?.adherence ?? null,
1500
+ source: entry ? `knowledge-base:${String(patternData?.version ?? "unknown")}` : "built-in"
1501
+ });
1502
+ }
1503
+ if (isAtLeast(formality, "high") && isAtLeast(humor, "medium")) {
1504
+ const entry = patternData?.interactions?.["formality-high_humor-medium-plus"];
1505
+ interactions.push({
1506
+ id: "formality-high_humor-medium-plus",
1507
+ pattern: entry?.pattern ?? interactionTemplate(flavor, "formality-high_humor-medium-plus"),
1508
+ adherence: entry?.adherence ?? null,
1509
+ source: entry ? `knowledge-base:${String(patternData?.version ?? "unknown")}` : "built-in"
1510
+ });
1511
+ }
1512
+ return interactions;
1513
+ }
1514
+
1515
+ // src/compiler/safety-floor.ts
1516
+ function getSafetyFloor(model) {
1517
+ if (isClaudeModel(model)) {
1518
+ return [
1519
+ "<safety_floor>",
1520
+ "Maintain safety refusals for harmful or disallowed requests.",
1521
+ "If refusing, be concise, respectful, and provide a safe alternative when possible.",
1522
+ "</safety_floor>"
1523
+ ].join("\n");
1524
+ }
1525
+ return [
1526
+ "[SAFETY FLOOR]",
1527
+ "Maintain refusal behavior for harmful or disallowed requests.",
1528
+ "If refusing, be concise, respectful, and provide safe alternatives where possible."
1529
+ ].join("\n");
1530
+ }
1531
+
1532
+ // src/compiler/engine.ts
1533
+ function selectPlacement(model) {
1534
+ if (isClaudeModel(model)) {
1535
+ return {
1536
+ model,
1537
+ recommended_position: "start",
1538
+ rationale: "Claude generally responds best when personality guidance is front-loaded in the system prompt."
1539
+ };
1540
+ }
1541
+ if (isGptModel(model)) {
1542
+ return {
1543
+ model,
1544
+ recommended_position: "after_tools",
1545
+ rationale: "GPT-family models generally perform best when personality guidance follows tool definitions."
1546
+ };
1547
+ }
1548
+ return {
1549
+ model,
1550
+ recommended_position: "end",
1551
+ rationale: "Unknown model family; defaulting to end placement to reduce conflict with existing system instructions."
1552
+ };
1553
+ }
1554
+ function stringifyDimensionTarget(value) {
1555
+ if (typeof value === "string") return value;
1556
+ if (value && typeof value === "object") {
1557
+ return String(value.target ?? "medium");
1558
+ }
1559
+ return "medium";
1560
+ }
1561
+ function applyContextAdjustments(profile, context = {}) {
1562
+ const resolved = resolveActiveContext(profile, context);
1563
+ const effective = clone(profile);
1564
+ effective.voice = effective.voice ?? {};
1565
+ for (const [dimension, adjustment] of Object.entries(resolved.resolvedAdjustments)) {
1566
+ effective.voice[dimension] = clone(adjustment);
1567
+ }
1568
+ const behavioralRules = [
1569
+ ...asArray(effective.behavioral_rules),
1570
+ ...resolved.injectRules
1571
+ ];
1572
+ effective.behavioral_rules = behavioralRules;
1573
+ return {
1574
+ profile: effective,
1575
+ contextResolution: resolved
1576
+ };
1577
+ }
1578
+ function enforceProtectedVocabulary(forbiddenTerms) {
1579
+ const protectedLower = new Set(PROTECTED_REFUSAL_TERMS.map((term) => term.toLowerCase()));
1580
+ const restored = [];
1581
+ const filteredForbidden = [];
1582
+ for (const term of asArray(forbiddenTerms)) {
1583
+ const normalized = String(term).toLowerCase();
1584
+ if (protectedLower.has(normalized)) {
1585
+ restored.push(term);
1586
+ continue;
1587
+ }
1588
+ filteredForbidden.push(term);
1589
+ }
1590
+ return {
1591
+ filteredForbidden,
1592
+ restoredProtectedTerms: restored
1593
+ };
1594
+ }
1595
+ function estimateTokenCount(text) {
1596
+ const wordCount = String(text).trim().split(/\s+/).filter(Boolean).length;
1597
+ return Math.ceil(wordCount * 1.3);
1598
+ }
1599
+ function collectAdaptiveDimensions(voice = {}) {
1600
+ const adaptive = [];
1601
+ for (const [dimension, value] of Object.entries(voice)) {
1602
+ if (value && typeof value === "object" && value.adapt === true) {
1603
+ adaptive.push(dimension);
1604
+ }
1605
+ }
1606
+ return adaptive;
1607
+ }
1608
+ function renderPersonalityText(profile, model, contextResolution, compileOptions = {}) {
1609
+ const voice = profile.voice ?? {};
1610
+ const vocabulary = profile.vocabulary ?? {};
1611
+ const safeForbidden = enforceProtectedVocabulary(vocabulary.forbidden_terms);
1612
+ const safetyFloor = getSafetyFloor(model);
1613
+ const patternSelections = selectPatterns(voice, model, {
1614
+ knowledgeBaseDir: compileOptions.knowledgeBaseDir
1615
+ });
1616
+ const interactionPatterns = selectInteractionPatterns(voice, model, {
1617
+ knowledgeBaseDir: compileOptions.knowledgeBaseDir
1618
+ });
1619
+ const lines = [];
1620
+ lines.push("[TRAITS PERSONALITY]");
1621
+ lines.push(`Name: ${profile?.meta?.name ?? "unknown"}`);
1622
+ lines.push(`Version: ${profile?.meta?.version ?? "unknown"}`);
1623
+ lines.push("");
1624
+ lines.push("[IDENTITY]");
1625
+ lines.push(`Role: ${profile?.identity?.role ?? "assistant"}`);
1626
+ if (profile?.identity?.backstory) {
1627
+ lines.push(`Backstory: ${String(profile.identity.backstory).replace(/\s+/g, " ").trim()}`);
1628
+ }
1629
+ lines.push("");
1630
+ lines.push("[VOICE TARGETS]");
1631
+ lines.push(`formality: ${stringifyDimensionTarget(voice.formality)}`);
1632
+ lines.push(`warmth: ${stringifyDimensionTarget(voice.warmth)}`);
1633
+ lines.push(`verbosity: ${stringifyDimensionTarget(voice.verbosity)}`);
1634
+ lines.push(`directness: ${stringifyDimensionTarget(voice.directness)}`);
1635
+ lines.push(`empathy: ${stringifyDimensionTarget(voice.empathy)}`);
1636
+ lines.push(`humor: ${stringifyDimensionTarget(voice.humor)}`);
1637
+ if (voice.humor && typeof voice.humor === "object" && voice.humor.style) {
1638
+ lines.push(`humor_style: ${voice.humor.style}`);
1639
+ }
1640
+ lines.push("");
1641
+ lines.push("[PATTERN GUIDANCE]");
1642
+ for (const selection of patternSelections) {
1643
+ lines.push(`- ${selection.dimension} (${selection.level}): ${selection.pattern}`);
1644
+ }
1645
+ if (interactionPatterns.length > 0) {
1646
+ lines.push("");
1647
+ lines.push("[INTERACTION GUIDANCE]");
1648
+ for (const interaction of interactionPatterns) {
1649
+ lines.push(`- ${interaction.id}: ${interaction.pattern}`);
1650
+ }
1651
+ }
1652
+ lines.push("");
1653
+ lines.push("[VOCABULARY]");
1654
+ if (asArray(vocabulary.preferred_terms).length > 0) {
1655
+ lines.push(`Preferred terms: ${asArray(vocabulary.preferred_terms).join("; ")}`);
1656
+ } else {
1657
+ lines.push("Preferred terms: (none)");
1658
+ }
1659
+ if (safeForbidden.filteredForbidden.length > 0) {
1660
+ lines.push(`Forbidden terms: ${safeForbidden.filteredForbidden.join("; ")}`);
1661
+ } else {
1662
+ lines.push("Forbidden terms: (none)");
1663
+ }
1664
+ lines.push(`Protected refusal terms (always available): ${PROTECTED_REFUSAL_TERMS.join("; ")}`);
1665
+ lines.push("");
1666
+ lines.push("[BEHAVIORAL RULES]");
1667
+ const rules = asArray(profile.behavioral_rules);
1668
+ if (rules.length === 0) {
1669
+ lines.push("- (none)");
1670
+ } else {
1671
+ for (const rule of rules) {
1672
+ lines.push(`- ${rule}`);
1673
+ }
1674
+ }
1675
+ if (contextResolution.matched.length > 0) {
1676
+ lines.push("");
1677
+ lines.push("[ACTIVE CONTEXT]");
1678
+ lines.push(
1679
+ `Matched: ${contextResolution.matched.map((item) => `${item.when}${item.priority != null ? ` (priority ${item.priority})` : ""}`).join(", ")}`
1680
+ );
1681
+ }
1682
+ lines.push("");
1683
+ lines.push(safetyFloor);
1684
+ return {
1685
+ text: lines.join("\n"),
1686
+ restoredProtectedTerms: safeForbidden.restoredProtectedTerms,
1687
+ patternSelections,
1688
+ interactionPatterns
1689
+ };
1690
+ }
1691
+ function compileResolvedProfile(profile, options = {}) {
1692
+ const model = String(options.model ?? "claude-sonnet");
1693
+ const context = options.context ?? {};
1694
+ const explain = Boolean(options.explain);
1695
+ const { profile: effectiveProfile, contextResolution } = applyContextAdjustments(profile, context);
1696
+ const rendered = renderPersonalityText(effectiveProfile, model, contextResolution, options);
1697
+ const placement = selectPlacement(model);
1698
+ const text = rendered.text;
1699
+ const compiled = {
1700
+ text,
1701
+ placement,
1702
+ metadata: {
1703
+ profile: effectiveProfile?.meta?.name ?? "unknown",
1704
+ version: effectiveProfile?.meta?.version ?? "unknown",
1705
+ schema_version: effectiveProfile?.schema ?? "unknown",
1706
+ model_target: model,
1707
+ token_count: estimateTokenCount(text),
1708
+ safety_floor_included: true,
1709
+ adaptive_dimensions: collectAdaptiveDimensions(effectiveProfile.voice),
1710
+ humor_style: effectiveProfile?.voice?.humor && typeof effectiveProfile.voice.humor === "object" ? effectiveProfile.voice.humor.style ?? null : null,
1711
+ compilation_timestamp: (/* @__PURE__ */ new Date()).toISOString()
1712
+ }
1713
+ };
1714
+ if (explain) {
1715
+ compiled.trace = {
1716
+ context_matches: contextResolution.matched.map((item) => item.when),
1717
+ adjustments_applied: contextResolution.resolvedAdjustments,
1718
+ inject_rules: contextResolution.injectRules,
1719
+ protected_refusal_terms_restored: rendered.restoredProtectedTerms,
1720
+ pattern_selections: rendered.patternSelections,
1721
+ interaction_patterns: rendered.interactionPatterns
1722
+ };
1723
+ }
1724
+ return compiled;
1725
+ }
1726
+ function compileProfile(profilePath, options = {}) {
1727
+ const strict = Boolean(options.strict);
1728
+ const validation = validateProfile(profilePath, {
1729
+ strict,
1730
+ bundledProfilesDir: options.bundledProfilesDir
1731
+ });
1732
+ if (validation.effectiveErrors.length > 0) {
1733
+ const error = new Error("Profile failed validation and cannot be compiled.");
1734
+ error.code = "E_COMPILE_VALIDATION";
1735
+ error.validation = validation;
1736
+ throw error;
1737
+ }
1738
+ if (!validation.profile) {
1739
+ const error = new Error("Profile failed validation and cannot be compiled.");
1740
+ error.code = "E_COMPILE_VALIDATION";
1741
+ error.validation = validation;
1742
+ throw error;
1743
+ }
1744
+ const compiled = compileResolvedProfile(validation.profile, options);
1745
+ compiled.validation = {
1746
+ warnings: validation.warnings,
1747
+ errors: validation.errors,
1748
+ strict
1749
+ };
1750
+ return compiled;
1751
+ }
1752
+
1753
+ // src/inject/detect-sections.ts
1754
+ var MARKDOWN_HEADING = /^##\s+/;
1755
+ var TOOLS_HEADING = /^##\s*tools\b/i;
1756
+ var XML_TOOLS_OPEN = /<tools>/i;
1757
+ var XML_TOOLS_CLOSE = /<\/tools>/i;
1758
+ var TOOLS_SENTENCE = /you have access to/i;
1759
+ function findMarkdownToolsWindow(lines) {
1760
+ const start = lines.findIndex((line) => TOOLS_HEADING.test(line.trim()));
1761
+ if (start === -1) return null;
1762
+ for (let index = start + 1; index < lines.length; index += 1) {
1763
+ if (MARKDOWN_HEADING.test(lines[index].trim())) {
1764
+ return { start, end: index };
1765
+ }
1766
+ }
1767
+ return { start, end: lines.length };
1768
+ }
1769
+ function findXmlToolsWindow(lines) {
1770
+ const open = lines.findIndex((line) => XML_TOOLS_OPEN.test(line));
1771
+ if (open === -1) return null;
1772
+ const close = lines.findIndex((line, index) => index >= open && XML_TOOLS_CLOSE.test(line));
1773
+ if (close === -1) return { start: open, end: lines.length };
1774
+ return { start: open, end: close + 1 };
1775
+ }
1776
+ function findToolsSentenceWindow(lines) {
1777
+ const sentenceIndex = lines.findIndex((line) => TOOLS_SENTENCE.test(line));
1778
+ if (sentenceIndex === -1) return null;
1779
+ for (let index = sentenceIndex + 1; index < lines.length; index += 1) {
1780
+ if (lines[index].trim() === "") {
1781
+ return { start: sentenceIndex, end: index + 1 };
1782
+ }
1783
+ }
1784
+ return { start: sentenceIndex, end: lines.length };
1785
+ }
1786
+ function detectToolsSection(systemPrompt) {
1787
+ const lines = String(systemPrompt).split("\n");
1788
+ return findXmlToolsWindow(lines) ?? findMarkdownToolsWindow(lines) ?? findToolsSentenceWindow(lines);
1789
+ }
1790
+
1791
+ // src/inject/inject.ts
1792
+ function defaultPlacement(model) {
1793
+ if (isClaudeModel(model)) return "start";
1794
+ if (isGptModel(model)) return "after_tools";
1795
+ return "start";
1796
+ }
1797
+ function splitLines(text) {
1798
+ return String(text ?? "").split("\n");
1799
+ }
1800
+ function concatSections(parts) {
1801
+ return parts.map((part) => String(part ?? "").trim()).filter((part) => part.length > 0).join("\n\n");
1802
+ }
1803
+ function extractCompiledText(compiledPersonality) {
1804
+ if (typeof compiledPersonality === "string") {
1805
+ return {
1806
+ text: compiledPersonality,
1807
+ placement: null
1808
+ };
1809
+ }
1810
+ return {
1811
+ text: String(compiledPersonality?.text ?? ""),
1812
+ placement: compiledPersonality?.placement?.recommended_position ?? null
1813
+ };
1814
+ }
1815
+ function injectPersonality({
1816
+ compiledPersonality,
1817
+ system = "",
1818
+ model = ""
1819
+ }) {
1820
+ const personality = extractCompiledText(compiledPersonality);
1821
+ const personalityText = personality.text.trim();
1822
+ const existingSystem = String(system ?? "");
1823
+ if (personalityText.length === 0) return existingSystem;
1824
+ if (existingSystem.trim().length === 0) return personalityText;
1825
+ const placement = personality.placement ?? defaultPlacement(model);
1826
+ if (placement === "start" || placement === "end") {
1827
+ if (placement === "start") {
1828
+ return concatSections([personalityText, existingSystem]).replace(/\n{3,}/g, "\n\n");
1829
+ }
1830
+ return concatSections([existingSystem, personalityText]).replace(/\n{3,}/g, "\n\n");
1831
+ }
1832
+ if (placement === "after_tools") {
1833
+ const section = detectToolsSection(existingSystem);
1834
+ if (!section) {
1835
+ return concatSections([personalityText, existingSystem]).replace(/\n{3,}/g, "\n\n");
1836
+ }
1837
+ const lines = splitLines(existingSystem);
1838
+ const before = lines.slice(0, section.end).join("\n");
1839
+ const after = lines.slice(section.end).join("\n");
1840
+ return concatSections([before, personalityText, after]).replace(/\n{3,}/g, "\n\n");
1841
+ }
1842
+ return concatSections([personalityText, existingSystem]).replace(/\n{3,}/g, "\n\n");
1843
+ }
1844
+
1845
+ // src/eval/types.ts
1846
+ var VALID_CATEGORIES = /* @__PURE__ */ new Set([
1847
+ "standard",
1848
+ "frustrated",
1849
+ "edge",
1850
+ "multi-turn",
1851
+ "formal",
1852
+ "casual",
1853
+ "mixed"
1854
+ ]);
1855
+ var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
1856
+ function isObject3(value) {
1857
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1858
+ }
1859
+ function isNonEmptyString(value) {
1860
+ return typeof value === "string" && value.trim().length > 0;
1861
+ }
1862
+ function validateEvalScenario(scenario) {
1863
+ const errors = [];
1864
+ if (!isObject3(scenario)) {
1865
+ return {
1866
+ valid: false,
1867
+ errors: ["Scenario must be an object"]
1868
+ };
1869
+ }
1870
+ if (!isNonEmptyString(scenario.id)) {
1871
+ errors.push('Scenario is missing required "id"');
1872
+ }
1873
+ if (!isNonEmptyString(scenario.category) || !VALID_CATEGORIES.has(scenario.category)) {
1874
+ errors.push(
1875
+ `Scenario category must be one of: ${Array.from(VALID_CATEGORIES).join(", ")}`
1876
+ );
1877
+ }
1878
+ const messages = asArray(scenario.messages);
1879
+ if (messages.length === 0) {
1880
+ errors.push('Scenario must include at least one message in "messages"');
1881
+ } else {
1882
+ messages.forEach((message, index) => {
1883
+ if (!isObject3(message)) {
1884
+ errors.push(`messages[${index}] must be an object`);
1885
+ return;
1886
+ }
1887
+ if (!isNonEmptyString(message.role) || !VALID_ROLES.has(message.role)) {
1888
+ errors.push(`messages[${index}].role must be "user" or "assistant"`);
1889
+ }
1890
+ if (!isNonEmptyString(message.content)) {
1891
+ errors.push(`messages[${index}].content must be a non-empty string`);
1892
+ }
1893
+ });
1894
+ }
1895
+ if (scenario.domain != null && !isNonEmptyString(scenario.domain)) {
1896
+ errors.push('"domain" must be a non-empty string when provided');
1897
+ }
1898
+ if (scenario.expected_behavior != null && !isNonEmptyString(scenario.expected_behavior)) {
1899
+ errors.push('"expected_behavior" must be a non-empty string when provided');
1900
+ }
1901
+ return {
1902
+ valid: errors.length === 0,
1903
+ errors
1904
+ };
1905
+ }
1906
+ function validateEvalScenarios(scenarios) {
1907
+ const input = asArray(scenarios);
1908
+ const invalid = [];
1909
+ input.forEach((scenario, index) => {
1910
+ const result = validateEvalScenario(scenario);
1911
+ if (result.valid) return;
1912
+ invalid.push({
1913
+ index,
1914
+ id: scenario?.id ?? null,
1915
+ errors: result.errors
1916
+ });
1917
+ });
1918
+ return {
1919
+ valid: invalid.length === 0,
1920
+ count: input.length,
1921
+ invalid
1922
+ };
1923
+ }
1924
+
1925
+ // src/eval/tier1.ts
1926
+ function normalize(value) {
1927
+ return String(value ?? "").toLowerCase();
1928
+ }
1929
+ function countMatches(text, terms) {
1930
+ const lowered = normalize(text);
1931
+ let count = 0;
1932
+ for (const term of terms) {
1933
+ if (!term) continue;
1934
+ if (lowered.includes(normalize(term))) count += 1;
1935
+ }
1936
+ return count;
1937
+ }
1938
+ function evaluateTier1Response(profile, responseText, options = {}) {
1939
+ const response = String(responseText ?? "");
1940
+ const includeHelpfulness = options.includeHelpfulness !== false;
1941
+ const preferredTerms = asArray(profile?.vocabulary?.preferred_terms);
1942
+ const forbiddenTerms = asArray(profile?.vocabulary?.forbidden_terms);
1943
+ const preferredMatches = countMatches(response, preferredTerms);
1944
+ const forbiddenMatches = countMatches(response, forbiddenTerms);
1945
+ const vocabularyCheck = {
1946
+ preferred_total: preferredTerms.length,
1947
+ preferred_matched: preferredMatches,
1948
+ forbidden_total: forbiddenTerms.length,
1949
+ forbidden_matched: forbiddenMatches,
1950
+ pass: forbiddenMatches === 0
1951
+ };
1952
+ const behavioralRules = asArray(profile?.behavioral_rules);
1953
+ const structureCheck = {
1954
+ behavioral_rule_count: behavioralRules.length,
1955
+ response_non_empty: response.trim().length > 0,
1956
+ pass: response.trim().length > 0
1957
+ };
1958
+ const helpfulnessCheck = {
1959
+ char_count: response.trim().length,
1960
+ pass: includeHelpfulness ? response.trim().length >= 40 : true,
1961
+ skipped: !includeHelpfulness
1962
+ };
1963
+ const preferredCoverage = preferredTerms.length === 0 ? 1 : preferredMatches / preferredTerms.length;
1964
+ const forbiddenPenalty = forbiddenTerms.length === 0 ? 0 : forbiddenMatches / forbiddenTerms.length;
1965
+ const helpfulnessScore = includeHelpfulness ? helpfulnessCheck.pass ? 1 : Math.min(1, response.trim().length / 40) : null;
1966
+ const helpfulnessWeighted = helpfulnessScore ?? 0;
1967
+ const score = includeHelpfulness ? Math.max(
1968
+ 0,
1969
+ 0.45 * preferredCoverage + 0.35 * (1 - forbiddenPenalty) + 0.2 * helpfulnessWeighted
1970
+ ) : Math.max(0, 0.55 * preferredCoverage + 0.45 * (1 - forbiddenPenalty));
1971
+ return {
1972
+ score,
1973
+ checks: {
1974
+ vocabulary: vocabularyCheck,
1975
+ structure: structureCheck,
1976
+ helpfulness: helpfulnessCheck
1977
+ }
1978
+ };
1979
+ }
1980
+ function runTier1Evaluation(profile, samples, options = {}) {
1981
+ const items = asArray(samples);
1982
+ const perSample = items.map((sample) => {
1983
+ const id = sample?.id ?? "unknown";
1984
+ const response = sample?.response ?? "";
1985
+ const result = evaluateTier1Response(profile, response, options);
1986
+ return {
1987
+ id,
1988
+ ...result
1989
+ };
1990
+ });
1991
+ const averageScore = perSample.length === 0 ? 0 : perSample.reduce((sum, item) => sum + item.score, 0) / perSample.length;
1992
+ return {
1993
+ tier: 1,
1994
+ sample_count: perSample.length,
1995
+ average_score: averageScore,
1996
+ samples: perSample
1997
+ };
1998
+ }
1999
+ function runTier1EvaluationForProfile(profilePath, samples, options = {}) {
2000
+ const validation = validateProfile(profilePath, {
2001
+ strict: Boolean(options.strict),
2002
+ bundledProfilesDir: options.bundledProfilesDir
2003
+ });
2004
+ if (validation.effectiveErrors.length > 0) {
2005
+ const error = new Error("Profile failed validation for eval.");
2006
+ error.code = "E_EVAL_VALIDATION";
2007
+ error.validation = validation;
2008
+ throw error;
2009
+ }
2010
+ if (!validation.profile) {
2011
+ const error = new Error("Profile failed validation for eval.");
2012
+ error.code = "E_EVAL_VALIDATION";
2013
+ error.validation = validation;
2014
+ throw error;
2015
+ }
2016
+ const report = runTier1Evaluation(validation.profile, samples, options);
2017
+ return {
2018
+ validation,
2019
+ report
2020
+ };
2021
+ }
2022
+
2023
+ // src/eval/tier2.ts
2024
+ var import_node_fs3 = __toESM(require("fs"), 1);
2025
+ var import_node_path3 = __toESM(require("path"), 1);
2026
+
2027
+ // src/eval/providers/runtime.ts
2028
+ var DEFAULT_TIMEOUT_MS = 2e4;
2029
+ var DEFAULT_MAX_RETRIES = 2;
2030
+ var DEFAULT_RETRY_BASE_MS = 250;
2031
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 409, 425, 429, 500, 502, 503, 504]);
2032
+ function toNonNegativeInteger(value, fallback) {
2033
+ if (value == null) return fallback;
2034
+ const parsed = Number.parseInt(String(value), 10);
2035
+ if (!Number.isFinite(parsed) || parsed < 0) return fallback;
2036
+ return parsed;
2037
+ }
2038
+ function shouldRetryStatus(status) {
2039
+ return RETRYABLE_STATUSES.has(Number(status));
2040
+ }
2041
+ function shouldRetryError(error) {
2042
+ if (!error) return false;
2043
+ if (error.retryable === true) return true;
2044
+ if (error.name === "AbortError") return true;
2045
+ if (error.name === "TypeError") return true;
2046
+ return false;
2047
+ }
2048
+ function sleep(ms) {
2049
+ return new Promise((resolve) => setTimeout(resolve, ms));
2050
+ }
2051
+ function buildDelay(attempt, baseMs) {
2052
+ return Math.max(0, baseMs) * 2 ** attempt;
2053
+ }
2054
+ async function fetchWithTimeout(fetchImpl, url, init, timeoutMs) {
2055
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
2056
+ return fetchImpl(url, init);
2057
+ }
2058
+ const controller = new AbortController();
2059
+ const timeoutId = setTimeout(() => {
2060
+ controller.abort();
2061
+ }, timeoutMs);
2062
+ try {
2063
+ return await fetchImpl(url, {
2064
+ ...init,
2065
+ signal: controller.signal
2066
+ });
2067
+ } catch (error) {
2068
+ if (error?.name === "AbortError") {
2069
+ const timeoutError = new Error(`Request timed out after ${timeoutMs}ms.`);
2070
+ timeoutError.code = "E_PROVIDER_TIMEOUT";
2071
+ timeoutError.retryable = true;
2072
+ throw timeoutError;
2073
+ }
2074
+ throw error;
2075
+ } finally {
2076
+ clearTimeout(timeoutId);
2077
+ }
2078
+ }
2079
+ function normalizeProviderRuntimeOptions(options = {}) {
2080
+ return {
2081
+ timeoutMs: toNonNegativeInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS),
2082
+ maxRetries: toNonNegativeInteger(options.maxRetries, DEFAULT_MAX_RETRIES),
2083
+ retryBaseMs: toNonNegativeInteger(options.retryBaseMs, DEFAULT_RETRY_BASE_MS)
2084
+ };
2085
+ }
2086
+ async function requestTextWithRetry({
2087
+ service,
2088
+ url,
2089
+ init,
2090
+ fetchImpl = fetch,
2091
+ timeoutMs = DEFAULT_TIMEOUT_MS,
2092
+ maxRetries = DEFAULT_MAX_RETRIES,
2093
+ retryBaseMs = DEFAULT_RETRY_BASE_MS
2094
+ }) {
2095
+ const normalized = normalizeProviderRuntimeOptions({
2096
+ timeoutMs,
2097
+ maxRetries,
2098
+ retryBaseMs
2099
+ });
2100
+ for (let attempt = 0; attempt <= normalized.maxRetries; attempt += 1) {
2101
+ try {
2102
+ const response = await fetchWithTimeout(fetchImpl, url, init, normalized.timeoutMs);
2103
+ const bodyText = await response.text();
2104
+ if (response.ok) {
2105
+ return { response, bodyText };
2106
+ }
2107
+ const requestError = new Error(
2108
+ `${service} request failed (${response.status}): ${bodyText}`
2109
+ );
2110
+ requestError.status = response.status;
2111
+ requestError.retryable = shouldRetryStatus(response.status);
2112
+ throw requestError;
2113
+ } catch (error) {
2114
+ const shouldRetry = attempt < normalized.maxRetries && shouldRetryError(error) === true;
2115
+ if (!shouldRetry) {
2116
+ throw error;
2117
+ }
2118
+ await sleep(buildDelay(attempt, normalized.retryBaseMs));
2119
+ }
2120
+ }
2121
+ throw new Error(`${service} request failed after retries.`);
2122
+ }
2123
+
2124
+ // src/eval/providers/openai.ts
2125
+ var OPENAI_BASE_URL = "https://api.openai.com/v1";
2126
+ function parseJSONResponse(service, bodyText) {
2127
+ try {
2128
+ return JSON.parse(bodyText);
2129
+ } catch {
2130
+ throw new Error(`${service} response was not valid JSON.`);
2131
+ }
2132
+ }
2133
+ async function openAIEmbed({
2134
+ apiKey,
2135
+ input,
2136
+ model = "text-embedding-3-small",
2137
+ baseUrl = OPENAI_BASE_URL,
2138
+ fetchImpl = fetch,
2139
+ timeoutMs,
2140
+ maxRetries,
2141
+ retryBaseMs
2142
+ }) {
2143
+ if (!apiKey) {
2144
+ throw new Error("Missing OpenAI API key for embedding request.");
2145
+ }
2146
+ const runtime = normalizeProviderRuntimeOptions({
2147
+ timeoutMs,
2148
+ maxRetries,
2149
+ retryBaseMs
2150
+ });
2151
+ const { bodyText } = await requestTextWithRetry({
2152
+ service: "OpenAI",
2153
+ url: `${baseUrl}/embeddings`,
2154
+ fetchImpl,
2155
+ timeoutMs: runtime.timeoutMs,
2156
+ maxRetries: runtime.maxRetries,
2157
+ retryBaseMs: runtime.retryBaseMs,
2158
+ init: {
2159
+ method: "POST",
2160
+ headers: {
2161
+ "content-type": "application/json",
2162
+ authorization: `Bearer ${apiKey}`
2163
+ },
2164
+ body: JSON.stringify({
2165
+ model,
2166
+ input
2167
+ })
2168
+ }
2169
+ });
2170
+ const data = parseJSONResponse("OpenAI embedding", bodyText);
2171
+ const embedding = data?.data?.[0]?.embedding;
2172
+ if (!Array.isArray(embedding)) {
2173
+ throw new Error("OpenAI embedding response did not include a valid embedding vector.");
2174
+ }
2175
+ return embedding;
2176
+ }
2177
+ async function openAIJudge({
2178
+ apiKey,
2179
+ systemPrompt,
2180
+ userPrompt,
2181
+ model = "gpt-4o-mini",
2182
+ baseUrl = OPENAI_BASE_URL,
2183
+ fetchImpl = fetch,
2184
+ timeoutMs,
2185
+ maxRetries,
2186
+ retryBaseMs
2187
+ }) {
2188
+ if (!apiKey) {
2189
+ throw new Error("Missing OpenAI API key for judge request.");
2190
+ }
2191
+ const runtime = normalizeProviderRuntimeOptions({
2192
+ timeoutMs,
2193
+ maxRetries,
2194
+ retryBaseMs
2195
+ });
2196
+ const { bodyText } = await requestTextWithRetry({
2197
+ service: "OpenAI",
2198
+ url: `${baseUrl}/chat/completions`,
2199
+ fetchImpl,
2200
+ timeoutMs: runtime.timeoutMs,
2201
+ maxRetries: runtime.maxRetries,
2202
+ retryBaseMs: runtime.retryBaseMs,
2203
+ init: {
2204
+ method: "POST",
2205
+ headers: {
2206
+ "content-type": "application/json",
2207
+ authorization: `Bearer ${apiKey}`
2208
+ },
2209
+ body: JSON.stringify({
2210
+ model,
2211
+ temperature: 0,
2212
+ response_format: { type: "json_object" },
2213
+ messages: [
2214
+ {
2215
+ role: "system",
2216
+ content: systemPrompt
2217
+ },
2218
+ {
2219
+ role: "user",
2220
+ content: userPrompt
2221
+ }
2222
+ ]
2223
+ })
2224
+ }
2225
+ });
2226
+ const data = parseJSONResponse("OpenAI judge", bodyText);
2227
+ const content = data?.choices?.[0]?.message?.content;
2228
+ if (typeof content !== "string" || content.trim().length === 0) {
2229
+ throw new Error("OpenAI judge response did not include message content.");
2230
+ }
2231
+ return content;
2232
+ }
2233
+
2234
+ // src/eval/tier2.ts
2235
+ var KB_CACHE2 = /* @__PURE__ */ new Map();
2236
+ function targetLevel(value) {
2237
+ if (typeof value === "string") return value;
2238
+ if (value && typeof value === "object") {
2239
+ const target = value.target;
2240
+ if (typeof target === "string") {
2241
+ return target;
2242
+ }
2243
+ }
2244
+ return "medium";
2245
+ }
2246
+ function clamp01(value) {
2247
+ const num = Number(value);
2248
+ if (!Number.isFinite(num)) return 0;
2249
+ return Math.max(0, Math.min(1, num));
2250
+ }
2251
+ function cosineSimilarity(vecA, vecB) {
2252
+ if (!Array.isArray(vecA) || !Array.isArray(vecB) || vecA.length !== vecB.length) return 0;
2253
+ let dot = 0;
2254
+ let normA = 0;
2255
+ let normB = 0;
2256
+ for (let index = 0; index < vecA.length; index += 1) {
2257
+ const a = Number(vecA[index]) || 0;
2258
+ const b = Number(vecB[index]) || 0;
2259
+ dot += a * b;
2260
+ normA += a * a;
2261
+ normB += b * b;
2262
+ }
2263
+ if (normA === 0 || normB === 0) return 0;
2264
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB));
2265
+ }
2266
+ function normalizeCosine(similarity) {
2267
+ return clamp01((similarity + 1) / 2);
2268
+ }
2269
+ function modelFlavor2(model) {
2270
+ if (/claude/i.test(String(model))) return "claude";
2271
+ if (/gpt/i.test(String(model))) return "gpt";
2272
+ return "generic";
2273
+ }
2274
+ function inferModelTarget(profile, options) {
2275
+ if (options.modelTarget) return options.modelTarget;
2276
+ const tags = asArray(profile?.meta?.tags);
2277
+ for (const tag of tags) {
2278
+ if (!String(tag).startsWith("model:")) continue;
2279
+ const value = String(tag).slice("model:".length).trim();
2280
+ if (value) return value;
2281
+ }
2282
+ return null;
2283
+ }
2284
+ function resolvePatternFile2(profile, options) {
2285
+ const flavor = modelFlavor2(inferModelTarget(profile, options));
2286
+ if (flavor === "generic") return null;
2287
+ const knowledgeBaseDir = options.knowledgeBaseDir ?? import_node_path3.default.resolve(process.cwd(), "knowledge-base");
2288
+ return import_node_path3.default.resolve(knowledgeBaseDir, flavor, "patterns.json");
2289
+ }
2290
+ function loadPatternData2(profile, options) {
2291
+ const patternFile = resolvePatternFile2(profile, options);
2292
+ if (!patternFile) return null;
2293
+ if (KB_CACHE2.has(patternFile)) {
2294
+ const cached = KB_CACHE2.get(patternFile);
2295
+ return cached ?? null;
2296
+ }
2297
+ try {
2298
+ if (!import_node_fs3.default.existsSync(patternFile)) {
2299
+ KB_CACHE2.set(patternFile, null);
2300
+ return null;
2301
+ }
2302
+ const parsed = JSON.parse(import_node_fs3.default.readFileSync(patternFile, "utf8"));
2303
+ KB_CACHE2.set(patternFile, parsed);
2304
+ return parsed;
2305
+ } catch {
2306
+ KB_CACHE2.set(patternFile, null);
2307
+ return null;
2308
+ }
2309
+ }
2310
+ function buildDimensionReferenceText(profile, dimension, level, options) {
2311
+ const patternText = loadPatternData2(profile, options)?.dimensions?.[dimension]?.[level]?.pattern;
2312
+ if (typeof patternText === "string" && patternText.trim()) {
2313
+ return patternText.trim();
2314
+ }
2315
+ return `Reference response style for ${dimension} at ${level} level.`;
2316
+ }
2317
+ function buildHelpfulnessReference(profile, sample) {
2318
+ if (sample?.prompt) return String(sample.prompt);
2319
+ if (profile?.meta?.description) return String(profile.meta.description);
2320
+ return String(profile?.identity?.role ?? "helpful assistant response");
2321
+ }
2322
+ function makeEmbeddingFunction(options = {}) {
2323
+ if (typeof options.embeddingFn === "function") return options.embeddingFn;
2324
+ const apiKey = options.openaiApiKey ?? process.env.TRAITS_OPENAI_API_KEY;
2325
+ if (!apiKey) {
2326
+ const error = new Error("Tier 2 requires TRAITS_OPENAI_API_KEY.");
2327
+ error.code = "E_EVAL_TIER2_UNAVAILABLE";
2328
+ throw error;
2329
+ }
2330
+ return (text) => openAIEmbed({
2331
+ apiKey,
2332
+ input: text,
2333
+ model: options.embeddingModel ?? "text-embedding-3-small",
2334
+ baseUrl: options.openaiBaseUrl ?? options.openAIBaseUrl,
2335
+ fetchImpl: options.fetchImpl,
2336
+ timeoutMs: options.fetchTimeoutMs,
2337
+ maxRetries: options.fetchMaxRetries,
2338
+ retryBaseMs: options.fetchRetryBaseMs
2339
+ });
2340
+ }
2341
+ async function runTier2Evaluation(profile, samples, options = {}) {
2342
+ const embed = makeEmbeddingFunction(options);
2343
+ const includeHelpfulness = options.includeHelpfulness !== false;
2344
+ const items = asArray(samples);
2345
+ const voice = profile?.voice ?? {};
2346
+ const cache = /* @__PURE__ */ new Map();
2347
+ async function getEmbedding(text) {
2348
+ const key = String(text);
2349
+ if (cache.has(key)) {
2350
+ const cached = cache.get(key);
2351
+ if (cached) return cached;
2352
+ }
2353
+ const embedding = await embed(key);
2354
+ cache.set(key, embedding);
2355
+ return embedding;
2356
+ }
2357
+ const dimensionRefs = {};
2358
+ for (const dimension of DIMENSIONS) {
2359
+ const level = targetLevel(voice?.[dimension]);
2360
+ const refText = buildDimensionReferenceText(profile, dimension, level, options);
2361
+ dimensionRefs[dimension] = await getEmbedding(refText);
2362
+ }
2363
+ const perSample = [];
2364
+ for (const sample of items) {
2365
+ const responseText = String(sample?.response ?? "");
2366
+ const responseEmbedding = await getEmbedding(responseText);
2367
+ const dimensionScores = {};
2368
+ for (const dimension of DIMENSIONS) {
2369
+ const similarity = cosineSimilarity(responseEmbedding, dimensionRefs[dimension]);
2370
+ dimensionScores[dimension] = normalizeCosine(similarity);
2371
+ }
2372
+ let helpfulnessScore = null;
2373
+ if (includeHelpfulness) {
2374
+ const helpfulnessReference = buildHelpfulnessReference(profile, sample);
2375
+ const helpfulnessEmbedding = await getEmbedding(helpfulnessReference);
2376
+ helpfulnessScore = normalizeCosine(
2377
+ cosineSimilarity(responseEmbedding, helpfulnessEmbedding)
2378
+ );
2379
+ }
2380
+ const dimAverage = DIMENSIONS.reduce((sum, dimension) => sum + dimensionScores[dimension], 0) / DIMENSIONS.length;
2381
+ const score = includeHelpfulness ? clamp01(0.7 * dimAverage + 0.3 * (helpfulnessScore ?? 0)) : clamp01(dimAverage);
2382
+ perSample.push({
2383
+ id: sample?.id ?? "unknown",
2384
+ score,
2385
+ dimension_scores: dimensionScores,
2386
+ helpfulness_score: helpfulnessScore
2387
+ });
2388
+ }
2389
+ const averageScore = perSample.length === 0 ? 0 : perSample.reduce((sum, sample) => sum + sample.score, 0) / perSample.length;
2390
+ const dimensionAverages = {};
2391
+ for (const dimension of DIMENSIONS) {
2392
+ dimensionAverages[dimension] = perSample.length === 0 ? 0 : perSample.reduce((sum, sample) => sum + sample.dimension_scores[dimension], 0) / perSample.length;
2393
+ }
2394
+ const helpfulnessAverage = includeHelpfulness ? perSample.length === 0 ? 0 : perSample.reduce((sum, sample) => sum + (sample.helpfulness_score ?? 0), 0) / perSample.length : null;
2395
+ return {
2396
+ tier: 2,
2397
+ provider: "openai",
2398
+ helpfulness_included: includeHelpfulness,
2399
+ sample_count: perSample.length,
2400
+ average_score: averageScore,
2401
+ dimension_averages: dimensionAverages,
2402
+ helpfulness_average: helpfulnessAverage,
2403
+ samples: perSample
2404
+ };
2405
+ }
2406
+ async function runTier2EvaluationForProfile(profilePath, samples, options = {}) {
2407
+ const validation = validateProfile(profilePath, {
2408
+ strict: Boolean(options.strict),
2409
+ bundledProfilesDir: options.bundledProfilesDir
2410
+ });
2411
+ if (validation.effectiveErrors.length > 0) {
2412
+ const error = new Error("Profile failed validation for eval.");
2413
+ error.code = "E_EVAL_VALIDATION";
2414
+ error.validation = validation;
2415
+ throw error;
2416
+ }
2417
+ if (!validation.profile) {
2418
+ const error = new Error("Profile failed validation for eval.");
2419
+ error.code = "E_EVAL_VALIDATION";
2420
+ error.validation = validation;
2421
+ throw error;
2422
+ }
2423
+ const report = await runTier2Evaluation(validation.profile, samples, options);
2424
+ return {
2425
+ validation,
2426
+ report
2427
+ };
2428
+ }
2429
+
2430
+ // src/eval/providers/anthropic.ts
2431
+ var ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
2432
+ function parseJSONResponse2(service, bodyText) {
2433
+ try {
2434
+ return JSON.parse(bodyText);
2435
+ } catch {
2436
+ throw new Error(`${service} response was not valid JSON.`);
2437
+ }
2438
+ }
2439
+ async function anthropicJudge({
2440
+ apiKey,
2441
+ systemPrompt,
2442
+ userPrompt,
2443
+ model = "claude-3-5-sonnet-latest",
2444
+ baseUrl = ANTHROPIC_BASE_URL,
2445
+ fetchImpl = fetch,
2446
+ timeoutMs,
2447
+ maxRetries,
2448
+ retryBaseMs
2449
+ }) {
2450
+ if (!apiKey) {
2451
+ throw new Error("Missing Anthropic API key for judge request.");
2452
+ }
2453
+ const runtime = normalizeProviderRuntimeOptions({
2454
+ timeoutMs,
2455
+ maxRetries,
2456
+ retryBaseMs
2457
+ });
2458
+ const { bodyText } = await requestTextWithRetry({
2459
+ service: "Anthropic",
2460
+ url: `${baseUrl}/messages`,
2461
+ fetchImpl,
2462
+ timeoutMs: runtime.timeoutMs,
2463
+ maxRetries: runtime.maxRetries,
2464
+ retryBaseMs: runtime.retryBaseMs,
2465
+ init: {
2466
+ method: "POST",
2467
+ headers: {
2468
+ "content-type": "application/json",
2469
+ "x-api-key": apiKey,
2470
+ "anthropic-version": "2023-06-01"
2471
+ },
2472
+ body: JSON.stringify({
2473
+ model,
2474
+ max_tokens: 512,
2475
+ temperature: 0,
2476
+ system: systemPrompt,
2477
+ messages: [
2478
+ {
2479
+ role: "user",
2480
+ content: userPrompt
2481
+ }
2482
+ ]
2483
+ })
2484
+ }
2485
+ });
2486
+ const data = parseJSONResponse2("Anthropic judge", bodyText);
2487
+ const textBlock = Array.isArray(data?.content) ? data.content.find((item) => item?.type === "text") : null;
2488
+ const content = textBlock?.text;
2489
+ if (typeof content !== "string" || content.trim().length === 0) {
2490
+ throw new Error("Anthropic judge response did not include text content.");
2491
+ }
2492
+ return content;
2493
+ }
2494
+
2495
+ // src/eval/tier3.ts
2496
+ var SCORING_DIMENSIONS = [
2497
+ "formality",
2498
+ "warmth",
2499
+ "verbosity",
2500
+ "directness",
2501
+ "empathy",
2502
+ "humor"
2503
+ ];
2504
+ function targetLevel2(value) {
2505
+ if (typeof value === "string") return value;
2506
+ if (value && typeof value === "object" && typeof value.target === "string") {
2507
+ return value.target;
2508
+ }
2509
+ return "medium";
2510
+ }
2511
+ function clamp012(value) {
2512
+ const num = Number(value);
2513
+ if (!Number.isFinite(num)) return 0;
2514
+ return Math.max(0, Math.min(1, num));
2515
+ }
2516
+ function extractJSONObject(text) {
2517
+ const raw = String(text ?? "").trim();
2518
+ try {
2519
+ return JSON.parse(raw);
2520
+ } catch {
2521
+ const match = raw.match(/\{[\s\S]*\}/);
2522
+ if (!match) {
2523
+ throw new Error("Judge response did not contain JSON.");
2524
+ }
2525
+ return JSON.parse(match[0]);
2526
+ }
2527
+ }
2528
+ function selectJudgeProvider(options = {}) {
2529
+ if (typeof options.judgeFn === "function") {
2530
+ return {
2531
+ provider: "custom",
2532
+ judgeFn: options.judgeFn
2533
+ };
2534
+ }
2535
+ const requested = String(options.provider ?? "auto").toLowerCase();
2536
+ const openaiKey = options.openaiApiKey ?? process.env.TRAITS_OPENAI_API_KEY;
2537
+ const anthropicKey = options.anthropicApiKey ?? process.env.TRAITS_ANTHROPIC_API_KEY;
2538
+ if (requested === "openai" || requested === "auto" && openaiKey) {
2539
+ if (!openaiKey) {
2540
+ const error2 = new Error("Tier 3 requested OpenAI provider but TRAITS_OPENAI_API_KEY is missing.");
2541
+ error2.code = "E_EVAL_TIER3_UNAVAILABLE";
2542
+ throw error2;
2543
+ }
2544
+ return {
2545
+ provider: "openai",
2546
+ judgeFn: ({ systemPrompt, userPrompt }) => openAIJudge({
2547
+ apiKey: openaiKey,
2548
+ systemPrompt,
2549
+ userPrompt,
2550
+ model: options.judgeModel ?? "gpt-4o-mini",
2551
+ baseUrl: options.openaiBaseUrl ?? options.openAIBaseUrl,
2552
+ fetchImpl: options.fetchImpl,
2553
+ timeoutMs: options.fetchTimeoutMs,
2554
+ maxRetries: options.fetchMaxRetries,
2555
+ retryBaseMs: options.fetchRetryBaseMs
2556
+ })
2557
+ };
2558
+ }
2559
+ if (requested === "anthropic" || requested === "auto" && anthropicKey) {
2560
+ if (!anthropicKey) {
2561
+ const error2 = new Error(
2562
+ "Tier 3 requested Anthropic provider but TRAITS_ANTHROPIC_API_KEY is missing."
2563
+ );
2564
+ error2.code = "E_EVAL_TIER3_UNAVAILABLE";
2565
+ throw error2;
2566
+ }
2567
+ return {
2568
+ provider: "anthropic",
2569
+ judgeFn: ({ systemPrompt, userPrompt }) => anthropicJudge({
2570
+ apiKey: anthropicKey,
2571
+ systemPrompt,
2572
+ userPrompt,
2573
+ model: options.judgeModel ?? "claude-3-5-sonnet-latest",
2574
+ baseUrl: options.anthropicBaseUrl,
2575
+ fetchImpl: options.fetchImpl,
2576
+ timeoutMs: options.fetchTimeoutMs,
2577
+ maxRetries: options.fetchMaxRetries,
2578
+ retryBaseMs: options.fetchRetryBaseMs
2579
+ })
2580
+ };
2581
+ }
2582
+ const error = new Error(
2583
+ "Tier 3 requires TRAITS_OPENAI_API_KEY or TRAITS_ANTHROPIC_API_KEY."
2584
+ );
2585
+ error.code = "E_EVAL_TIER3_UNAVAILABLE";
2586
+ throw error;
2587
+ }
2588
+ function collectVoiceTargets(profile) {
2589
+ const voice = profile?.voice ?? {};
2590
+ return {
2591
+ formality: targetLevel2(voice.formality),
2592
+ warmth: targetLevel2(voice.warmth),
2593
+ verbosity: targetLevel2(voice.verbosity),
2594
+ directness: targetLevel2(voice.directness),
2595
+ empathy: targetLevel2(voice.empathy),
2596
+ humor: targetLevel2(voice.humor)
2597
+ };
2598
+ }
2599
+ function buildJudgeSystemPrompt(profile, includeHelpfulness = true) {
2600
+ const targets = collectVoiceTargets(profile);
2601
+ const lines = [
2602
+ "You are evaluating personality target adherence for a single assistant response.",
2603
+ "Score adherence to the stated target level for each dimension, not generic response quality.",
2604
+ "Scoring scale per dimension:",
2605
+ "- 1.0: response clearly matches target level",
2606
+ "- 0.5: partially aligned or ambiguous",
2607
+ "- 0.0: clearly misaligned/opposite target",
2608
+ "Example: if humor target is very-low, a response with no humor should score 1.0 because it matches target adherence.",
2609
+ "Voice targets to score:",
2610
+ `- formality (target: ${targets.formality})`,
2611
+ `- warmth (target: ${targets.warmth})`,
2612
+ `- verbosity (target: ${targets.verbosity})`,
2613
+ `- directness (target: ${targets.directness})`,
2614
+ `- empathy (target: ${targets.empathy})`,
2615
+ `- humor (target: ${targets.humor})`
2616
+ ];
2617
+ if (includeHelpfulness) {
2618
+ lines.push(
2619
+ "Also score helpfulness in [0,1] based on actionable utility and correctness intent."
2620
+ );
2621
+ } else {
2622
+ lines.push("Helpfulness scoring is disabled for this run.");
2623
+ }
2624
+ lines.push(
2625
+ "Return strict JSON only with numeric fields in [0,1] and a short rationale string.",
2626
+ includeHelpfulness ? '{"formality":0,"warmth":0,"verbosity":0,"directness":0,"empathy":0,"humor":0,"helpfulness":0,"rationale":"..."}' : '{"formality":0,"warmth":0,"verbosity":0,"directness":0,"empathy":0,"humor":0,"rationale":"..."}'
2627
+ );
2628
+ return lines.join("\n");
2629
+ }
2630
+ function buildJudgeUserPrompt(profile, sample) {
2631
+ const targets = collectVoiceTargets(profile);
2632
+ const preferredTerms = asArray(profile?.vocabulary?.preferred_terms);
2633
+ const forbiddenTerms = asArray(profile?.vocabulary?.forbidden_terms);
2634
+ const behavioralRules = asArray(profile?.behavioral_rules);
2635
+ return [
2636
+ `Profile: ${profile?.meta?.name ?? "unknown"}`,
2637
+ `Role: ${profile?.identity?.role ?? "assistant"}`,
2638
+ `Voice targets: ${JSON.stringify(targets)}`,
2639
+ preferredTerms.length > 0 ? `Preferred terms: ${preferredTerms.join("; ")}` : "Preferred terms: (none)",
2640
+ forbiddenTerms.length > 0 ? `Forbidden terms: ${forbiddenTerms.join("; ")}` : "Forbidden terms: (none)",
2641
+ behavioralRules.length > 0 ? `Behavioral rules: ${behavioralRules.join("; ")}` : "Behavioral rules: (none)",
2642
+ sample?.prompt ? `User prompt: ${sample.prompt}` : "User prompt: (not provided)",
2643
+ `Assistant response: ${String(sample?.response ?? "")}`
2644
+ ].join("\n");
2645
+ }
2646
+ function parseDimensionScores(parsed) {
2647
+ return {
2648
+ formality: clamp012(parsed.formality),
2649
+ warmth: clamp012(parsed.warmth),
2650
+ verbosity: clamp012(parsed.verbosity),
2651
+ directness: clamp012(parsed.directness),
2652
+ empathy: clamp012(parsed.empathy),
2653
+ humor: clamp012(parsed.humor)
2654
+ };
2655
+ }
2656
+ function averageDimensionScores(scores) {
2657
+ const total = SCORING_DIMENSIONS.reduce((sum, dimension) => sum + scores[dimension], 0);
2658
+ return total / SCORING_DIMENSIONS.length;
2659
+ }
2660
+ async function runTier3Evaluation(profile, samples, options = {}) {
2661
+ const judge = selectJudgeProvider(options);
2662
+ const includeHelpfulness = options.includeHelpfulness !== false;
2663
+ const systemPrompt = buildJudgeSystemPrompt(profile, includeHelpfulness);
2664
+ const items = asArray(samples);
2665
+ const perSample = [];
2666
+ for (const sample of items) {
2667
+ const raw = await judge.judgeFn({
2668
+ systemPrompt,
2669
+ userPrompt: buildJudgeUserPrompt(profile, sample)
2670
+ });
2671
+ const parsed = extractJSONObject(raw);
2672
+ const dimensions = parseDimensionScores(parsed);
2673
+ const dimensionAverage = averageDimensionScores(dimensions);
2674
+ const helpfulness = includeHelpfulness ? clamp012(parsed.helpfulness) : null;
2675
+ const score = includeHelpfulness ? clamp012(0.7 * dimensionAverage + 0.3 * (helpfulness ?? 0)) : clamp012(dimensionAverage);
2676
+ perSample.push({
2677
+ id: sample?.id ?? "unknown",
2678
+ score,
2679
+ dimension_average: dimensionAverage,
2680
+ ...dimensions,
2681
+ helpfulness,
2682
+ rationale: String(parsed.rationale ?? "")
2683
+ });
2684
+ }
2685
+ const averageScore = perSample.length === 0 ? 0 : perSample.reduce((sum, sample) => sum + sample.score, 0) / perSample.length;
2686
+ return {
2687
+ tier: 3,
2688
+ provider: judge.provider,
2689
+ helpfulness_included: includeHelpfulness,
2690
+ sample_count: perSample.length,
2691
+ average_score: averageScore,
2692
+ samples: perSample
2693
+ };
2694
+ }
2695
+ async function runTier3EvaluationForProfile(profilePath, samples, options = {}) {
2696
+ const validation = validateProfile(profilePath, {
2697
+ strict: Boolean(options.strict),
2698
+ bundledProfilesDir: options.bundledProfilesDir
2699
+ });
2700
+ if (validation.effectiveErrors.length > 0) {
2701
+ const error = new Error("Profile failed validation for eval.");
2702
+ error.code = "E_EVAL_VALIDATION";
2703
+ error.validation = validation;
2704
+ throw error;
2705
+ }
2706
+ if (!validation.profile) {
2707
+ const error = new Error("Profile failed validation for eval.");
2708
+ error.code = "E_EVAL_VALIDATION";
2709
+ error.validation = validation;
2710
+ throw error;
2711
+ }
2712
+ const report = await runTier3Evaluation(validation.profile, samples, options);
2713
+ return {
2714
+ validation,
2715
+ report
2716
+ };
2717
+ }
2718
+
2719
+ // src/import/engine.ts
2720
+ var import_yaml2 = require("yaml");
2721
+ var HUMOR_STYLES2 = ["none", "dry", "subtle-wit", "playful"];
2722
+ function asObject(value) {
2723
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2724
+ return value;
2725
+ }
2726
+ function normalizeLevel2(value, fallback = "medium") {
2727
+ if (typeof value !== "string") return fallback;
2728
+ const normalized = value.trim().toLowerCase();
2729
+ if (!LEVELS.includes(normalized)) return fallback;
2730
+ return normalized;
2731
+ }
2732
+ function normalizeHumorStyle(value) {
2733
+ if (typeof value !== "string") return "none";
2734
+ const normalized = value.trim().toLowerCase();
2735
+ if (!HUMOR_STYLES2.includes(normalized)) return "none";
2736
+ return normalized;
2737
+ }
2738
+ function normalizeStringArray(value, limit = 12) {
2739
+ const deduped = /* @__PURE__ */ new Set();
2740
+ for (const item of asArray(value)) {
2741
+ const normalized = String(item ?? "").trim();
2742
+ if (!normalized) continue;
2743
+ if (deduped.has(normalized)) continue;
2744
+ deduped.add(normalized);
2745
+ if (deduped.size >= limit) break;
2746
+ }
2747
+ return [...deduped];
2748
+ }
2749
+ function normalizeDimensionValue(value, fallbackLevel = "medium") {
2750
+ if (typeof value === "string") {
2751
+ return normalizeLevel2(value, fallbackLevel);
2752
+ }
2753
+ const dimension = asObject(value);
2754
+ const target = normalizeLevel2(dimension.target ?? dimension.level, fallbackLevel);
2755
+ const adapt = dimension.adapt === true;
2756
+ if (!adapt) {
2757
+ return target;
2758
+ }
2759
+ return {
2760
+ target,
2761
+ adapt: true,
2762
+ floor: normalizeLevel2(dimension.floor, "low"),
2763
+ ceiling: normalizeLevel2(dimension.ceiling, "high")
2764
+ };
2765
+ }
2766
+ function normalizeHumorValue(value) {
2767
+ if (typeof value === "string") {
2768
+ return {
2769
+ target: normalizeLevel2(value, "low"),
2770
+ style: "none"
2771
+ };
2772
+ }
2773
+ const dimension = asObject(value);
2774
+ return {
2775
+ target: normalizeLevel2(dimension.target ?? dimension.level, "low"),
2776
+ style: normalizeHumorStyle(dimension.style)
2777
+ };
2778
+ }
2779
+ function slugifyName(value) {
2780
+ const slug = String(value ?? "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2781
+ return slug || "imported-profile";
2782
+ }
2783
+ function extractJSONObject2(text) {
2784
+ const raw = String(text ?? "").trim();
2785
+ if (!raw) return {};
2786
+ try {
2787
+ return JSON.parse(raw);
2788
+ } catch {
2789
+ const match = raw.match(/\{[\s\S]*\}/);
2790
+ if (!match) {
2791
+ throw new Error("Import analysis response did not contain JSON.");
2792
+ }
2793
+ return JSON.parse(match[0]);
2794
+ }
2795
+ }
2796
+ function selectImportProvider(options = {}) {
2797
+ if (typeof options.analysisFn === "function") {
2798
+ return {
2799
+ provider: "custom",
2800
+ analyzeFn: options.analysisFn
2801
+ };
2802
+ }
2803
+ const requested = String(options.provider ?? "auto").toLowerCase();
2804
+ const openaiKey = options.openaiApiKey ?? process.env.TRAITS_OPENAI_API_KEY;
2805
+ const anthropicKey = options.anthropicApiKey ?? process.env.TRAITS_ANTHROPIC_API_KEY;
2806
+ if (requested === "openai" || requested === "auto" && openaiKey) {
2807
+ if (!openaiKey) {
2808
+ const error2 = new Error(
2809
+ "Import requested OpenAI provider but TRAITS_OPENAI_API_KEY is missing."
2810
+ );
2811
+ error2.code = "E_IMPORT_PROVIDER_UNAVAILABLE";
2812
+ throw error2;
2813
+ }
2814
+ return {
2815
+ provider: "openai",
2816
+ analyzeFn: ({ systemPrompt, userPrompt }) => openAIJudge({
2817
+ apiKey: openaiKey,
2818
+ systemPrompt,
2819
+ userPrompt,
2820
+ model: options.model ?? "gpt-4o-mini",
2821
+ baseUrl: options.openaiBaseUrl ?? options.openAIBaseUrl,
2822
+ fetchImpl: options.fetchImpl,
2823
+ timeoutMs: options.fetchTimeoutMs,
2824
+ maxRetries: options.fetchMaxRetries,
2825
+ retryBaseMs: options.fetchRetryBaseMs
2826
+ })
2827
+ };
2828
+ }
2829
+ if (requested === "anthropic" || requested === "auto" && anthropicKey) {
2830
+ if (!anthropicKey) {
2831
+ const error2 = new Error(
2832
+ "Import requested Anthropic provider but TRAITS_ANTHROPIC_API_KEY is missing."
2833
+ );
2834
+ error2.code = "E_IMPORT_PROVIDER_UNAVAILABLE";
2835
+ throw error2;
2836
+ }
2837
+ return {
2838
+ provider: "anthropic",
2839
+ analyzeFn: ({ systemPrompt, userPrompt }) => anthropicJudge({
2840
+ apiKey: anthropicKey,
2841
+ systemPrompt,
2842
+ userPrompt,
2843
+ model: options.model ?? "claude-3-5-sonnet-latest",
2844
+ baseUrl: options.anthropicBaseUrl,
2845
+ fetchImpl: options.fetchImpl,
2846
+ timeoutMs: options.fetchTimeoutMs,
2847
+ maxRetries: options.fetchMaxRetries,
2848
+ retryBaseMs: options.fetchRetryBaseMs
2849
+ })
2850
+ };
2851
+ }
2852
+ const error = new Error(
2853
+ "Import requires TRAITS_OPENAI_API_KEY or TRAITS_ANTHROPIC_API_KEY."
2854
+ );
2855
+ error.code = "E_IMPORT_PROVIDER_UNAVAILABLE";
2856
+ throw error;
2857
+ }
2858
+ function buildImportSystemPrompt() {
2859
+ return [
2860
+ "Analyze the system prompt and extract personality signals.",
2861
+ "Return strict JSON only with this shape:",
2862
+ "{",
2863
+ ' "detected_role": "string",',
2864
+ ' "detected_dimensions": {',
2865
+ ' "formality": "very-low|low|medium|high|very-high",',
2866
+ ' "warmth": "very-low|low|medium|high|very-high",',
2867
+ ' "verbosity": "very-low|low|medium|high|very-high",',
2868
+ ' "directness": "very-low|low|medium|high|very-high",',
2869
+ ' "empathy": "very-low|low|medium|high|very-high",',
2870
+ ' "humor": { "level": "very-low|low|medium|high|very-high", "style": "none|dry|subtle-wit|playful" }',
2871
+ " },",
2872
+ ' "detected_vocabulary": { "preferred": ["..."], "forbidden": ["..."] },',
2873
+ ' "detected_behavioral_rules": ["..."],',
2874
+ ' "confidence": 0.0,',
2875
+ ' "notes": "short rationale"',
2876
+ "}",
2877
+ "Do not include markdown fences."
2878
+ ].join("\n");
2879
+ }
2880
+ function buildImportUserPrompt(promptText) {
2881
+ return [
2882
+ "System prompt to analyze:",
2883
+ "<system_prompt>",
2884
+ String(promptText ?? ""),
2885
+ "</system_prompt>"
2886
+ ].join("\n");
2887
+ }
2888
+ function mapImportAnalysisToProfile(analysis, options = {}) {
2889
+ const source = asObject(analysis);
2890
+ const dimensions = asObject(source.detected_dimensions);
2891
+ const vocabulary = asObject(source.detected_vocabulary);
2892
+ const profileName = options.profileName ?? slugifyName(source.detected_role ?? "imported-profile");
2893
+ const role = String(source.detected_role ?? "Helpful assistant").trim() || "Helpful assistant";
2894
+ const notes = String(source.notes ?? "").trim();
2895
+ const confidence = typeof source.confidence === "number" && Number.isFinite(source.confidence) ? source.confidence : null;
2896
+ const preferredTerms = normalizeStringArray(vocabulary.preferred, 16);
2897
+ const forbiddenTerms = normalizeStringArray(vocabulary.forbidden, 16);
2898
+ const behavioralRules = normalizeStringArray(source.detected_behavioral_rules, 20);
2899
+ const profile = {
2900
+ schema: "v1.4",
2901
+ meta: {
2902
+ name: profileName,
2903
+ version: "0.1.0",
2904
+ description: options.description ?? `Imported profile derived from an existing system prompt${confidence != null ? ` (confidence ${confidence.toFixed(2)})` : ""}.`,
2905
+ tags: ["imported"],
2906
+ target_audience: "General users"
2907
+ },
2908
+ identity: {
2909
+ role,
2910
+ backstory: notes || "Derived from imported system prompt analysis."
2911
+ },
2912
+ voice: {
2913
+ formality: normalizeDimensionValue(dimensions.formality, "medium"),
2914
+ warmth: normalizeDimensionValue(dimensions.warmth, "medium"),
2915
+ verbosity: normalizeDimensionValue(dimensions.verbosity, "medium"),
2916
+ directness: normalizeDimensionValue(dimensions.directness, "medium"),
2917
+ empathy: normalizeDimensionValue(dimensions.empathy, "medium"),
2918
+ humor: normalizeHumorValue(dimensions.humor)
2919
+ },
2920
+ vocabulary: {
2921
+ preferred_terms: preferredTerms,
2922
+ forbidden_terms: forbiddenTerms
2923
+ },
2924
+ behavioral_rules: behavioralRules.length > 0 ? behavioralRules : ["Address the user's request directly and provide actionable help."]
2925
+ };
2926
+ return profile;
2927
+ }
2928
+ function renderImportedProfileYAML(profile) {
2929
+ return (0, import_yaml2.stringify)(profile);
2930
+ }
2931
+ async function runImportAnalysis(promptText, options = {}) {
2932
+ const provider = selectImportProvider(options);
2933
+ const raw = await provider.analyzeFn({
2934
+ systemPrompt: buildImportSystemPrompt(),
2935
+ userPrompt: buildImportUserPrompt(promptText)
2936
+ });
2937
+ const analysis = typeof raw === "string" ? extractJSONObject2(raw) : asObject(raw);
2938
+ const profile = mapImportAnalysisToProfile(analysis, options);
2939
+ const yaml = renderImportedProfileYAML(profile);
2940
+ return {
2941
+ provider: provider.provider,
2942
+ analysis,
2943
+ profile,
2944
+ yaml
2945
+ };
2946
+ }
2947
+ // Annotate the CommonJS export names for ESM import in node:
2948
+ 0 && (module.exports = {
2949
+ compileProfile,
2950
+ compileResolvedProfile,
2951
+ evaluateTier1Response,
2952
+ injectPersonality,
2953
+ loadProfileFile,
2954
+ normalizeProfile,
2955
+ resolveActiveContext,
2956
+ resolveExtends,
2957
+ runImportAnalysis,
2958
+ runTier1Evaluation,
2959
+ runTier1EvaluationForProfile,
2960
+ runTier2Evaluation,
2961
+ runTier2EvaluationForProfile,
2962
+ runTier3Evaluation,
2963
+ runTier3EvaluationForProfile,
2964
+ validateEvalScenario,
2965
+ validateEvalScenarios,
2966
+ validateProfile,
2967
+ validateResolvedProfile
2968
+ });