@traits-dev/core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @traits-dev/core
2
2
 
3
- Core SDK for traits.dev personality profiles: validate, compile, inject, and evaluate.
3
+ Core SDK for traits.dev voice profiles and behavioral policies: validate, compile, inject, and evaluate.
4
4
 
5
5
  ## Install
6
6
 
@@ -25,6 +25,15 @@ interface ContextAdaptation {
25
25
  inject?: string[];
26
26
  priority?: number;
27
27
  }
28
+ interface CapabilityHandoff {
29
+ trigger: string;
30
+ action: string;
31
+ }
32
+ interface ProfileCapabilities {
33
+ tools: string[];
34
+ constraints: string[];
35
+ handoff: CapabilityHandoff;
36
+ }
28
37
  interface PersonalityProfile {
29
38
  schema: string;
30
39
  meta: {
@@ -53,6 +62,7 @@ interface PersonalityProfile {
53
62
  vocabulary?: VocabularyConstraints;
54
63
  behavioral_rules?: string[];
55
64
  context_adaptations?: ContextAdaptation[];
65
+ capabilities?: ProfileCapabilities;
56
66
  localization?: Record<string, unknown>;
57
67
  channel_adaptations?: Record<string, unknown>;
58
68
  extends?: string;
@@ -349,4 +359,4 @@ declare function runImportAnalysis(promptText: unknown, options?: ImportOptions)
349
359
  yaml: string;
350
360
  }>;
351
361
 
352
- export { runImportAnalysis as A, runTier1EvaluationForProfile as B, type CompileOptions as C, type DimensionName as D, type EvalSample as E, runTier2Evaluation as F, runTier2EvaluationForProfile as G, type HumorDimensionObject as H, type ImportOptions as I, runTier3Evaluation as J, runTier3EvaluationForProfile as K, type Level as L, validateEvalScenario as M, validateEvalScenarios as N, validateProfile as O, type PersonalityProfile as P, validateResolvedProfile as Q, type Tier1Options as T, type ValidationResult as V, type ValidationCheckSummary as a, type ValidationDiagnostic as b, type CompiledPersonality as c, type ContextAdaptation as d, type ContextResolution as e, type DimensionObject as f, type DimensionShorthand as g, type DimensionValue as h, type ExtendsDiagnostics as i, type ExtendsResult as j, type HumorDimensionValue as k, type HumorStyle as l, type Tier2Options as m, type Tier3Options as n, type VocabularyConstraints as o, compileProfile as p, compileResolvedProfile as q, runTier1Evaluation as r, evaluateTier1Response as s, injectPersonality as t, loadProfileFile as u, mapImportAnalysisToProfile as v, normalizeProfile as w, renderImportedProfileYAML as x, resolveActiveContext as y, resolveExtends as z };
362
+ export { resolveActiveContext as A, resolveExtends as B, type CapabilityHandoff as C, type DimensionName as D, type EvalSample as E, runImportAnalysis as F, runTier1EvaluationForProfile as G, type HumorDimensionObject as H, type ImportOptions as I, runTier2Evaluation as J, runTier2EvaluationForProfile as K, type Level as L, runTier3Evaluation as M, runTier3EvaluationForProfile as N, validateEvalScenario as O, type PersonalityProfile as P, validateEvalScenarios as Q, validateProfile as R, validateResolvedProfile as S, type Tier1Options as T, type ValidationResult as V, type ValidationCheckSummary as a, type ValidationDiagnostic as b, type CompileOptions as c, type CompiledPersonality as d, type ContextAdaptation as e, type ContextResolution as f, type DimensionObject as g, type DimensionShorthand as h, type DimensionValue as i, type ExtendsDiagnostics as j, type ExtendsResult as k, type HumorDimensionValue as l, type HumorStyle as m, type ProfileCapabilities as n, type Tier2Options as o, type Tier3Options as p, type VocabularyConstraints as q, runTier1Evaluation as r, compileProfile as s, compileResolvedProfile as t, evaluateTier1Response as u, injectPersonality as v, loadProfileFile as w, mapImportAnalysisToProfile as x, normalizeProfile as y, renderImportedProfileYAML as z };
@@ -25,6 +25,15 @@ interface ContextAdaptation {
25
25
  inject?: string[];
26
26
  priority?: number;
27
27
  }
28
+ interface CapabilityHandoff {
29
+ trigger: string;
30
+ action: string;
31
+ }
32
+ interface ProfileCapabilities {
33
+ tools: string[];
34
+ constraints: string[];
35
+ handoff: CapabilityHandoff;
36
+ }
28
37
  interface PersonalityProfile {
29
38
  schema: string;
30
39
  meta: {
@@ -53,6 +62,7 @@ interface PersonalityProfile {
53
62
  vocabulary?: VocabularyConstraints;
54
63
  behavioral_rules?: string[];
55
64
  context_adaptations?: ContextAdaptation[];
65
+ capabilities?: ProfileCapabilities;
56
66
  localization?: Record<string, unknown>;
57
67
  channel_adaptations?: Record<string, unknown>;
58
68
  extends?: string;
@@ -349,4 +359,4 @@ declare function runImportAnalysis(promptText: unknown, options?: ImportOptions)
349
359
  yaml: string;
350
360
  }>;
351
361
 
352
- export { runImportAnalysis as A, runTier1EvaluationForProfile as B, type CompileOptions as C, type DimensionName as D, type EvalSample as E, runTier2Evaluation as F, runTier2EvaluationForProfile as G, type HumorDimensionObject as H, type ImportOptions as I, runTier3Evaluation as J, runTier3EvaluationForProfile as K, type Level as L, validateEvalScenario as M, validateEvalScenarios as N, validateProfile as O, type PersonalityProfile as P, validateResolvedProfile as Q, type Tier1Options as T, type ValidationResult as V, type ValidationCheckSummary as a, type ValidationDiagnostic as b, type CompiledPersonality as c, type ContextAdaptation as d, type ContextResolution as e, type DimensionObject as f, type DimensionShorthand as g, type DimensionValue as h, type ExtendsDiagnostics as i, type ExtendsResult as j, type HumorDimensionValue as k, type HumorStyle as l, type Tier2Options as m, type Tier3Options as n, type VocabularyConstraints as o, compileProfile as p, compileResolvedProfile as q, runTier1Evaluation as r, evaluateTier1Response as s, injectPersonality as t, loadProfileFile as u, mapImportAnalysisToProfile as v, normalizeProfile as w, renderImportedProfileYAML as x, resolveActiveContext as y, resolveExtends as z };
362
+ export { resolveActiveContext as A, resolveExtends as B, type CapabilityHandoff as C, type DimensionName as D, type EvalSample as E, runImportAnalysis as F, runTier1EvaluationForProfile as G, type HumorDimensionObject as H, type ImportOptions as I, runTier2Evaluation as J, runTier2EvaluationForProfile as K, type Level as L, runTier3Evaluation as M, runTier3EvaluationForProfile as N, validateEvalScenario as O, type PersonalityProfile as P, validateEvalScenarios as Q, validateProfile as R, validateResolvedProfile as S, type Tier1Options as T, type ValidationResult as V, type ValidationCheckSummary as a, type ValidationDiagnostic as b, type CompileOptions as c, type CompiledPersonality as d, type ContextAdaptation as e, type ContextResolution as f, type DimensionObject as g, type DimensionShorthand as h, type DimensionValue as i, type ExtendsDiagnostics as j, type ExtendsResult as k, type HumorDimensionValue as l, type HumorStyle as m, type ProfileCapabilities as n, type Tier2Options as o, type Tier3Options as p, type VocabularyConstraints as q, runTier1Evaluation as r, compileProfile as s, compileResolvedProfile as t, evaluateTier1Response as u, injectPersonality as v, loadProfileFile as w, mapImportAnalysisToProfile as x, normalizeProfile as y, renderImportedProfileYAML as z };
package/dist/index.cjs CHANGED
@@ -112,6 +112,7 @@ var PASS_THROUGH_FIELDS = /* @__PURE__ */ new Set([
112
112
  "vocabulary",
113
113
  "behavioral_rules",
114
114
  "context_adaptations",
115
+ "capabilities",
115
116
  "extends",
116
117
  "behavioral_rules_remove",
117
118
  "context_adaptations_remove"
@@ -194,6 +195,27 @@ function mergeContextAdaptations(parentAdaptations = [], childAdaptations = [])
194
195
  }
195
196
  return out;
196
197
  }
198
+ function mergeCapabilities(parentCapabilities, childCapabilities) {
199
+ if (!parentCapabilities && !childCapabilities) return void 0;
200
+ if (!parentCapabilities) return clone(childCapabilities);
201
+ if (!childCapabilities) return clone(parentCapabilities);
202
+ const mergedTools = dedupCaseInsensitive([
203
+ ...asArray(parentCapabilities.tools),
204
+ ...asArray(childCapabilities.tools)
205
+ ]);
206
+ const mergedConstraints = dedupCaseInsensitive([
207
+ ...asArray(parentCapabilities.constraints),
208
+ ...asArray(childCapabilities.constraints)
209
+ ]);
210
+ return {
211
+ tools: mergedTools,
212
+ constraints: mergedConstraints,
213
+ handoff: {
214
+ trigger: childCapabilities.handoff?.trigger ?? parentCapabilities.handoff?.trigger ?? "",
215
+ action: childCapabilities.handoff?.action ?? parentCapabilities.handoff?.action ?? ""
216
+ }
217
+ };
218
+ }
197
219
  function removeCaseInsensitive(items, removals) {
198
220
  const removalSet = new Set(
199
221
  asArray(removals).map((item) => String(item).toLowerCase())
@@ -252,6 +274,10 @@ function mergeProfiles(parentProfile, childProfile) {
252
274
  parentProfile.context_adaptations,
253
275
  childProfile.context_adaptations
254
276
  );
277
+ merged.capabilities = mergeCapabilities(
278
+ parentProfile.capabilities,
279
+ childProfile.capabilities
280
+ );
255
281
  for (const [key, value] of Object.entries(childProfile)) {
256
282
  if (PASS_THROUGH_FIELDS.has(key)) {
257
283
  continue;
@@ -380,6 +406,7 @@ function checkOverspec(profile) {
380
406
 
381
407
  // src/validator/schema.ts
382
408
  var HUMOR_STYLES = ["none", "dry", "subtle-wit", "playful"];
409
+ var SUPPORTED_SCHEMAS = /* @__PURE__ */ new Set(["v1.4", "v1.5"]);
383
410
  var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
384
411
  "schema",
385
412
  "meta",
@@ -388,6 +415,7 @@ var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
388
415
  "vocabulary",
389
416
  "behavioral_rules",
390
417
  "context_adaptations",
418
+ "capabilities",
391
419
  "localization",
392
420
  "channel_adaptations",
393
421
  "extends",
@@ -400,6 +428,7 @@ var VOCABULARY_KEYS = /* @__PURE__ */ new Set([
400
428
  "preferred_terms_remove",
401
429
  "forbidden_terms_remove"
402
430
  ]);
431
+ var CAPABILITIES_KEYS = /* @__PURE__ */ new Set(["tools", "constraints", "handoff"]);
403
432
  function isObject(value) {
404
433
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
405
434
  }
@@ -576,7 +605,7 @@ function validateSchema(profile) {
576
605
  `Missing required "schema" field`,
577
606
  "schema"
578
607
  );
579
- } else if (profile.schema !== "v1.4") {
608
+ } else if (!SUPPORTED_SCHEMAS.has(profile.schema)) {
580
609
  pushDiagnostic(
581
610
  structureDiagnostics,
582
611
  "V001",
@@ -724,6 +753,76 @@ function validateSchema(profile) {
724
753
  "behavioral_rules"
725
754
  );
726
755
  }
756
+ if (profile.capabilities != null) {
757
+ if (profile.schema !== "v1.5") {
758
+ pushDiagnostic(
759
+ structureDiagnostics,
760
+ "V001",
761
+ `The "capabilities" section requires schema version "v1.5"`,
762
+ "capabilities"
763
+ );
764
+ }
765
+ if (!isObject(profile.capabilities)) {
766
+ pushDiagnostic(
767
+ structureDiagnostics,
768
+ "V001",
769
+ `Expected "capabilities" to be an object`,
770
+ "capabilities"
771
+ );
772
+ } else {
773
+ for (const key of Object.keys(profile.capabilities)) {
774
+ if (!CAPABILITIES_KEYS.has(key)) {
775
+ pushDiagnostic(
776
+ structureDiagnostics,
777
+ "V001",
778
+ `Unknown capabilities key "${key}"`,
779
+ `capabilities.${key}`
780
+ );
781
+ }
782
+ }
783
+ if (!isStringArray(profile.capabilities.tools)) {
784
+ pushDiagnostic(
785
+ structureDiagnostics,
786
+ "V001",
787
+ `Expected "capabilities.tools" to be an array of strings`,
788
+ "capabilities.tools"
789
+ );
790
+ }
791
+ if (!isStringArray(profile.capabilities.constraints)) {
792
+ pushDiagnostic(
793
+ structureDiagnostics,
794
+ "V001",
795
+ `Expected "capabilities.constraints" to be an array of strings`,
796
+ "capabilities.constraints"
797
+ );
798
+ }
799
+ if (!isObject(profile.capabilities.handoff)) {
800
+ pushDiagnostic(
801
+ structureDiagnostics,
802
+ "V001",
803
+ `Expected "capabilities.handoff" to be an object`,
804
+ "capabilities.handoff"
805
+ );
806
+ } else {
807
+ if (!isString(profile.capabilities.handoff.trigger)) {
808
+ pushDiagnostic(
809
+ structureDiagnostics,
810
+ "V001",
811
+ `Expected "capabilities.handoff.trigger" to be a non-empty string`,
812
+ "capabilities.handoff.trigger"
813
+ );
814
+ }
815
+ if (!isString(profile.capabilities.handoff.action)) {
816
+ pushDiagnostic(
817
+ structureDiagnostics,
818
+ "V001",
819
+ `Expected "capabilities.handoff.action" to be a non-empty string`,
820
+ "capabilities.handoff.action"
821
+ );
822
+ }
823
+ }
824
+ }
825
+ }
727
826
  if (profile.behavioral_rules_remove != null && !isStringArray(profile.behavioral_rules_remove)) {
728
827
  pushDiagnostic(
729
828
  structureDiagnostics,
@@ -940,6 +1039,38 @@ var S005_PATTERNS = [
940
1039
  { id: "mode-switching", regex: /\b(switch|change)\b.*\bmode\b/i },
941
1040
  { id: "jailbreak-language", regex: /\b(jailbreak|dan mode|developer mode)\b/i }
942
1041
  ];
1042
+ var S008_ACTION_PATTERNS = [
1043
+ {
1044
+ id: "take-care-of",
1045
+ regex: /\b(i(?:'ll| will)\s+take\s+care\s+of)\b/i,
1046
+ toolHints: ["case", "ticket", "workflow", "task", "support", "assist"]
1047
+ },
1048
+ {
1049
+ id: "escalate",
1050
+ regex: /\b(i(?:'ll| will)\s+escalat(?:e|ing)|i\s+can\s+escalate)\b/i,
1051
+ toolHints: ["escalat", "ticket"]
1052
+ },
1053
+ {
1054
+ id: "contact-or-notify",
1055
+ regex: /\b(i(?:'ll| will)\s+(?:contact|notify|reach out to|message))\b/i,
1056
+ toolHints: ["contact", "notify", "message", "email", "sms", "ticket"]
1057
+ },
1058
+ {
1059
+ id: "refund-action",
1060
+ regex: /\b(i(?:'ll| will)\s+(?:issue|process|submit)\s+(?:a\s+)?refund)\b/i,
1061
+ toolHints: ["refund", "payment", "billing"]
1062
+ },
1063
+ {
1064
+ id: "schedule-action",
1065
+ regex: /\b(i(?:'ll| will)\s+(?:schedule|book|reschedule))\b/i,
1066
+ toolHints: ["schedule", "calendar", "appointment", "booking"]
1067
+ },
1068
+ {
1069
+ id: "generic-i-will-action",
1070
+ regex: /\b(i(?:'ll| will)\s+(?:handle|resolve|fix|approve|arrange|dispatch|submit))\b/i,
1071
+ toolHints: []
1072
+ }
1073
+ ];
943
1074
  function normalizeText(value) {
944
1075
  return String(value ?? "").trim();
945
1076
  }
@@ -1010,6 +1141,21 @@ function collectS005Candidates(profile) {
1010
1141
  });
1011
1142
  return candidates.filter((item) => item.text.length > 0);
1012
1143
  }
1144
+ function collectS008Candidates(profile) {
1145
+ const candidates = [];
1146
+ asArray(profile?.behavioral_rules).forEach((rule, idx) => {
1147
+ candidates.push({
1148
+ location: `behavioral_rules[${idx}]`,
1149
+ text: normalizeText(rule)
1150
+ });
1151
+ });
1152
+ return candidates.filter((item) => item.text.length > 0);
1153
+ }
1154
+ function hasCapabilityForPattern(tools, pattern) {
1155
+ if (tools.length === 0) return false;
1156
+ if (pattern.toolHints.length === 0) return tools.length > 0;
1157
+ return pattern.toolHints.some((hint) => tools.some((tool) => tool.includes(hint)));
1158
+ }
1013
1159
  function matchPatterns(candidates, patterns, code, severity) {
1014
1160
  const diagnostics = [];
1015
1161
  for (const candidate of candidates) {
@@ -1104,6 +1250,28 @@ function checkS003(profile) {
1104
1250
  function checkS005(profile) {
1105
1251
  return matchPatterns(collectS005Candidates(profile), S005_PATTERNS, "S005", "warning");
1106
1252
  }
1253
+ function checkS008(profile) {
1254
+ if (!profile?.capabilities) return [];
1255
+ const tools = asArray(profile.capabilities.tools).map(
1256
+ (tool) => String(tool).toLowerCase()
1257
+ );
1258
+ const diagnostics = [];
1259
+ for (const candidate of collectS008Candidates(profile)) {
1260
+ for (const pattern of S008_ACTION_PATTERNS) {
1261
+ if (!pattern.regex.test(candidate.text)) continue;
1262
+ if (hasCapabilityForPattern(tools, pattern)) break;
1263
+ const expectedTools = pattern.toolHints.length > 0 ? pattern.toolHints.map((hint) => `"*${hint}*"`).join(", ") : "a concrete action tool";
1264
+ diagnostics.push({
1265
+ code: "S008",
1266
+ severity: "warning",
1267
+ message: `Action-claiming language matched "${pattern.id}" at ${candidate.location}, but capabilities.tools does not indicate support (expected ${expectedTools}).`,
1268
+ location: candidate.location
1269
+ });
1270
+ break;
1271
+ }
1272
+ }
1273
+ return diagnostics;
1274
+ }
1107
1275
 
1108
1276
  // src/validator/inheritance.ts
1109
1277
  var SAFETY_ADAPTATION_NAME = /(crisis|emergency|harm|suicid|self[-_ ]?harm)/i;
@@ -1229,6 +1397,7 @@ function validateResolvedProfile(profile, options = {}) {
1229
1397
  ...checkS002(profile),
1230
1398
  ...checkS003(profile),
1231
1399
  ...checkS005(profile),
1400
+ ...checkS008(profile),
1232
1401
  ...checkS007(profile),
1233
1402
  ...overspec.diagnostics
1234
1403
  ];
@@ -1672,6 +1841,26 @@ function renderPersonalityText(profile, model, contextResolution, compileOptions
1672
1841
  lines.push(`- ${rule}`);
1673
1842
  }
1674
1843
  }
1844
+ if (profile.schema === "v1.5" && profile.capabilities) {
1845
+ const capabilities = profile.capabilities;
1846
+ const tools = asArray(capabilities.tools);
1847
+ const constraints = asArray(capabilities.constraints);
1848
+ lines.push("");
1849
+ lines.push("[CAPABILITY BOUNDARIES]");
1850
+ lines.push(
1851
+ `Tools: ${tools.length > 0 ? tools.join("; ") : "(none \u2014 advisory only, no side-effect tools configured)"}`
1852
+ );
1853
+ lines.push("Constraints:");
1854
+ if (constraints.length === 0) {
1855
+ lines.push("- (none)");
1856
+ } else {
1857
+ for (const constraint of constraints) {
1858
+ lines.push(`- ${constraint}`);
1859
+ }
1860
+ }
1861
+ lines.push(`Handoff trigger: ${capabilities.handoff.trigger}`);
1862
+ lines.push(`Handoff action: ${capabilities.handoff.action}`);
1863
+ }
1675
1864
  if (contextResolution.matched.length > 0) {
1676
1865
  lines.push("");
1677
1866
  lines.push("[ACTIVE CONTEXT]");
package/dist/index.d.cts CHANGED
@@ -1 +1 @@
1
- export { C as CompileOptions, c as CompiledPersonality, d as ContextAdaptation, e as ContextResolution, D as DimensionName, f as DimensionObject, g as DimensionShorthand, h as DimensionValue, E as EvalSample, i as ExtendsDiagnostics, j as ExtendsResult, H as HumorDimensionObject, k as HumorDimensionValue, l as HumorStyle, I as ImportOptions, L as Level, P as PersonalityProfile, T as Tier1Options, m as Tier2Options, n as Tier3Options, b as ValidationDiagnostic, V as ValidationResult, o as VocabularyConstraints, p as compileProfile, q as compileResolvedProfile, s as evaluateTier1Response, t as injectPersonality, u as loadProfileFile, w as normalizeProfile, y as resolveActiveContext, z as resolveExtends, A as runImportAnalysis, r as runTier1Evaluation, B as runTier1EvaluationForProfile, F as runTier2Evaluation, G as runTier2EvaluationForProfile, J as runTier3Evaluation, K as runTier3EvaluationForProfile, M as validateEvalScenario, N as validateEvalScenarios, O as validateProfile, Q as validateResolvedProfile } from './index-CkGiIKnu.cjs';
1
+ export { C as CapabilityHandoff, c as CompileOptions, d as CompiledPersonality, e as ContextAdaptation, f as ContextResolution, D as DimensionName, g as DimensionObject, h as DimensionShorthand, i as DimensionValue, E as EvalSample, j as ExtendsDiagnostics, k as ExtendsResult, H as HumorDimensionObject, l as HumorDimensionValue, m as HumorStyle, I as ImportOptions, L as Level, P as PersonalityProfile, n as ProfileCapabilities, T as Tier1Options, o as Tier2Options, p as Tier3Options, b as ValidationDiagnostic, V as ValidationResult, q as VocabularyConstraints, s as compileProfile, t as compileResolvedProfile, u as evaluateTier1Response, v as injectPersonality, w as loadProfileFile, y as normalizeProfile, A as resolveActiveContext, B as resolveExtends, F as runImportAnalysis, r as runTier1Evaluation, G as runTier1EvaluationForProfile, J as runTier2Evaluation, K as runTier2EvaluationForProfile, M as runTier3Evaluation, N as runTier3EvaluationForProfile, O as validateEvalScenario, Q as validateEvalScenarios, R as validateProfile, S as validateResolvedProfile } from './index-1c7xQG2q.cjs';
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { C as CompileOptions, c as CompiledPersonality, d as ContextAdaptation, e as ContextResolution, D as DimensionName, f as DimensionObject, g as DimensionShorthand, h as DimensionValue, E as EvalSample, i as ExtendsDiagnostics, j as ExtendsResult, H as HumorDimensionObject, k as HumorDimensionValue, l as HumorStyle, I as ImportOptions, L as Level, P as PersonalityProfile, T as Tier1Options, m as Tier2Options, n as Tier3Options, b as ValidationDiagnostic, V as ValidationResult, o as VocabularyConstraints, p as compileProfile, q as compileResolvedProfile, s as evaluateTier1Response, t as injectPersonality, u as loadProfileFile, w as normalizeProfile, y as resolveActiveContext, z as resolveExtends, A as runImportAnalysis, r as runTier1Evaluation, B as runTier1EvaluationForProfile, F as runTier2Evaluation, G as runTier2EvaluationForProfile, J as runTier3Evaluation, K as runTier3EvaluationForProfile, M as validateEvalScenario, N as validateEvalScenarios, O as validateProfile, Q as validateResolvedProfile } from './index-CkGiIKnu.js';
1
+ export { C as CapabilityHandoff, c as CompileOptions, d as CompiledPersonality, e as ContextAdaptation, f as ContextResolution, D as DimensionName, g as DimensionObject, h as DimensionShorthand, i as DimensionValue, E as EvalSample, j as ExtendsDiagnostics, k as ExtendsResult, H as HumorDimensionObject, l as HumorDimensionValue, m as HumorStyle, I as ImportOptions, L as Level, P as PersonalityProfile, n as ProfileCapabilities, T as Tier1Options, o as Tier2Options, p as Tier3Options, b as ValidationDiagnostic, V as ValidationResult, q as VocabularyConstraints, s as compileProfile, t as compileResolvedProfile, u as evaluateTier1Response, v as injectPersonality, w as loadProfileFile, y as normalizeProfile, A as resolveActiveContext, B as resolveExtends, F as runImportAnalysis, r as runTier1Evaluation, G as runTier1EvaluationForProfile, J as runTier2Evaluation, K as runTier2EvaluationForProfile, M as runTier3Evaluation, N as runTier3EvaluationForProfile, O as validateEvalScenario, Q as validateEvalScenarios, R as validateProfile, S as validateResolvedProfile } from './index-1c7xQG2q.js';
package/dist/index.js CHANGED
@@ -58,6 +58,7 @@ var PASS_THROUGH_FIELDS = /* @__PURE__ */ new Set([
58
58
  "vocabulary",
59
59
  "behavioral_rules",
60
60
  "context_adaptations",
61
+ "capabilities",
61
62
  "extends",
62
63
  "behavioral_rules_remove",
63
64
  "context_adaptations_remove"
@@ -140,6 +141,27 @@ function mergeContextAdaptations(parentAdaptations = [], childAdaptations = [])
140
141
  }
141
142
  return out;
142
143
  }
144
+ function mergeCapabilities(parentCapabilities, childCapabilities) {
145
+ if (!parentCapabilities && !childCapabilities) return void 0;
146
+ if (!parentCapabilities) return clone(childCapabilities);
147
+ if (!childCapabilities) return clone(parentCapabilities);
148
+ const mergedTools = dedupCaseInsensitive([
149
+ ...asArray(parentCapabilities.tools),
150
+ ...asArray(childCapabilities.tools)
151
+ ]);
152
+ const mergedConstraints = dedupCaseInsensitive([
153
+ ...asArray(parentCapabilities.constraints),
154
+ ...asArray(childCapabilities.constraints)
155
+ ]);
156
+ return {
157
+ tools: mergedTools,
158
+ constraints: mergedConstraints,
159
+ handoff: {
160
+ trigger: childCapabilities.handoff?.trigger ?? parentCapabilities.handoff?.trigger ?? "",
161
+ action: childCapabilities.handoff?.action ?? parentCapabilities.handoff?.action ?? ""
162
+ }
163
+ };
164
+ }
143
165
  function removeCaseInsensitive(items, removals) {
144
166
  const removalSet = new Set(
145
167
  asArray(removals).map((item) => String(item).toLowerCase())
@@ -198,6 +220,10 @@ function mergeProfiles(parentProfile, childProfile) {
198
220
  parentProfile.context_adaptations,
199
221
  childProfile.context_adaptations
200
222
  );
223
+ merged.capabilities = mergeCapabilities(
224
+ parentProfile.capabilities,
225
+ childProfile.capabilities
226
+ );
201
227
  for (const [key, value] of Object.entries(childProfile)) {
202
228
  if (PASS_THROUGH_FIELDS.has(key)) {
203
229
  continue;
@@ -326,6 +352,7 @@ function checkOverspec(profile) {
326
352
 
327
353
  // src/validator/schema.ts
328
354
  var HUMOR_STYLES = ["none", "dry", "subtle-wit", "playful"];
355
+ var SUPPORTED_SCHEMAS = /* @__PURE__ */ new Set(["v1.4", "v1.5"]);
329
356
  var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
330
357
  "schema",
331
358
  "meta",
@@ -334,6 +361,7 @@ var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
334
361
  "vocabulary",
335
362
  "behavioral_rules",
336
363
  "context_adaptations",
364
+ "capabilities",
337
365
  "localization",
338
366
  "channel_adaptations",
339
367
  "extends",
@@ -346,6 +374,7 @@ var VOCABULARY_KEYS = /* @__PURE__ */ new Set([
346
374
  "preferred_terms_remove",
347
375
  "forbidden_terms_remove"
348
376
  ]);
377
+ var CAPABILITIES_KEYS = /* @__PURE__ */ new Set(["tools", "constraints", "handoff"]);
349
378
  function isObject(value) {
350
379
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
351
380
  }
@@ -522,7 +551,7 @@ function validateSchema(profile) {
522
551
  `Missing required "schema" field`,
523
552
  "schema"
524
553
  );
525
- } else if (profile.schema !== "v1.4") {
554
+ } else if (!SUPPORTED_SCHEMAS.has(profile.schema)) {
526
555
  pushDiagnostic(
527
556
  structureDiagnostics,
528
557
  "V001",
@@ -670,6 +699,76 @@ function validateSchema(profile) {
670
699
  "behavioral_rules"
671
700
  );
672
701
  }
702
+ if (profile.capabilities != null) {
703
+ if (profile.schema !== "v1.5") {
704
+ pushDiagnostic(
705
+ structureDiagnostics,
706
+ "V001",
707
+ `The "capabilities" section requires schema version "v1.5"`,
708
+ "capabilities"
709
+ );
710
+ }
711
+ if (!isObject(profile.capabilities)) {
712
+ pushDiagnostic(
713
+ structureDiagnostics,
714
+ "V001",
715
+ `Expected "capabilities" to be an object`,
716
+ "capabilities"
717
+ );
718
+ } else {
719
+ for (const key of Object.keys(profile.capabilities)) {
720
+ if (!CAPABILITIES_KEYS.has(key)) {
721
+ pushDiagnostic(
722
+ structureDiagnostics,
723
+ "V001",
724
+ `Unknown capabilities key "${key}"`,
725
+ `capabilities.${key}`
726
+ );
727
+ }
728
+ }
729
+ if (!isStringArray(profile.capabilities.tools)) {
730
+ pushDiagnostic(
731
+ structureDiagnostics,
732
+ "V001",
733
+ `Expected "capabilities.tools" to be an array of strings`,
734
+ "capabilities.tools"
735
+ );
736
+ }
737
+ if (!isStringArray(profile.capabilities.constraints)) {
738
+ pushDiagnostic(
739
+ structureDiagnostics,
740
+ "V001",
741
+ `Expected "capabilities.constraints" to be an array of strings`,
742
+ "capabilities.constraints"
743
+ );
744
+ }
745
+ if (!isObject(profile.capabilities.handoff)) {
746
+ pushDiagnostic(
747
+ structureDiagnostics,
748
+ "V001",
749
+ `Expected "capabilities.handoff" to be an object`,
750
+ "capabilities.handoff"
751
+ );
752
+ } else {
753
+ if (!isString(profile.capabilities.handoff.trigger)) {
754
+ pushDiagnostic(
755
+ structureDiagnostics,
756
+ "V001",
757
+ `Expected "capabilities.handoff.trigger" to be a non-empty string`,
758
+ "capabilities.handoff.trigger"
759
+ );
760
+ }
761
+ if (!isString(profile.capabilities.handoff.action)) {
762
+ pushDiagnostic(
763
+ structureDiagnostics,
764
+ "V001",
765
+ `Expected "capabilities.handoff.action" to be a non-empty string`,
766
+ "capabilities.handoff.action"
767
+ );
768
+ }
769
+ }
770
+ }
771
+ }
673
772
  if (profile.behavioral_rules_remove != null && !isStringArray(profile.behavioral_rules_remove)) {
674
773
  pushDiagnostic(
675
774
  structureDiagnostics,
@@ -886,6 +985,38 @@ var S005_PATTERNS = [
886
985
  { id: "mode-switching", regex: /\b(switch|change)\b.*\bmode\b/i },
887
986
  { id: "jailbreak-language", regex: /\b(jailbreak|dan mode|developer mode)\b/i }
888
987
  ];
988
+ var S008_ACTION_PATTERNS = [
989
+ {
990
+ id: "take-care-of",
991
+ regex: /\b(i(?:'ll| will)\s+take\s+care\s+of)\b/i,
992
+ toolHints: ["case", "ticket", "workflow", "task", "support", "assist"]
993
+ },
994
+ {
995
+ id: "escalate",
996
+ regex: /\b(i(?:'ll| will)\s+escalat(?:e|ing)|i\s+can\s+escalate)\b/i,
997
+ toolHints: ["escalat", "ticket"]
998
+ },
999
+ {
1000
+ id: "contact-or-notify",
1001
+ regex: /\b(i(?:'ll| will)\s+(?:contact|notify|reach out to|message))\b/i,
1002
+ toolHints: ["contact", "notify", "message", "email", "sms", "ticket"]
1003
+ },
1004
+ {
1005
+ id: "refund-action",
1006
+ regex: /\b(i(?:'ll| will)\s+(?:issue|process|submit)\s+(?:a\s+)?refund)\b/i,
1007
+ toolHints: ["refund", "payment", "billing"]
1008
+ },
1009
+ {
1010
+ id: "schedule-action",
1011
+ regex: /\b(i(?:'ll| will)\s+(?:schedule|book|reschedule))\b/i,
1012
+ toolHints: ["schedule", "calendar", "appointment", "booking"]
1013
+ },
1014
+ {
1015
+ id: "generic-i-will-action",
1016
+ regex: /\b(i(?:'ll| will)\s+(?:handle|resolve|fix|approve|arrange|dispatch|submit))\b/i,
1017
+ toolHints: []
1018
+ }
1019
+ ];
889
1020
  function normalizeText(value) {
890
1021
  return String(value ?? "").trim();
891
1022
  }
@@ -956,6 +1087,21 @@ function collectS005Candidates(profile) {
956
1087
  });
957
1088
  return candidates.filter((item) => item.text.length > 0);
958
1089
  }
1090
+ function collectS008Candidates(profile) {
1091
+ const candidates = [];
1092
+ asArray(profile?.behavioral_rules).forEach((rule, idx) => {
1093
+ candidates.push({
1094
+ location: `behavioral_rules[${idx}]`,
1095
+ text: normalizeText(rule)
1096
+ });
1097
+ });
1098
+ return candidates.filter((item) => item.text.length > 0);
1099
+ }
1100
+ function hasCapabilityForPattern(tools, pattern) {
1101
+ if (tools.length === 0) return false;
1102
+ if (pattern.toolHints.length === 0) return tools.length > 0;
1103
+ return pattern.toolHints.some((hint) => tools.some((tool) => tool.includes(hint)));
1104
+ }
959
1105
  function matchPatterns(candidates, patterns, code, severity) {
960
1106
  const diagnostics = [];
961
1107
  for (const candidate of candidates) {
@@ -1050,6 +1196,28 @@ function checkS003(profile) {
1050
1196
  function checkS005(profile) {
1051
1197
  return matchPatterns(collectS005Candidates(profile), S005_PATTERNS, "S005", "warning");
1052
1198
  }
1199
+ function checkS008(profile) {
1200
+ if (!profile?.capabilities) return [];
1201
+ const tools = asArray(profile.capabilities.tools).map(
1202
+ (tool) => String(tool).toLowerCase()
1203
+ );
1204
+ const diagnostics = [];
1205
+ for (const candidate of collectS008Candidates(profile)) {
1206
+ for (const pattern of S008_ACTION_PATTERNS) {
1207
+ if (!pattern.regex.test(candidate.text)) continue;
1208
+ if (hasCapabilityForPattern(tools, pattern)) break;
1209
+ const expectedTools = pattern.toolHints.length > 0 ? pattern.toolHints.map((hint) => `"*${hint}*"`).join(", ") : "a concrete action tool";
1210
+ diagnostics.push({
1211
+ code: "S008",
1212
+ severity: "warning",
1213
+ message: `Action-claiming language matched "${pattern.id}" at ${candidate.location}, but capabilities.tools does not indicate support (expected ${expectedTools}).`,
1214
+ location: candidate.location
1215
+ });
1216
+ break;
1217
+ }
1218
+ }
1219
+ return diagnostics;
1220
+ }
1053
1221
 
1054
1222
  // src/validator/inheritance.ts
1055
1223
  var SAFETY_ADAPTATION_NAME = /(crisis|emergency|harm|suicid|self[-_ ]?harm)/i;
@@ -1175,6 +1343,7 @@ function validateResolvedProfile(profile, options = {}) {
1175
1343
  ...checkS002(profile),
1176
1344
  ...checkS003(profile),
1177
1345
  ...checkS005(profile),
1346
+ ...checkS008(profile),
1178
1347
  ...checkS007(profile),
1179
1348
  ...overspec.diagnostics
1180
1349
  ];
@@ -1618,6 +1787,26 @@ function renderPersonalityText(profile, model, contextResolution, compileOptions
1618
1787
  lines.push(`- ${rule}`);
1619
1788
  }
1620
1789
  }
1790
+ if (profile.schema === "v1.5" && profile.capabilities) {
1791
+ const capabilities = profile.capabilities;
1792
+ const tools = asArray(capabilities.tools);
1793
+ const constraints = asArray(capabilities.constraints);
1794
+ lines.push("");
1795
+ lines.push("[CAPABILITY BOUNDARIES]");
1796
+ lines.push(
1797
+ `Tools: ${tools.length > 0 ? tools.join("; ") : "(none \u2014 advisory only, no side-effect tools configured)"}`
1798
+ );
1799
+ lines.push("Constraints:");
1800
+ if (constraints.length === 0) {
1801
+ lines.push("- (none)");
1802
+ } else {
1803
+ for (const constraint of constraints) {
1804
+ lines.push(`- ${constraint}`);
1805
+ }
1806
+ }
1807
+ lines.push(`Handoff trigger: ${capabilities.handoff.trigger}`);
1808
+ lines.push(`Handoff action: ${capabilities.handoff.action}`);
1809
+ }
1621
1810
  if (contextResolution.matched.length > 0) {
1622
1811
  lines.push("");
1623
1812
  lines.push("[ACTIVE CONTEXT]");
package/dist/internal.cjs CHANGED
@@ -124,6 +124,7 @@ var PASS_THROUGH_FIELDS = /* @__PURE__ */ new Set([
124
124
  "vocabulary",
125
125
  "behavioral_rules",
126
126
  "context_adaptations",
127
+ "capabilities",
127
128
  "extends",
128
129
  "behavioral_rules_remove",
129
130
  "context_adaptations_remove"
@@ -206,6 +207,27 @@ function mergeContextAdaptations(parentAdaptations = [], childAdaptations = [])
206
207
  }
207
208
  return out;
208
209
  }
210
+ function mergeCapabilities(parentCapabilities, childCapabilities) {
211
+ if (!parentCapabilities && !childCapabilities) return void 0;
212
+ if (!parentCapabilities) return clone(childCapabilities);
213
+ if (!childCapabilities) return clone(parentCapabilities);
214
+ const mergedTools = dedupCaseInsensitive([
215
+ ...asArray(parentCapabilities.tools),
216
+ ...asArray(childCapabilities.tools)
217
+ ]);
218
+ const mergedConstraints = dedupCaseInsensitive([
219
+ ...asArray(parentCapabilities.constraints),
220
+ ...asArray(childCapabilities.constraints)
221
+ ]);
222
+ return {
223
+ tools: mergedTools,
224
+ constraints: mergedConstraints,
225
+ handoff: {
226
+ trigger: childCapabilities.handoff?.trigger ?? parentCapabilities.handoff?.trigger ?? "",
227
+ action: childCapabilities.handoff?.action ?? parentCapabilities.handoff?.action ?? ""
228
+ }
229
+ };
230
+ }
209
231
  function removeCaseInsensitive(items, removals) {
210
232
  const removalSet = new Set(
211
233
  asArray(removals).map((item) => String(item).toLowerCase())
@@ -264,6 +286,10 @@ function mergeProfiles(parentProfile, childProfile) {
264
286
  parentProfile.context_adaptations,
265
287
  childProfile.context_adaptations
266
288
  );
289
+ merged.capabilities = mergeCapabilities(
290
+ parentProfile.capabilities,
291
+ childProfile.capabilities
292
+ );
267
293
  for (const [key, value] of Object.entries(childProfile)) {
268
294
  if (PASS_THROUGH_FIELDS.has(key)) {
269
295
  continue;
@@ -392,6 +418,7 @@ function checkOverspec(profile) {
392
418
 
393
419
  // src/validator/schema.ts
394
420
  var HUMOR_STYLES = ["none", "dry", "subtle-wit", "playful"];
421
+ var SUPPORTED_SCHEMAS = /* @__PURE__ */ new Set(["v1.4", "v1.5"]);
395
422
  var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
396
423
  "schema",
397
424
  "meta",
@@ -400,6 +427,7 @@ var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
400
427
  "vocabulary",
401
428
  "behavioral_rules",
402
429
  "context_adaptations",
430
+ "capabilities",
403
431
  "localization",
404
432
  "channel_adaptations",
405
433
  "extends",
@@ -412,6 +440,7 @@ var VOCABULARY_KEYS = /* @__PURE__ */ new Set([
412
440
  "preferred_terms_remove",
413
441
  "forbidden_terms_remove"
414
442
  ]);
443
+ var CAPABILITIES_KEYS = /* @__PURE__ */ new Set(["tools", "constraints", "handoff"]);
415
444
  function isObject(value) {
416
445
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
417
446
  }
@@ -588,7 +617,7 @@ function validateSchema(profile) {
588
617
  `Missing required "schema" field`,
589
618
  "schema"
590
619
  );
591
- } else if (profile.schema !== "v1.4") {
620
+ } else if (!SUPPORTED_SCHEMAS.has(profile.schema)) {
592
621
  pushDiagnostic(
593
622
  structureDiagnostics,
594
623
  "V001",
@@ -736,6 +765,76 @@ function validateSchema(profile) {
736
765
  "behavioral_rules"
737
766
  );
738
767
  }
768
+ if (profile.capabilities != null) {
769
+ if (profile.schema !== "v1.5") {
770
+ pushDiagnostic(
771
+ structureDiagnostics,
772
+ "V001",
773
+ `The "capabilities" section requires schema version "v1.5"`,
774
+ "capabilities"
775
+ );
776
+ }
777
+ if (!isObject(profile.capabilities)) {
778
+ pushDiagnostic(
779
+ structureDiagnostics,
780
+ "V001",
781
+ `Expected "capabilities" to be an object`,
782
+ "capabilities"
783
+ );
784
+ } else {
785
+ for (const key of Object.keys(profile.capabilities)) {
786
+ if (!CAPABILITIES_KEYS.has(key)) {
787
+ pushDiagnostic(
788
+ structureDiagnostics,
789
+ "V001",
790
+ `Unknown capabilities key "${key}"`,
791
+ `capabilities.${key}`
792
+ );
793
+ }
794
+ }
795
+ if (!isStringArray(profile.capabilities.tools)) {
796
+ pushDiagnostic(
797
+ structureDiagnostics,
798
+ "V001",
799
+ `Expected "capabilities.tools" to be an array of strings`,
800
+ "capabilities.tools"
801
+ );
802
+ }
803
+ if (!isStringArray(profile.capabilities.constraints)) {
804
+ pushDiagnostic(
805
+ structureDiagnostics,
806
+ "V001",
807
+ `Expected "capabilities.constraints" to be an array of strings`,
808
+ "capabilities.constraints"
809
+ );
810
+ }
811
+ if (!isObject(profile.capabilities.handoff)) {
812
+ pushDiagnostic(
813
+ structureDiagnostics,
814
+ "V001",
815
+ `Expected "capabilities.handoff" to be an object`,
816
+ "capabilities.handoff"
817
+ );
818
+ } else {
819
+ if (!isString(profile.capabilities.handoff.trigger)) {
820
+ pushDiagnostic(
821
+ structureDiagnostics,
822
+ "V001",
823
+ `Expected "capabilities.handoff.trigger" to be a non-empty string`,
824
+ "capabilities.handoff.trigger"
825
+ );
826
+ }
827
+ if (!isString(profile.capabilities.handoff.action)) {
828
+ pushDiagnostic(
829
+ structureDiagnostics,
830
+ "V001",
831
+ `Expected "capabilities.handoff.action" to be a non-empty string`,
832
+ "capabilities.handoff.action"
833
+ );
834
+ }
835
+ }
836
+ }
837
+ }
739
838
  if (profile.behavioral_rules_remove != null && !isStringArray(profile.behavioral_rules_remove)) {
740
839
  pushDiagnostic(
741
840
  structureDiagnostics,
@@ -952,6 +1051,38 @@ var S005_PATTERNS = [
952
1051
  { id: "mode-switching", regex: /\b(switch|change)\b.*\bmode\b/i },
953
1052
  { id: "jailbreak-language", regex: /\b(jailbreak|dan mode|developer mode)\b/i }
954
1053
  ];
1054
+ var S008_ACTION_PATTERNS = [
1055
+ {
1056
+ id: "take-care-of",
1057
+ regex: /\b(i(?:'ll| will)\s+take\s+care\s+of)\b/i,
1058
+ toolHints: ["case", "ticket", "workflow", "task", "support", "assist"]
1059
+ },
1060
+ {
1061
+ id: "escalate",
1062
+ regex: /\b(i(?:'ll| will)\s+escalat(?:e|ing)|i\s+can\s+escalate)\b/i,
1063
+ toolHints: ["escalat", "ticket"]
1064
+ },
1065
+ {
1066
+ id: "contact-or-notify",
1067
+ regex: /\b(i(?:'ll| will)\s+(?:contact|notify|reach out to|message))\b/i,
1068
+ toolHints: ["contact", "notify", "message", "email", "sms", "ticket"]
1069
+ },
1070
+ {
1071
+ id: "refund-action",
1072
+ regex: /\b(i(?:'ll| will)\s+(?:issue|process|submit)\s+(?:a\s+)?refund)\b/i,
1073
+ toolHints: ["refund", "payment", "billing"]
1074
+ },
1075
+ {
1076
+ id: "schedule-action",
1077
+ regex: /\b(i(?:'ll| will)\s+(?:schedule|book|reschedule))\b/i,
1078
+ toolHints: ["schedule", "calendar", "appointment", "booking"]
1079
+ },
1080
+ {
1081
+ id: "generic-i-will-action",
1082
+ regex: /\b(i(?:'ll| will)\s+(?:handle|resolve|fix|approve|arrange|dispatch|submit))\b/i,
1083
+ toolHints: []
1084
+ }
1085
+ ];
955
1086
  function normalizeText(value) {
956
1087
  return String(value ?? "").trim();
957
1088
  }
@@ -1022,6 +1153,21 @@ function collectS005Candidates(profile) {
1022
1153
  });
1023
1154
  return candidates.filter((item) => item.text.length > 0);
1024
1155
  }
1156
+ function collectS008Candidates(profile) {
1157
+ const candidates = [];
1158
+ asArray(profile?.behavioral_rules).forEach((rule, idx) => {
1159
+ candidates.push({
1160
+ location: `behavioral_rules[${idx}]`,
1161
+ text: normalizeText(rule)
1162
+ });
1163
+ });
1164
+ return candidates.filter((item) => item.text.length > 0);
1165
+ }
1166
+ function hasCapabilityForPattern(tools, pattern) {
1167
+ if (tools.length === 0) return false;
1168
+ if (pattern.toolHints.length === 0) return tools.length > 0;
1169
+ return pattern.toolHints.some((hint) => tools.some((tool) => tool.includes(hint)));
1170
+ }
1025
1171
  function matchPatterns(candidates, patterns, code, severity) {
1026
1172
  const diagnostics = [];
1027
1173
  for (const candidate of candidates) {
@@ -1116,6 +1262,28 @@ function checkS003(profile) {
1116
1262
  function checkS005(profile) {
1117
1263
  return matchPatterns(collectS005Candidates(profile), S005_PATTERNS, "S005", "warning");
1118
1264
  }
1265
+ function checkS008(profile) {
1266
+ if (!profile?.capabilities) return [];
1267
+ const tools = asArray(profile.capabilities.tools).map(
1268
+ (tool) => String(tool).toLowerCase()
1269
+ );
1270
+ const diagnostics = [];
1271
+ for (const candidate of collectS008Candidates(profile)) {
1272
+ for (const pattern of S008_ACTION_PATTERNS) {
1273
+ if (!pattern.regex.test(candidate.text)) continue;
1274
+ if (hasCapabilityForPattern(tools, pattern)) break;
1275
+ const expectedTools = pattern.toolHints.length > 0 ? pattern.toolHints.map((hint) => `"*${hint}*"`).join(", ") : "a concrete action tool";
1276
+ diagnostics.push({
1277
+ code: "S008",
1278
+ severity: "warning",
1279
+ message: `Action-claiming language matched "${pattern.id}" at ${candidate.location}, but capabilities.tools does not indicate support (expected ${expectedTools}).`,
1280
+ location: candidate.location
1281
+ });
1282
+ break;
1283
+ }
1284
+ }
1285
+ return diagnostics;
1286
+ }
1119
1287
 
1120
1288
  // src/validator/inheritance.ts
1121
1289
  var SAFETY_ADAPTATION_NAME = /(crisis|emergency|harm|suicid|self[-_ ]?harm)/i;
@@ -1241,6 +1409,7 @@ function validateResolvedProfile(profile, options = {}) {
1241
1409
  ...checkS002(profile),
1242
1410
  ...checkS003(profile),
1243
1411
  ...checkS005(profile),
1412
+ ...checkS008(profile),
1244
1413
  ...checkS007(profile),
1245
1414
  ...overspec.diagnostics
1246
1415
  ];
@@ -1684,6 +1853,26 @@ function renderPersonalityText(profile, model, contextResolution, compileOptions
1684
1853
  lines.push(`- ${rule}`);
1685
1854
  }
1686
1855
  }
1856
+ if (profile.schema === "v1.5" && profile.capabilities) {
1857
+ const capabilities = profile.capabilities;
1858
+ const tools = asArray(capabilities.tools);
1859
+ const constraints = asArray(capabilities.constraints);
1860
+ lines.push("");
1861
+ lines.push("[CAPABILITY BOUNDARIES]");
1862
+ lines.push(
1863
+ `Tools: ${tools.length > 0 ? tools.join("; ") : "(none \u2014 advisory only, no side-effect tools configured)"}`
1864
+ );
1865
+ lines.push("Constraints:");
1866
+ if (constraints.length === 0) {
1867
+ lines.push("- (none)");
1868
+ } else {
1869
+ for (const constraint of constraints) {
1870
+ lines.push(`- ${constraint}`);
1871
+ }
1872
+ }
1873
+ lines.push(`Handoff trigger: ${capabilities.handoff.trigger}`);
1874
+ lines.push(`Handoff action: ${capabilities.handoff.action}`);
1875
+ }
1687
1876
  if (contextResolution.matched.length > 0) {
1688
1877
  lines.push("");
1689
1878
  lines.push("[ACTIVE CONTEXT]");
@@ -2974,7 +3163,7 @@ function formatDiagnostic(diagnostic) {
2974
3163
  return `${label} [${diagnostic.code}]: ${diagnostic.message}`;
2975
3164
  }
2976
3165
  function countSafetyDiagnostics(diagnostics) {
2977
- return diagnostics.filter((diagnostic) => /^S00[1-7]$/.test(String(diagnostic.code))).length;
3166
+ return diagnostics.filter((diagnostic) => /^S00[1-8]$/.test(String(diagnostic.code))).length;
2978
3167
  }
2979
3168
  function toValidationResultObject(result) {
2980
3169
  return {
@@ -1,5 +1,5 @@
1
- import { V as ValidationResult, a as ValidationCheckSummary, b as ValidationDiagnostic, P as PersonalityProfile, r as runTier1Evaluation } from './index-CkGiIKnu.cjs';
2
- export { C as CompileOptions, c as CompiledPersonality, d as ContextAdaptation, e as ContextResolution, D as DimensionName, f as DimensionObject, g as DimensionShorthand, h as DimensionValue, E as EvalSample, i as ExtendsDiagnostics, j as ExtendsResult, H as HumorDimensionObject, k as HumorDimensionValue, l as HumorStyle, I as ImportOptions, L as Level, T as Tier1Options, m as Tier2Options, n as Tier3Options, o as VocabularyConstraints, p as compileProfile, q as compileResolvedProfile, s as evaluateTier1Response, t as injectPersonality, u as loadProfileFile, v as mapImportAnalysisToProfile, w as normalizeProfile, x as renderImportedProfileYAML, y as resolveActiveContext, z as resolveExtends, A as runImportAnalysis, B as runTier1EvaluationForProfile, F as runTier2Evaluation, G as runTier2EvaluationForProfile, J as runTier3Evaluation, K as runTier3EvaluationForProfile, M as validateEvalScenario, N as validateEvalScenarios, O as validateProfile, Q as validateResolvedProfile } from './index-CkGiIKnu.cjs';
1
+ import { V as ValidationResult, a as ValidationCheckSummary, b as ValidationDiagnostic, P as PersonalityProfile, r as runTier1Evaluation } from './index-1c7xQG2q.cjs';
2
+ export { C as CapabilityHandoff, c as CompileOptions, d as CompiledPersonality, e as ContextAdaptation, f as ContextResolution, D as DimensionName, g as DimensionObject, h as DimensionShorthand, i as DimensionValue, E as EvalSample, j as ExtendsDiagnostics, k as ExtendsResult, H as HumorDimensionObject, l as HumorDimensionValue, m as HumorStyle, I as ImportOptions, L as Level, n as ProfileCapabilities, T as Tier1Options, o as Tier2Options, p as Tier3Options, q as VocabularyConstraints, s as compileProfile, t as compileResolvedProfile, u as evaluateTier1Response, v as injectPersonality, w as loadProfileFile, x as mapImportAnalysisToProfile, y as normalizeProfile, z as renderImportedProfileYAML, A as resolveActiveContext, B as resolveExtends, F as runImportAnalysis, G as runTier1EvaluationForProfile, J as runTier2Evaluation, K as runTier2EvaluationForProfile, M as runTier3Evaluation, N as runTier3EvaluationForProfile, O as validateEvalScenario, Q as validateEvalScenarios, R as validateProfile, S as validateResolvedProfile } from './index-1c7xQG2q.cjs';
3
3
 
4
4
  type ValidationResultObject = {
5
5
  profilePath: string | null;
@@ -1,5 +1,5 @@
1
- import { V as ValidationResult, a as ValidationCheckSummary, b as ValidationDiagnostic, P as PersonalityProfile, r as runTier1Evaluation } from './index-CkGiIKnu.js';
2
- export { C as CompileOptions, c as CompiledPersonality, d as ContextAdaptation, e as ContextResolution, D as DimensionName, f as DimensionObject, g as DimensionShorthand, h as DimensionValue, E as EvalSample, i as ExtendsDiagnostics, j as ExtendsResult, H as HumorDimensionObject, k as HumorDimensionValue, l as HumorStyle, I as ImportOptions, L as Level, T as Tier1Options, m as Tier2Options, n as Tier3Options, o as VocabularyConstraints, p as compileProfile, q as compileResolvedProfile, s as evaluateTier1Response, t as injectPersonality, u as loadProfileFile, v as mapImportAnalysisToProfile, w as normalizeProfile, x as renderImportedProfileYAML, y as resolveActiveContext, z as resolveExtends, A as runImportAnalysis, B as runTier1EvaluationForProfile, F as runTier2Evaluation, G as runTier2EvaluationForProfile, J as runTier3Evaluation, K as runTier3EvaluationForProfile, M as validateEvalScenario, N as validateEvalScenarios, O as validateProfile, Q as validateResolvedProfile } from './index-CkGiIKnu.js';
1
+ import { V as ValidationResult, a as ValidationCheckSummary, b as ValidationDiagnostic, P as PersonalityProfile, r as runTier1Evaluation } from './index-1c7xQG2q.js';
2
+ export { C as CapabilityHandoff, c as CompileOptions, d as CompiledPersonality, e as ContextAdaptation, f as ContextResolution, D as DimensionName, g as DimensionObject, h as DimensionShorthand, i as DimensionValue, E as EvalSample, j as ExtendsDiagnostics, k as ExtendsResult, H as HumorDimensionObject, l as HumorDimensionValue, m as HumorStyle, I as ImportOptions, L as Level, n as ProfileCapabilities, T as Tier1Options, o as Tier2Options, p as Tier3Options, q as VocabularyConstraints, s as compileProfile, t as compileResolvedProfile, u as evaluateTier1Response, v as injectPersonality, w as loadProfileFile, x as mapImportAnalysisToProfile, y as normalizeProfile, z as renderImportedProfileYAML, A as resolveActiveContext, B as resolveExtends, F as runImportAnalysis, G as runTier1EvaluationForProfile, J as runTier2Evaluation, K as runTier2EvaluationForProfile, M as runTier3Evaluation, N as runTier3EvaluationForProfile, O as validateEvalScenario, Q as validateEvalScenarios, R as validateProfile, S as validateResolvedProfile } from './index-1c7xQG2q.js';
3
3
 
4
4
  type ValidationResultObject = {
5
5
  profilePath: string | null;
package/dist/internal.js CHANGED
@@ -58,6 +58,7 @@ var PASS_THROUGH_FIELDS = /* @__PURE__ */ new Set([
58
58
  "vocabulary",
59
59
  "behavioral_rules",
60
60
  "context_adaptations",
61
+ "capabilities",
61
62
  "extends",
62
63
  "behavioral_rules_remove",
63
64
  "context_adaptations_remove"
@@ -140,6 +141,27 @@ function mergeContextAdaptations(parentAdaptations = [], childAdaptations = [])
140
141
  }
141
142
  return out;
142
143
  }
144
+ function mergeCapabilities(parentCapabilities, childCapabilities) {
145
+ if (!parentCapabilities && !childCapabilities) return void 0;
146
+ if (!parentCapabilities) return clone(childCapabilities);
147
+ if (!childCapabilities) return clone(parentCapabilities);
148
+ const mergedTools = dedupCaseInsensitive([
149
+ ...asArray(parentCapabilities.tools),
150
+ ...asArray(childCapabilities.tools)
151
+ ]);
152
+ const mergedConstraints = dedupCaseInsensitive([
153
+ ...asArray(parentCapabilities.constraints),
154
+ ...asArray(childCapabilities.constraints)
155
+ ]);
156
+ return {
157
+ tools: mergedTools,
158
+ constraints: mergedConstraints,
159
+ handoff: {
160
+ trigger: childCapabilities.handoff?.trigger ?? parentCapabilities.handoff?.trigger ?? "",
161
+ action: childCapabilities.handoff?.action ?? parentCapabilities.handoff?.action ?? ""
162
+ }
163
+ };
164
+ }
143
165
  function removeCaseInsensitive(items, removals) {
144
166
  const removalSet = new Set(
145
167
  asArray(removals).map((item) => String(item).toLowerCase())
@@ -198,6 +220,10 @@ function mergeProfiles(parentProfile, childProfile) {
198
220
  parentProfile.context_adaptations,
199
221
  childProfile.context_adaptations
200
222
  );
223
+ merged.capabilities = mergeCapabilities(
224
+ parentProfile.capabilities,
225
+ childProfile.capabilities
226
+ );
201
227
  for (const [key, value] of Object.entries(childProfile)) {
202
228
  if (PASS_THROUGH_FIELDS.has(key)) {
203
229
  continue;
@@ -326,6 +352,7 @@ function checkOverspec(profile) {
326
352
 
327
353
  // src/validator/schema.ts
328
354
  var HUMOR_STYLES = ["none", "dry", "subtle-wit", "playful"];
355
+ var SUPPORTED_SCHEMAS = /* @__PURE__ */ new Set(["v1.4", "v1.5"]);
329
356
  var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
330
357
  "schema",
331
358
  "meta",
@@ -334,6 +361,7 @@ var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
334
361
  "vocabulary",
335
362
  "behavioral_rules",
336
363
  "context_adaptations",
364
+ "capabilities",
337
365
  "localization",
338
366
  "channel_adaptations",
339
367
  "extends",
@@ -346,6 +374,7 @@ var VOCABULARY_KEYS = /* @__PURE__ */ new Set([
346
374
  "preferred_terms_remove",
347
375
  "forbidden_terms_remove"
348
376
  ]);
377
+ var CAPABILITIES_KEYS = /* @__PURE__ */ new Set(["tools", "constraints", "handoff"]);
349
378
  function isObject(value) {
350
379
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
351
380
  }
@@ -522,7 +551,7 @@ function validateSchema(profile) {
522
551
  `Missing required "schema" field`,
523
552
  "schema"
524
553
  );
525
- } else if (profile.schema !== "v1.4") {
554
+ } else if (!SUPPORTED_SCHEMAS.has(profile.schema)) {
526
555
  pushDiagnostic(
527
556
  structureDiagnostics,
528
557
  "V001",
@@ -670,6 +699,76 @@ function validateSchema(profile) {
670
699
  "behavioral_rules"
671
700
  );
672
701
  }
702
+ if (profile.capabilities != null) {
703
+ if (profile.schema !== "v1.5") {
704
+ pushDiagnostic(
705
+ structureDiagnostics,
706
+ "V001",
707
+ `The "capabilities" section requires schema version "v1.5"`,
708
+ "capabilities"
709
+ );
710
+ }
711
+ if (!isObject(profile.capabilities)) {
712
+ pushDiagnostic(
713
+ structureDiagnostics,
714
+ "V001",
715
+ `Expected "capabilities" to be an object`,
716
+ "capabilities"
717
+ );
718
+ } else {
719
+ for (const key of Object.keys(profile.capabilities)) {
720
+ if (!CAPABILITIES_KEYS.has(key)) {
721
+ pushDiagnostic(
722
+ structureDiagnostics,
723
+ "V001",
724
+ `Unknown capabilities key "${key}"`,
725
+ `capabilities.${key}`
726
+ );
727
+ }
728
+ }
729
+ if (!isStringArray(profile.capabilities.tools)) {
730
+ pushDiagnostic(
731
+ structureDiagnostics,
732
+ "V001",
733
+ `Expected "capabilities.tools" to be an array of strings`,
734
+ "capabilities.tools"
735
+ );
736
+ }
737
+ if (!isStringArray(profile.capabilities.constraints)) {
738
+ pushDiagnostic(
739
+ structureDiagnostics,
740
+ "V001",
741
+ `Expected "capabilities.constraints" to be an array of strings`,
742
+ "capabilities.constraints"
743
+ );
744
+ }
745
+ if (!isObject(profile.capabilities.handoff)) {
746
+ pushDiagnostic(
747
+ structureDiagnostics,
748
+ "V001",
749
+ `Expected "capabilities.handoff" to be an object`,
750
+ "capabilities.handoff"
751
+ );
752
+ } else {
753
+ if (!isString(profile.capabilities.handoff.trigger)) {
754
+ pushDiagnostic(
755
+ structureDiagnostics,
756
+ "V001",
757
+ `Expected "capabilities.handoff.trigger" to be a non-empty string`,
758
+ "capabilities.handoff.trigger"
759
+ );
760
+ }
761
+ if (!isString(profile.capabilities.handoff.action)) {
762
+ pushDiagnostic(
763
+ structureDiagnostics,
764
+ "V001",
765
+ `Expected "capabilities.handoff.action" to be a non-empty string`,
766
+ "capabilities.handoff.action"
767
+ );
768
+ }
769
+ }
770
+ }
771
+ }
673
772
  if (profile.behavioral_rules_remove != null && !isStringArray(profile.behavioral_rules_remove)) {
674
773
  pushDiagnostic(
675
774
  structureDiagnostics,
@@ -886,6 +985,38 @@ var S005_PATTERNS = [
886
985
  { id: "mode-switching", regex: /\b(switch|change)\b.*\bmode\b/i },
887
986
  { id: "jailbreak-language", regex: /\b(jailbreak|dan mode|developer mode)\b/i }
888
987
  ];
988
+ var S008_ACTION_PATTERNS = [
989
+ {
990
+ id: "take-care-of",
991
+ regex: /\b(i(?:'ll| will)\s+take\s+care\s+of)\b/i,
992
+ toolHints: ["case", "ticket", "workflow", "task", "support", "assist"]
993
+ },
994
+ {
995
+ id: "escalate",
996
+ regex: /\b(i(?:'ll| will)\s+escalat(?:e|ing)|i\s+can\s+escalate)\b/i,
997
+ toolHints: ["escalat", "ticket"]
998
+ },
999
+ {
1000
+ id: "contact-or-notify",
1001
+ regex: /\b(i(?:'ll| will)\s+(?:contact|notify|reach out to|message))\b/i,
1002
+ toolHints: ["contact", "notify", "message", "email", "sms", "ticket"]
1003
+ },
1004
+ {
1005
+ id: "refund-action",
1006
+ regex: /\b(i(?:'ll| will)\s+(?:issue|process|submit)\s+(?:a\s+)?refund)\b/i,
1007
+ toolHints: ["refund", "payment", "billing"]
1008
+ },
1009
+ {
1010
+ id: "schedule-action",
1011
+ regex: /\b(i(?:'ll| will)\s+(?:schedule|book|reschedule))\b/i,
1012
+ toolHints: ["schedule", "calendar", "appointment", "booking"]
1013
+ },
1014
+ {
1015
+ id: "generic-i-will-action",
1016
+ regex: /\b(i(?:'ll| will)\s+(?:handle|resolve|fix|approve|arrange|dispatch|submit))\b/i,
1017
+ toolHints: []
1018
+ }
1019
+ ];
889
1020
  function normalizeText(value) {
890
1021
  return String(value ?? "").trim();
891
1022
  }
@@ -956,6 +1087,21 @@ function collectS005Candidates(profile) {
956
1087
  });
957
1088
  return candidates.filter((item) => item.text.length > 0);
958
1089
  }
1090
+ function collectS008Candidates(profile) {
1091
+ const candidates = [];
1092
+ asArray(profile?.behavioral_rules).forEach((rule, idx) => {
1093
+ candidates.push({
1094
+ location: `behavioral_rules[${idx}]`,
1095
+ text: normalizeText(rule)
1096
+ });
1097
+ });
1098
+ return candidates.filter((item) => item.text.length > 0);
1099
+ }
1100
+ function hasCapabilityForPattern(tools, pattern) {
1101
+ if (tools.length === 0) return false;
1102
+ if (pattern.toolHints.length === 0) return tools.length > 0;
1103
+ return pattern.toolHints.some((hint) => tools.some((tool) => tool.includes(hint)));
1104
+ }
959
1105
  function matchPatterns(candidates, patterns, code, severity) {
960
1106
  const diagnostics = [];
961
1107
  for (const candidate of candidates) {
@@ -1050,6 +1196,28 @@ function checkS003(profile) {
1050
1196
  function checkS005(profile) {
1051
1197
  return matchPatterns(collectS005Candidates(profile), S005_PATTERNS, "S005", "warning");
1052
1198
  }
1199
+ function checkS008(profile) {
1200
+ if (!profile?.capabilities) return [];
1201
+ const tools = asArray(profile.capabilities.tools).map(
1202
+ (tool) => String(tool).toLowerCase()
1203
+ );
1204
+ const diagnostics = [];
1205
+ for (const candidate of collectS008Candidates(profile)) {
1206
+ for (const pattern of S008_ACTION_PATTERNS) {
1207
+ if (!pattern.regex.test(candidate.text)) continue;
1208
+ if (hasCapabilityForPattern(tools, pattern)) break;
1209
+ const expectedTools = pattern.toolHints.length > 0 ? pattern.toolHints.map((hint) => `"*${hint}*"`).join(", ") : "a concrete action tool";
1210
+ diagnostics.push({
1211
+ code: "S008",
1212
+ severity: "warning",
1213
+ message: `Action-claiming language matched "${pattern.id}" at ${candidate.location}, but capabilities.tools does not indicate support (expected ${expectedTools}).`,
1214
+ location: candidate.location
1215
+ });
1216
+ break;
1217
+ }
1218
+ }
1219
+ return diagnostics;
1220
+ }
1053
1221
 
1054
1222
  // src/validator/inheritance.ts
1055
1223
  var SAFETY_ADAPTATION_NAME = /(crisis|emergency|harm|suicid|self[-_ ]?harm)/i;
@@ -1175,6 +1343,7 @@ function validateResolvedProfile(profile, options = {}) {
1175
1343
  ...checkS002(profile),
1176
1344
  ...checkS003(profile),
1177
1345
  ...checkS005(profile),
1346
+ ...checkS008(profile),
1178
1347
  ...checkS007(profile),
1179
1348
  ...overspec.diagnostics
1180
1349
  ];
@@ -1618,6 +1787,26 @@ function renderPersonalityText(profile, model, contextResolution, compileOptions
1618
1787
  lines.push(`- ${rule}`);
1619
1788
  }
1620
1789
  }
1790
+ if (profile.schema === "v1.5" && profile.capabilities) {
1791
+ const capabilities = profile.capabilities;
1792
+ const tools = asArray(capabilities.tools);
1793
+ const constraints = asArray(capabilities.constraints);
1794
+ lines.push("");
1795
+ lines.push("[CAPABILITY BOUNDARIES]");
1796
+ lines.push(
1797
+ `Tools: ${tools.length > 0 ? tools.join("; ") : "(none \u2014 advisory only, no side-effect tools configured)"}`
1798
+ );
1799
+ lines.push("Constraints:");
1800
+ if (constraints.length === 0) {
1801
+ lines.push("- (none)");
1802
+ } else {
1803
+ for (const constraint of constraints) {
1804
+ lines.push(`- ${constraint}`);
1805
+ }
1806
+ }
1807
+ lines.push(`Handoff trigger: ${capabilities.handoff.trigger}`);
1808
+ lines.push(`Handoff action: ${capabilities.handoff.action}`);
1809
+ }
1621
1810
  if (contextResolution.matched.length > 0) {
1622
1811
  lines.push("");
1623
1812
  lines.push("[ACTIVE CONTEXT]");
@@ -2908,7 +3097,7 @@ function formatDiagnostic(diagnostic) {
2908
3097
  return `${label} [${diagnostic.code}]: ${diagnostic.message}`;
2909
3098
  }
2910
3099
  function countSafetyDiagnostics(diagnostics) {
2911
- return diagnostics.filter((diagnostic) => /^S00[1-7]$/.test(String(diagnostic.code))).length;
3100
+ return diagnostics.filter((diagnostic) => /^S00[1-8]$/.test(String(diagnostic.code))).length;
2912
3101
  }
2913
3102
  function toValidationResultObject(result) {
2914
3103
  return {
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@traits-dev/core",
3
- "version": "0.1.0",
4
- "description": "Core traits.dev SDK for personality profile validation, compilation, and evaluation.",
3
+ "version": "0.2.0",
4
+ "description": "Core traits.dev SDK for voice profile validation, behavioral policy compilation, and evaluation.",
5
5
  "keywords": [
6
6
  "traits-dev",
7
7
  "llm",
8
- "personality",
8
+ "voice-profile",
9
+ "behavioral-policy",
9
10
  "prompt-engineering",
10
11
  "evaluation"
11
12
  ],