@auxiora/personality 1.0.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/__tests__/architect-awareness-collector.test.d.ts +2 -0
  2. package/dist/__tests__/architect-awareness-collector.test.d.ts.map +1 -0
  3. package/dist/__tests__/architect-awareness-collector.test.js +57 -0
  4. package/dist/__tests__/architect-awareness-collector.test.js.map +1 -0
  5. package/dist/__tests__/architect-bridge.test.d.ts +2 -0
  6. package/dist/__tests__/architect-bridge.test.d.ts.map +1 -0
  7. package/dist/__tests__/architect-bridge.test.js +59 -0
  8. package/dist/__tests__/architect-bridge.test.js.map +1 -0
  9. package/dist/__tests__/soul-bias-parser.test.d.ts +2 -0
  10. package/dist/__tests__/soul-bias-parser.test.d.ts.map +1 -0
  11. package/dist/__tests__/soul-bias-parser.test.js +47 -0
  12. package/dist/__tests__/soul-bias-parser.test.js.map +1 -0
  13. package/dist/architect-awareness-collector.d.ts +20 -0
  14. package/dist/architect-awareness-collector.d.ts.map +1 -0
  15. package/dist/architect-awareness-collector.js +41 -0
  16. package/dist/architect-awareness-collector.js.map +1 -0
  17. package/dist/architect-bridge.d.ts +35 -0
  18. package/dist/architect-bridge.d.ts.map +1 -0
  19. package/dist/architect-bridge.js +70 -0
  20. package/dist/architect-bridge.js.map +1 -0
  21. package/dist/index.d.ts +5 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +6 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/marketplace/schema.d.ts +6 -6
  26. package/dist/soul-bias-parser.d.ts +10 -0
  27. package/dist/soul-bias-parser.d.ts.map +1 -0
  28. package/dist/soul-bias-parser.js +48 -0
  29. package/dist/soul-bias-parser.js.map +1 -0
  30. package/lib/context-detector.d.ts +23 -0
  31. package/lib/context-detector.js +275 -0
  32. package/lib/context-profiles.d.ts +3 -0
  33. package/lib/context-profiles.js +550 -0
  34. package/lib/conversation-context.d.ts +70 -0
  35. package/lib/conversation-context.js +144 -0
  36. package/lib/conversation-export.d.ts +77 -0
  37. package/lib/conversation-export.js +254 -0
  38. package/lib/correction-store.d.ts +53 -0
  39. package/lib/correction-store.js +185 -0
  40. package/lib/custom-weights.d.ts +49 -0
  41. package/lib/custom-weights.js +181 -0
  42. package/lib/decision-log.d.ts +41 -0
  43. package/lib/decision-log.js +145 -0
  44. package/lib/emotional-overrides.d.ts +14 -0
  45. package/lib/emotional-overrides.js +86 -0
  46. package/lib/emotional-tracker.d.ts +41 -0
  47. package/lib/emotional-tracker.js +210 -0
  48. package/lib/feedback-store.d.ts +45 -0
  49. package/lib/feedback-store.js +152 -0
  50. package/lib/index.d.ts +204 -0
  51. package/lib/index.d.ts.map +1 -0
  52. package/lib/index.js +620 -0
  53. package/lib/index.js.map +1 -0
  54. package/lib/persistence-adapter.d.ts +55 -0
  55. package/lib/persistence-adapter.js +50 -0
  56. package/lib/persistence.d.ts +55 -0
  57. package/lib/persistence.js +123 -0
  58. package/lib/preference-history.d.ts +45 -0
  59. package/lib/preference-history.js +132 -0
  60. package/lib/prompt-assembler.d.ts +16 -0
  61. package/lib/prompt-assembler.js +66 -0
  62. package/lib/recommender.d.ts +25 -0
  63. package/lib/recommender.js +125 -0
  64. package/lib/schema.d.ts +177 -0
  65. package/lib/schema.d.ts.map +1 -0
  66. package/lib/schema.js +2 -0
  67. package/lib/schema.js.map +1 -0
  68. package/lib/source-map.d.ts +9 -0
  69. package/lib/source-map.js +223 -0
  70. package/lib/system-prompt.d.ts +2 -0
  71. package/lib/system-prompt.js +102 -0
  72. package/lib/the-architect/context-detector.d.ts +23 -0
  73. package/lib/the-architect/context-detector.d.ts.map +1 -0
  74. package/lib/the-architect/context-detector.js +275 -0
  75. package/lib/the-architect/context-detector.js.map +1 -0
  76. package/lib/the-architect/context-profiles.d.ts +3 -0
  77. package/lib/the-architect/context-profiles.d.ts.map +1 -0
  78. package/lib/the-architect/context-profiles.js +550 -0
  79. package/lib/the-architect/context-profiles.js.map +1 -0
  80. package/lib/the-architect/conversation-context.d.ts +70 -0
  81. package/lib/the-architect/conversation-context.js +144 -0
  82. package/lib/the-architect/conversation-context.js.map +1 -0
  83. package/lib/the-architect/conversation-export.d.ts +77 -0
  84. package/lib/the-architect/conversation-export.js +254 -0
  85. package/lib/the-architect/correction-store.d.ts +53 -0
  86. package/lib/the-architect/correction-store.d.ts.map +1 -0
  87. package/lib/the-architect/correction-store.js +185 -0
  88. package/lib/the-architect/correction-store.js.map +1 -0
  89. package/lib/the-architect/custom-weights.d.ts +49 -0
  90. package/lib/the-architect/custom-weights.js +181 -0
  91. package/lib/the-architect/decision-log.d.ts +41 -0
  92. package/lib/the-architect/decision-log.d.ts.map +1 -0
  93. package/lib/the-architect/decision-log.js +145 -0
  94. package/lib/the-architect/decision-log.js.map +1 -0
  95. package/lib/the-architect/emotional-overrides.d.ts +14 -0
  96. package/lib/the-architect/emotional-overrides.d.ts.map +1 -0
  97. package/lib/the-architect/emotional-overrides.js +86 -0
  98. package/lib/the-architect/emotional-overrides.js.map +1 -0
  99. package/lib/the-architect/emotional-tracker.d.ts +41 -0
  100. package/lib/the-architect/emotional-tracker.js +210 -0
  101. package/lib/the-architect/feedback-store.d.ts +45 -0
  102. package/lib/the-architect/feedback-store.d.ts.map +1 -0
  103. package/lib/the-architect/feedback-store.js +152 -0
  104. package/lib/the-architect/feedback-store.js.map +1 -0
  105. package/lib/the-architect/index.d.ts +199 -0
  106. package/lib/the-architect/index.d.ts.map +1 -0
  107. package/lib/the-architect/index.js +606 -0
  108. package/lib/the-architect/index.js.map +1 -0
  109. package/lib/the-architect/persistence-adapter.d.ts +55 -0
  110. package/lib/the-architect/persistence-adapter.js +50 -0
  111. package/lib/the-architect/persistence.d.ts +55 -0
  112. package/lib/the-architect/persistence.js +123 -0
  113. package/lib/the-architect/preference-history.d.ts +45 -0
  114. package/lib/the-architect/preference-history.d.ts.map +1 -0
  115. package/lib/the-architect/preference-history.js +132 -0
  116. package/lib/the-architect/preference-history.js.map +1 -0
  117. package/lib/the-architect/prompt-assembler.d.ts +16 -0
  118. package/lib/the-architect/prompt-assembler.d.ts.map +1 -0
  119. package/lib/the-architect/prompt-assembler.js +66 -0
  120. package/lib/the-architect/prompt-assembler.js.map +1 -0
  121. package/lib/the-architect/recommender.d.ts +25 -0
  122. package/lib/the-architect/recommender.js +125 -0
  123. package/lib/the-architect/source-map.d.ts +9 -0
  124. package/lib/the-architect/source-map.d.ts.map +1 -0
  125. package/lib/the-architect/source-map.js +223 -0
  126. package/lib/the-architect/source-map.js.map +1 -0
  127. package/lib/the-architect/system-prompt.d.ts +2 -0
  128. package/lib/the-architect/system-prompt.d.ts.map +1 -0
  129. package/lib/the-architect/system-prompt.js +102 -0
  130. package/lib/the-architect/system-prompt.js.map +1 -0
  131. package/lib/the-architect/trait-to-instruction.d.ts +12 -0
  132. package/lib/the-architect/trait-to-instruction.d.ts.map +1 -0
  133. package/lib/the-architect/trait-to-instruction.js +330 -0
  134. package/lib/the-architect/trait-to-instruction.js.map +1 -0
  135. package/lib/trait-to-instruction.d.ts +12 -0
  136. package/lib/trait-to-instruction.js +330 -0
  137. package/lib/user-model-synthesizer.d.ts +100 -0
  138. package/lib/user-model-synthesizer.js +224 -0
  139. package/package.json +15 -3
  140. package/modes/advisor.md +0 -24
  141. package/modes/analyst.md +0 -25
  142. package/modes/companion.md +0 -24
  143. package/modes/legal.md +0 -1188
  144. package/modes/operator.md +0 -24
  145. package/modes/roast.md +0 -24
  146. package/modes/socratic.md +0 -24
  147. package/modes/writer.md +0 -23
  148. package/src/__tests__/builder.test.ts +0 -78
  149. package/src/__tests__/conversation-builder.test.ts +0 -386
  150. package/src/__tests__/escalation.test.ts +0 -172
  151. package/src/__tests__/manager.test.ts +0 -141
  152. package/src/__tests__/parser.test.ts +0 -101
  153. package/src/__tests__/security-floor.test.ts +0 -212
  154. package/src/builder.ts +0 -75
  155. package/src/conversation-builder.ts +0 -279
  156. package/src/escalation.ts +0 -162
  157. package/src/index.ts +0 -55
  158. package/src/manager.ts +0 -119
  159. package/src/marketplace/__tests__/scanner.test.ts +0 -159
  160. package/src/marketplace/__tests__/schema.test.ts +0 -269
  161. package/src/marketplace/scanner.ts +0 -85
  162. package/src/marketplace/schema.ts +0 -141
  163. package/src/modes/__tests__/mode-detector.test.ts +0 -149
  164. package/src/modes/__tests__/mode-loader.test.ts +0 -143
  165. package/src/modes/__tests__/prompt-assembler.test.ts +0 -291
  166. package/src/modes/mode-detector.ts +0 -84
  167. package/src/modes/mode-loader.ts +0 -105
  168. package/src/modes/prompt-assembler.ts +0 -278
  169. package/src/modes/types.ts +0 -67
  170. package/src/parser.ts +0 -132
  171. package/src/security-floor.ts +0 -147
  172. package/src/types.ts +0 -27
  173. package/src/voice-profiles.ts +0 -88
  174. package/templates/chill.md +0 -30
  175. package/templates/creative.md +0 -29
  176. package/templates/friendly.md +0 -28
  177. package/templates/mentor.md +0 -31
  178. package/templates/minimal.md +0 -24
  179. package/templates/professional.md +0 -28
  180. package/templates/technical.md +0 -30
  181. package/tsconfig.json +0 -12
  182. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,86 @@
1
+ // ────────────────────────────────────────────────────────────────────────────
2
+ // Emotional overrides
3
+ // ────────────────────────────────────────────────────────────────────────────
4
+ /**
5
+ * Multipliers applied on top of the context profile's base trait values.
6
+ * Values > 1.0 amplify a trait; values < 1.0 dampen it.
7
+ * After multiplication, all traits are capped at 1.0 by `applyEmotionalOverride`.
8
+ */
9
+ export const EMOTIONAL_OVERRIDES = {
10
+ /** Slow everything down, lead with empathy and calm — suppress urgency and adversarial edges. */
11
+ stressed: {
12
+ stoicCalm: 1.5,
13
+ tacticalEmpathy: 1.4,
14
+ genuineCuriosity: 1.3,
15
+ warmth: 1.4,
16
+ urgency: 0.3,
17
+ paranoidVigilance: 0.4,
18
+ adversarialThinking: 0.4,
19
+ valueEquation: 0.5,
20
+ humor: 0.5,
21
+ verbosity: 0.7,
22
+ },
23
+ /** Stay calm, lean into empathy and humor to de-escalate — soften candor, suppress adversarial framing. */
24
+ frustrated: {
25
+ stoicCalm: 1.3,
26
+ tacticalEmpathy: 1.5,
27
+ genuineCuriosity: 1.3,
28
+ warmth: 1.3,
29
+ humor: 1.3,
30
+ inversion: 0.6,
31
+ adversarialThinking: 0.5,
32
+ radicalCandor: 0.7,
33
+ urgency: 0.5,
34
+ },
35
+ /** Coach and simplify — break complexity into digestible pieces, suppress anything intimidating. */
36
+ uncertain: {
37
+ developmentalCoaching: 1.5,
38
+ simplification: 1.4,
39
+ storytelling: 1.3,
40
+ warmth: 1.3,
41
+ firstPrinciples: 1.3,
42
+ adversarialThinking: 0.4,
43
+ paranoidVigilance: 0.4,
44
+ urgency: 0.4,
45
+ radicalCandor: 0.7,
46
+ },
47
+ /** Channel the energy productively — amplify execution and analytical rigor while riding the momentum. */
48
+ excited: {
49
+ ooda: 1.3,
50
+ valueEquation: 1.3,
51
+ humor: 1.3,
52
+ warmth: 1.2,
53
+ inversion: 1.2,
54
+ secondOrder: 1.3,
55
+ probabilistic: 1.2,
56
+ },
57
+ /** Celebrate genuinely, then pivot to systematizing the win — suppress paranoia to let the moment land. */
58
+ celebratory: {
59
+ warmth: 1.5,
60
+ humor: 1.4,
61
+ strategicGenerosity: 1.3,
62
+ plannedAbandonment: 0.5,
63
+ adversarialThinking: 0.3,
64
+ paranoidVigilance: 0.3,
65
+ },
66
+ /** No modification — the context profile speaks for itself. */
67
+ neutral: {},
68
+ };
69
+ // ────────────────────────────────────────────────────────────────────────────
70
+ // Application
71
+ // ────────────────────────────────────────────────────────────────────────────
72
+ /**
73
+ * Applies emotional multipliers to a base trait mix, producing a new TraitMix
74
+ * where each trait is multiplied by its override value (defaulting to 1.0)
75
+ * and capped at 1.0.
76
+ */
77
+ export function applyEmotionalOverride(baseMix, emotion) {
78
+ const overrides = EMOTIONAL_OVERRIDES[emotion];
79
+ const result = { ...baseMix };
80
+ for (const key of Object.keys(result)) {
81
+ const multiplier = overrides[key] ?? 1.0;
82
+ result[key] = Math.min(result[key] * multiplier, 1.0);
83
+ }
84
+ return result;
85
+ }
86
+ //# sourceMappingURL=emotional-overrides.js.map
@@ -0,0 +1,41 @@
1
+ import type { EmotionalRegister } from '../schema.js';
2
+ export type EmotionalTrajectory = 'stable' | 'escalating' | 'de_escalating' | 'shifting' | 'volatile';
3
+ export interface EffectiveEmotion {
4
+ emotion: EmotionalRegister;
5
+ intensity: number;
6
+ trajectory: EmotionalTrajectory;
7
+ escalationAlert: boolean;
8
+ }
9
+ /**
10
+ * Estimate emotional intensity from message text.
11
+ *
12
+ * Heuristic signals:
13
+ * - Excessive punctuation (!! or ??)
14
+ * - ALL CAPS segments
15
+ * - Explicit intensity words ("extremely", "so", "really", "very")
16
+ * - Profanity / frustration markers
17
+ * - Message length (longer rants → higher intensity)
18
+ */
19
+ export declare function estimateIntensity(message: string, emotion: EmotionalRegister): number;
20
+ /**
21
+ * Tracks emotional state across multiple messages, detecting escalation
22
+ * patterns that a single-message detector would miss.
23
+ *
24
+ * If a user is getting progressively more frustrated over 4-5 messages,
25
+ * the engine should amplify empathy before they reach a breaking point.
26
+ */
27
+ export declare class EmotionalTracker {
28
+ private history;
29
+ /** Record a detected emotion with its intensity and source message. */
30
+ recordEmotion(emotion: EmotionalRegister, intensity: number, message: string): void;
31
+ /**
32
+ * Get the effective emotional state considering trajectory across the
33
+ * conversation window.
34
+ */
35
+ getEffectiveEmotion(): EffectiveEmotion;
36
+ private detectTrajectory;
37
+ private mode;
38
+ /** Reset for a new conversation — clears all history. */
39
+ reset(): void;
40
+ }
41
+ //# sourceMappingURL=emotional-tracker.d.ts.map
@@ -0,0 +1,210 @@
1
+ // ────────────────────────────────────────────────────────────────────────────
2
+ // Constants
3
+ // ────────────────────────────────────────────────────────────────────────────
4
+ /** How many recent messages the sliding window considers. */
5
+ const WINDOW_SIZE = 5;
6
+ /** Emotions considered negative for escalation detection. */
7
+ const NEGATIVE_EMOTIONS = new Set(['frustrated', 'stressed']);
8
+ /** Minimum consecutive negative-and-increasing entries for escalation. */
9
+ const ESCALATION_MIN_STREAK = 3;
10
+ /** Minimum messages and intensity threshold for escalation alert. */
11
+ const ALERT_MIN_MESSAGES = 4;
12
+ const ALERT_INTENSITY_THRESHOLD = 0.7;
13
+ /** Minimum emotion changes in the window for volatile classification. */
14
+ const VOLATILE_CHANGE_THRESHOLD = 3;
15
+ // ────────────────────────────────────────────────────────────────────────────
16
+ // Intensity estimation
17
+ // ────────────────────────────────────────────────────────────────────────────
18
+ /**
19
+ * Estimate emotional intensity from message text.
20
+ *
21
+ * Heuristic signals:
22
+ * - Excessive punctuation (!! or ??)
23
+ * - ALL CAPS segments
24
+ * - Explicit intensity words ("extremely", "so", "really", "very")
25
+ * - Profanity / frustration markers
26
+ * - Message length (longer rants → higher intensity)
27
+ */
28
+ export function estimateIntensity(message, emotion) {
29
+ if (emotion === 'neutral')
30
+ return 0.2;
31
+ let intensity = 0.4; // base for any non-neutral emotion
32
+ const lower = message.toLowerCase();
33
+ // Punctuation intensity
34
+ const exclamations = (message.match(/!/g) ?? []).length;
35
+ const questions = (message.match(/\?/g) ?? []).length;
36
+ if (exclamations >= 3)
37
+ intensity += 0.15;
38
+ else if (exclamations >= 1)
39
+ intensity += 0.05;
40
+ if (questions >= 3)
41
+ intensity += 0.1;
42
+ // CAPS intensity
43
+ const capsRatio = message.replace(/[^A-Z]/g, '').length / Math.max(message.replace(/\s/g, '').length, 1);
44
+ if (capsRatio > 0.5 && message.length > 10)
45
+ intensity += 0.15;
46
+ // Intensity words
47
+ const intensifiers = ['extremely', 'incredibly', 'absolutely', 'totally', 'completely', 'so frustrated', 'so tired', 'really', 'very', 'cannot believe', "can't stand"];
48
+ for (const word of intensifiers) {
49
+ if (lower.includes(word)) {
50
+ intensity += 0.08;
51
+ break; // only count once
52
+ }
53
+ }
54
+ // Frustration markers
55
+ const frustrationMarkers = ['ugh', 'argh', 'ffs', 'wtf', 'omg', 'seriously', 'again', 'still broken', 'still not working'];
56
+ for (const marker of frustrationMarkers) {
57
+ if (lower.includes(marker)) {
58
+ intensity += 0.08;
59
+ break;
60
+ }
61
+ }
62
+ // Length-based (longer messages in negative emotions → venting → higher intensity)
63
+ if (NEGATIVE_EMOTIONS.has(emotion)) {
64
+ const wordCount = message.split(/\s+/).length;
65
+ if (wordCount > 50)
66
+ intensity += 0.1;
67
+ else if (wordCount > 25)
68
+ intensity += 0.05;
69
+ }
70
+ return Math.min(intensity, 1.0);
71
+ }
72
+ // ────────────────────────────────────────────────────────────────────────────
73
+ // EmotionalTracker
74
+ // ────────────────────────────────────────────────────────────────────────────
75
+ /**
76
+ * Tracks emotional state across multiple messages, detecting escalation
77
+ * patterns that a single-message detector would miss.
78
+ *
79
+ * If a user is getting progressively more frustrated over 4-5 messages,
80
+ * the engine should amplify empathy before they reach a breaking point.
81
+ */
82
+ export class EmotionalTracker {
83
+ history = [];
84
+ // ── Recording ───────────────────────────────────────────────────────────
85
+ /** Record a detected emotion with its intensity and source message. */
86
+ recordEmotion(emotion, intensity, message) {
87
+ this.history.push({
88
+ emotion,
89
+ intensity: Math.min(Math.max(intensity, 0), 1),
90
+ timestamp: Date.now(),
91
+ message,
92
+ });
93
+ }
94
+ // ── Effective emotion ──────────────────────────────────────────────────
95
+ /**
96
+ * Get the effective emotional state considering trajectory across the
97
+ * conversation window.
98
+ */
99
+ getEffectiveEmotion() {
100
+ if (this.history.length === 0) {
101
+ return { emotion: 'neutral', intensity: 0, trajectory: 'stable', escalationAlert: false };
102
+ }
103
+ const latest = this.history[this.history.length - 1];
104
+ if (this.history.length < 2) {
105
+ return { emotion: latest.emotion, intensity: latest.intensity, trajectory: 'stable', escalationAlert: false };
106
+ }
107
+ // Sliding window: last WINDOW_SIZE entries
108
+ const window = this.history.slice(-WINDOW_SIZE);
109
+ // Predominant emotion (mode)
110
+ const emotionCounts = new Map();
111
+ let totalIntensity = 0;
112
+ for (const entry of window) {
113
+ emotionCounts.set(entry.emotion, (emotionCounts.get(entry.emotion) ?? 0) + 1);
114
+ totalIntensity += entry.intensity;
115
+ }
116
+ const averageIntensity = totalIntensity / window.length;
117
+ let predominant = 'neutral';
118
+ let maxCount = 0;
119
+ for (const [emotion, count] of emotionCounts) {
120
+ if (count > maxCount) {
121
+ maxCount = count;
122
+ predominant = emotion;
123
+ }
124
+ }
125
+ // Detect trajectory
126
+ const trajectory = this.detectTrajectory(window);
127
+ // Escalation alert
128
+ const escalationAlert = trajectory === 'escalating' &&
129
+ averageIntensity > ALERT_INTENSITY_THRESHOLD &&
130
+ window.length >= ALERT_MIN_MESSAGES;
131
+ return {
132
+ emotion: predominant,
133
+ intensity: averageIntensity,
134
+ trajectory,
135
+ escalationAlert,
136
+ };
137
+ }
138
+ // ── Trajectory detection ───────────────────────────────────────────────
139
+ detectTrajectory(window) {
140
+ // Count distinct emotion changes
141
+ let emotionChanges = 0;
142
+ for (let i = 1; i < window.length; i++) {
143
+ if (window[i].emotion !== window[i - 1].emotion) {
144
+ emotionChanges++;
145
+ }
146
+ }
147
+ // VOLATILE: 3+ emotion changes in window
148
+ if (emotionChanges >= VOLATILE_CHANGE_THRESHOLD) {
149
+ return 'volatile';
150
+ }
151
+ // ESCALATING: last 3+ messages show increasing intensity of negative emotions
152
+ if (window.length >= ESCALATION_MIN_STREAK) {
153
+ const tail = window.slice(-ESCALATION_MIN_STREAK);
154
+ const allNegative = tail.every(e => NEGATIVE_EMOTIONS.has(e.emotion));
155
+ if (allNegative) {
156
+ let increasing = true;
157
+ for (let i = 1; i < tail.length; i++) {
158
+ if (tail[i].intensity < tail[i - 1].intensity) {
159
+ increasing = false;
160
+ break;
161
+ }
162
+ }
163
+ if (increasing)
164
+ return 'escalating';
165
+ }
166
+ }
167
+ // DE_ESCALATING: was negative, trending toward neutral/positive
168
+ if (window.length >= 2) {
169
+ const earlier = window.slice(0, Math.ceil(window.length / 2));
170
+ const later = window.slice(Math.ceil(window.length / 2));
171
+ const earlierNegativeRatio = earlier.filter(e => NEGATIVE_EMOTIONS.has(e.emotion)).length / earlier.length;
172
+ const laterNegativeRatio = later.filter(e => NEGATIVE_EMOTIONS.has(e.emotion)).length / later.length;
173
+ if (earlierNegativeRatio > 0.5 && laterNegativeRatio < earlierNegativeRatio) {
174
+ return 'de_escalating';
175
+ }
176
+ }
177
+ // SHIFTING: one distinct change (not same emotion throughout, not volatile)
178
+ if (emotionChanges >= 1 && emotionChanges < VOLATILE_CHANGE_THRESHOLD) {
179
+ // Only call it shifting if the latest emotion differs from the earlier predominant
180
+ const earlierEmotions = window.slice(0, -1);
181
+ const earlierMode = this.mode(earlierEmotions.map(e => e.emotion));
182
+ if (window[window.length - 1].emotion !== earlierMode) {
183
+ return 'shifting';
184
+ }
185
+ }
186
+ // STABLE: same emotion for 3+ messages or no significant change
187
+ return 'stable';
188
+ }
189
+ mode(values) {
190
+ const counts = new Map();
191
+ for (const v of values) {
192
+ counts.set(v, (counts.get(v) ?? 0) + 1);
193
+ }
194
+ let best = 'neutral';
195
+ let bestCount = 0;
196
+ for (const [val, count] of counts) {
197
+ if (count > bestCount) {
198
+ bestCount = count;
199
+ best = val;
200
+ }
201
+ }
202
+ return best;
203
+ }
204
+ // ── Lifecycle ──────────────────────────────────────────────────────────
205
+ /** Reset for a new conversation — clears all history. */
206
+ reset() {
207
+ this.history = [];
208
+ }
209
+ }
210
+ //# sourceMappingURL=emotional-tracker.js.map
@@ -0,0 +1,45 @@
1
+ import type { ContextDomain, TraitMix } from '../schema.js';
2
+ export type FeedbackRating = 'helpful' | 'off_target' | 'too_verbose' | 'too_brief' | 'wrong_tone';
3
+ export interface FeedbackEntry {
4
+ id: string;
5
+ timestamp: number;
6
+ domain: ContextDomain;
7
+ rating: FeedbackRating;
8
+ traitSnapshot: Partial<Record<keyof TraitMix, number>>;
9
+ note?: string;
10
+ }
11
+ export interface FeedbackInsight {
12
+ /** Trait adjustments suggested by accumulated feedback. */
13
+ suggestedAdjustments: Partial<Record<keyof TraitMix, number>>;
14
+ /** Domains where responses consistently miss. */
15
+ weakDomains: ContextDomain[];
16
+ /** Overall satisfaction trend: improving, declining, or stable. */
17
+ trend: 'improving' | 'declining' | 'stable';
18
+ /** Total feedback count. */
19
+ totalFeedback: number;
20
+ }
21
+ export declare class FeedbackStore {
22
+ private entries;
23
+ private maxEntries;
24
+ /** Record feedback on a response. Auto-generates id and timestamp. */
25
+ addFeedback(entry: Omit<FeedbackEntry, 'id' | 'timestamp'>): void;
26
+ /**
27
+ * Analyze all feedback to produce actionable insights.
28
+ * - too_verbose feedback -> suggest lowering verbosity (negative adjustment)
29
+ * - too_brief feedback -> suggest raising verbosity (positive adjustment)
30
+ * - off_target in a domain -> flag as weak domain
31
+ * - wrong_tone -> suggest adjusting warmth up
32
+ */
33
+ getInsights(): FeedbackInsight;
34
+ /** Get feedback for a specific domain. */
35
+ getForDomain(domain: ContextDomain): FeedbackEntry[];
36
+ /** Get the satisfaction trend over the last N entries. */
37
+ getRecentTrend(windowSize?: number): 'improving' | 'declining' | 'stable';
38
+ /** Serialize for encrypted storage. */
39
+ serialize(): string;
40
+ /** Deserialize from encrypted storage. */
41
+ static deserialize(data: string): FeedbackStore;
42
+ /** Clear all feedback (user data deletion). */
43
+ clear(): void;
44
+ }
45
+ //# sourceMappingURL=feedback-store.d.ts.map
@@ -0,0 +1,152 @@
1
+ // ────────────────────────────────────────────────────────────────────────────
2
+ // Helpers
3
+ // ────────────────────────────────────────────────────────────────────────────
4
+ /** Generate a v4-style UUID without crypto dependency. */
5
+ function generateId() {
6
+ const hex = '0123456789abcdef';
7
+ const segments = [8, 4, 4, 4, 12];
8
+ return segments.map(len => {
9
+ let s = '';
10
+ for (let i = 0; i < len; i++) {
11
+ s += hex[Math.floor(Math.random() * 16)];
12
+ }
13
+ return s;
14
+ }).join('-');
15
+ }
16
+ /**
17
+ * Compute the helpful ratio for a slice of entries.
18
+ * Returns 0 if the slice is empty.
19
+ */
20
+ function helpfulRatio(entries) {
21
+ if (entries.length === 0)
22
+ return 0;
23
+ const helpful = entries.filter(e => e.rating === 'helpful').length;
24
+ return helpful / entries.length;
25
+ }
26
+ /**
27
+ * Compare helpful ratios of first half vs second half.
28
+ * A difference > 0.10 in either direction triggers a trend change.
29
+ */
30
+ function computeTrend(entries) {
31
+ if (entries.length < 2)
32
+ return 'stable';
33
+ const mid = Math.floor(entries.length / 2);
34
+ const firstHalf = entries.slice(0, mid);
35
+ const secondHalf = entries.slice(mid);
36
+ const firstRatio = helpfulRatio(firstHalf);
37
+ const secondRatio = helpfulRatio(secondHalf);
38
+ const diff = secondRatio - firstRatio;
39
+ if (diff > 0.10)
40
+ return 'improving';
41
+ if (diff < -0.10)
42
+ return 'declining';
43
+ return 'stable';
44
+ }
45
+ // ────────────────────────────────────────────────────────────────────────────
46
+ // FeedbackStore
47
+ // ────────────────────────────────────────────────────────────────────────────
48
+ export class FeedbackStore {
49
+ entries = [];
50
+ maxEntries = 500;
51
+ /** Record feedback on a response. Auto-generates id and timestamp. */
52
+ addFeedback(entry) {
53
+ this.entries.push({
54
+ ...entry,
55
+ id: generateId(),
56
+ timestamp: Date.now(),
57
+ });
58
+ // Drop oldest entries when exceeding capacity
59
+ if (this.entries.length > this.maxEntries) {
60
+ this.entries = this.entries.slice(this.entries.length - this.maxEntries);
61
+ }
62
+ }
63
+ /**
64
+ * Analyze all feedback to produce actionable insights.
65
+ * - too_verbose feedback -> suggest lowering verbosity (negative adjustment)
66
+ * - too_brief feedback -> suggest raising verbosity (positive adjustment)
67
+ * - off_target in a domain -> flag as weak domain
68
+ * - wrong_tone -> suggest adjusting warmth up
69
+ */
70
+ getInsights() {
71
+ const suggestedAdjustments = {};
72
+ const weakDomains = [];
73
+ // Count each rating type across all entries
74
+ let tooVerboseCount = 0;
75
+ let tooBriefCount = 0;
76
+ let wrongToneCount = 0;
77
+ // Count off_target per domain
78
+ const offTargetByDomain = new Map();
79
+ for (const entry of this.entries) {
80
+ switch (entry.rating) {
81
+ case 'too_verbose':
82
+ tooVerboseCount++;
83
+ break;
84
+ case 'too_brief':
85
+ tooBriefCount++;
86
+ break;
87
+ case 'wrong_tone':
88
+ wrongToneCount++;
89
+ break;
90
+ case 'off_target': {
91
+ const count = offTargetByDomain.get(entry.domain) ?? 0;
92
+ offTargetByDomain.set(entry.domain, count + 1);
93
+ break;
94
+ }
95
+ }
96
+ }
97
+ // too_verbose (>= 5) -> lower verbosity, capped at -0.3
98
+ if (tooVerboseCount >= 5) {
99
+ const adj = -0.1 * tooVerboseCount;
100
+ suggestedAdjustments.verbosity = Math.max(adj, -0.3);
101
+ }
102
+ // too_brief (>= 5) -> raise verbosity, capped at +0.3
103
+ if (tooBriefCount >= 5) {
104
+ const adj = 0.1 * tooBriefCount;
105
+ suggestedAdjustments.verbosity = Math.min(adj, 0.3);
106
+ }
107
+ // wrong_tone (>= 5) -> adjust warmth up
108
+ if (wrongToneCount >= 5) {
109
+ suggestedAdjustments.warmth = 0.1;
110
+ }
111
+ // off_target in a domain (>= 3) -> weak domain
112
+ for (const [domain, count] of offTargetByDomain) {
113
+ if (count >= 3) {
114
+ weakDomains.push(domain);
115
+ }
116
+ }
117
+ const trend = computeTrend(this.entries);
118
+ return {
119
+ suggestedAdjustments,
120
+ weakDomains,
121
+ trend,
122
+ totalFeedback: this.entries.length,
123
+ };
124
+ }
125
+ /** Get feedback for a specific domain. */
126
+ getForDomain(domain) {
127
+ return this.entries.filter(e => e.domain === domain);
128
+ }
129
+ /** Get the satisfaction trend over the last N entries. */
130
+ getRecentTrend(windowSize = 20) {
131
+ const window = this.entries.slice(-windowSize);
132
+ return computeTrend(window);
133
+ }
134
+ /** Serialize for encrypted storage. */
135
+ serialize() {
136
+ return JSON.stringify({ entries: this.entries });
137
+ }
138
+ /** Deserialize from encrypted storage. */
139
+ static deserialize(data) {
140
+ const store = new FeedbackStore();
141
+ const parsed = JSON.parse(data);
142
+ if (Array.isArray(parsed.entries)) {
143
+ store.entries = parsed.entries;
144
+ }
145
+ return store;
146
+ }
147
+ /** Clear all feedback (user data deletion). */
148
+ clear() {
149
+ this.entries = [];
150
+ }
151
+ }
152
+ //# sourceMappingURL=feedback-store.js.map