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