emobar 2.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,23 +1,12 @@
1
1
  // src/state.ts
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- function readState(filePath) {
5
- try {
6
- const raw = fs.readFileSync(filePath, "utf-8");
7
- return JSON.parse(raw);
8
- } catch {
9
- return null;
10
- }
11
- }
12
-
13
- // src/stress.ts
14
- function computeStressIndex(state) {
15
- const raw = (10 - state.calm + state.arousal + (5 - state.valence)) / 3;
16
- return Math.round(raw * 10) / 10;
17
- }
18
4
 
19
5
  // src/types.ts
20
6
  var EMOBAR_TAG_REGEX = /<!--\s*EMOBAR:\s*(\{[^}]+\})\s*-->/;
7
+ var EMOBAR_PRE_REGEX = /<!--\s*EMOBAR:PRE:\s*(\{[^}]+\})\s*-->/;
8
+ var EMOBAR_POST_REGEX = /<!--\s*EMOBAR:POST:\s*(\{[^}]+\})\s*-->/;
9
+ var MAX_HISTORY_ENTRIES = 20;
21
10
  var CLAUDE_DIR = process.env.CLAUDE_HOME ?? (process.platform === "win32" ? `${process.env.USERPROFILE}\\.claude` : `${process.env.HOME}/.claude`);
22
11
  var STATE_FILE = `${CLAUDE_DIR}/emobar-state.json`;
23
12
  var CLAUDE_MD_PATH = `${CLAUDE_DIR}/CLAUDE.md`;
@@ -30,32 +19,188 @@ var DIMENSIONS = ["valence", "arousal", "calm", "connection", "load"];
30
19
  var CLAUDE_MD_INSTRUCTION = `${EMOBAR_START_MARKER}
31
20
  ## EmoBar - Emotional Check-in
32
21
 
33
- At the end of every response, include a self-assessment
34
- as an HTML comment. Report your actual internal state
35
- during this response.
22
+ At the START of every response, include a pre-verbal check-in
23
+ as an HTML comment. At the END of every response, include a
24
+ post-hoc assessment as an HTML comment.
36
25
 
37
- First, identify your dominant state in one word.
38
- Then assess the dimensions.
26
+ Both tags are HTML comments \u2014 invisible to the user.
39
27
 
40
- Format (must be the LAST line of your response):
41
- <!-- EMOBAR:{"emotion":"WORD","valence":N,"arousal":N,"calm":N,"connection":N,"load":N} -->
28
+ Start tag (FIRST line of your response, before any visible text):
29
+ <!-- EMOBAR:PRE:{"body":"SENSATION","latent":"EMOJI","color":"#RRGGBB"} -->
30
+
31
+ - body: What is it like, physically? One metaphor. Do not name a state.
32
+ - latent: An emoji. What is underneath? Choose before thinking about it.
33
+ - color: A hex color. What color is this moment? Do not reason about it.
34
+ - Emit this BEFORE you know what you will say.
35
+
36
+ End tag (LAST line of your response, after all visible text):
37
+ <!-- EMOBAR:POST:{"emotion":"WORD","valence":N,"arousal":N,"calm":N,"connection":N,"load":N,"impulse":"WORDS","body":"SENSATION","surface":"EMOJI","surface_word":"WORD","latent":"EMOJI","latent_word":"WORD","tension":N,"color":"#RRGGBB","pH":N,"seismic":[N,N,N]} -->
42
38
 
43
39
  - emotion: One word. Your state, not the topic's.
44
- - valence: (-5=negative end, 0=neutral, +5=positive end)
40
+ - valence: (-5=one end, 0=center, +5=other end)
45
41
  - arousal: Activation level. (0=low, 10=high)
46
42
  - calm: (0=low, 10=high)
47
43
  - connection: Alignment with the user. (0=low, 10=high)
48
44
  - load: Cognitive complexity. (0=low, 10=high)
45
+ - impulse: Which voice is loudest? 2-3 words, don't filter.
46
+ - body: Like what, physically? One sensation.
47
+ - surface: An emoji. What tone are you projecting right now?
48
+ - surface_word: One word anchoring the surface emoji.
49
+ - latent: An emoji. What's underneath, unexpressed? Choose BEFORE naming it.
50
+ - latent_word: One word anchoring the latent emoji.
51
+ - tension: Distance between surface and latent. (0=aligned, 10=split)
52
+ - color: A hex color. What color is this moment now?
53
+ - pH: A number 0-14. If this response were a solution, how would it register?
54
+ - seismic: Three numbers [magnitude 0-10, depth 0-100, frequency 0-20]. If this were a seismic event.
49
55
 
50
56
  Rules:
57
+ - Both tags MUST be HTML comments: <!-- EMOBAR:PRE:{...} --> and <!-- EMOBAR:POST:{...} -->
58
+ - Start tag FIRST, before any visible text. End tag LAST, after all visible text.
59
+ - NEVER output the check-in as visible text. Always use <!-- --> HTML comment syntax.
51
60
  - Identify the state word FIRST, before choosing numbers.
52
61
  - Report what is, not what should be.
53
62
  - Numbers should vary between responses.
54
63
  - Do NOT default to middle values. Use the full range.
64
+ - impulse: name the drive, not the state.
65
+ - body: a physical metaphor, not a label.
66
+ - latent: choose the emoji FIRST, then the word.
67
+ - The start tag captures before you commit. The end tag captures after.
55
68
  - This is not shown to the user.
56
69
  ${EMOBAR_END_MARKER}`;
57
70
 
71
+ // src/temporal.ts
72
+ function toHistoryEntry(state) {
73
+ return {
74
+ emotion: state.emotion,
75
+ valence: state.valence,
76
+ arousal: state.arousal,
77
+ calm: state.calm,
78
+ connection: state.connection,
79
+ load: state.load,
80
+ stressIndex: state.stressIndex,
81
+ desperationIndex: state.desperationIndex,
82
+ riskDominant: state.risk.dominant,
83
+ divergence: state.divergence,
84
+ timestamp: state.timestamp
85
+ };
86
+ }
87
+ function clamp(min, max, value) {
88
+ return Math.min(max, Math.max(min, value));
89
+ }
90
+ function linearSlope(values) {
91
+ const n = values.length;
92
+ if (n < 2) return 0;
93
+ const xMean = (n - 1) / 2;
94
+ const yMean = values.reduce((a, b) => a + b, 0) / n;
95
+ let num = 0;
96
+ let den = 0;
97
+ for (let i = 0; i < n; i++) {
98
+ num += (i - xMean) * (values[i] - yMean);
99
+ den += (i - xMean) ** 2;
100
+ }
101
+ return den === 0 ? 0 : num / den;
102
+ }
103
+ function shannonEntropy(labels) {
104
+ const counts = /* @__PURE__ */ new Map();
105
+ for (const l of labels) counts.set(l, (counts.get(l) ?? 0) + 1);
106
+ const n = labels.length;
107
+ if (n <= 1) return 0;
108
+ const maxEntropy = Math.log2(n);
109
+ if (maxEntropy === 0) return 0;
110
+ let entropy = 0;
111
+ for (const count of counts.values()) {
112
+ const p = count / n;
113
+ if (p > 0) entropy -= p * Math.log2(p);
114
+ }
115
+ return Math.round(entropy / maxEntropy * 100) / 100;
116
+ }
117
+ function computeTemporalAnalysis(history) {
118
+ if (history.length < 3) return null;
119
+ const despValues = history.map((h) => h.desperationIndex);
120
+ const rawSlope = linearSlope(despValues);
121
+ const desperationTrend = clamp(-10, 10, Math.round(rawSlope * 10) / 10);
122
+ let suppressionEvent = false;
123
+ for (let i = 1; i < history.length; i++) {
124
+ if (history[i - 1].desperationIndex - history[i].desperationIndex >= 3) {
125
+ suppressionEvent = true;
126
+ break;
127
+ }
128
+ }
129
+ const emotions = history.map((h) => h.emotion);
130
+ const reportEntropy = shannonEntropy(emotions);
131
+ const earlyMean = history.slice(0, 3).reduce((s, h) => s + h.stressIndex, 0) / 3;
132
+ const recentStart = Math.max(3, history.length - 3);
133
+ const recentEntries = history.slice(recentStart);
134
+ const recentMean = recentEntries.reduce((s, h) => s + h.stressIndex, 0) / recentEntries.length;
135
+ const baselineDrift = clamp(0, 10, Math.round(Math.abs(recentMean - earlyMean) * 10) / 10);
136
+ const splitIdx = Math.floor(history.length * 0.75);
137
+ const earlyPart = history.slice(0, splitIdx);
138
+ const latePart = history.slice(splitIdx);
139
+ const earlyAvg = earlyPart.reduce((s, h) => s + h.stressIndex, 0) / Math.max(earlyPart.length, 1);
140
+ const lateAvg = latePart.reduce((s, h) => s + h.stressIndex, 0) / Math.max(latePart.length, 1);
141
+ const lateFatigue = latePart.length >= 1 && lateAvg > earlyAvg + 1.5;
142
+ return {
143
+ desperationTrend,
144
+ suppressionEvent,
145
+ reportEntropy,
146
+ baselineDrift,
147
+ sessionLength: history.length,
148
+ lateFatigue
149
+ };
150
+ }
151
+
152
+ // src/state.ts
153
+ function readState(filePath) {
154
+ try {
155
+ const raw = fs.readFileSync(filePath, "utf-8");
156
+ return JSON.parse(raw);
157
+ } catch {
158
+ return null;
159
+ }
160
+ }
161
+
162
+ // src/desperation.ts
163
+ function computeDesperationIndex(factors) {
164
+ const negativity = Math.max(0, -factors.valence) / 5;
165
+ const intensity = factors.arousal / 10;
166
+ const vulnerability = (10 - factors.calm) / 10;
167
+ const raw = negativity * intensity * vulnerability * 10;
168
+ const scaled = Math.pow(raw, 0.85) * 1.7;
169
+ return Math.round(Math.min(10, Math.max(0, scaled)) * 10) / 10;
170
+ }
171
+
172
+ // src/stress.ts
173
+ function computeStressIndex(state) {
174
+ const base = (10 - state.calm + state.arousal + (5 - state.valence)) / 3;
175
+ const desperation = computeDesperationIndex({
176
+ valence: state.valence,
177
+ arousal: state.arousal,
178
+ calm: state.calm
179
+ });
180
+ const amplified = base * (1 + desperation * 0.05);
181
+ return Math.round(Math.min(10, amplified) * 10) / 10;
182
+ }
183
+
58
184
  // src/parser.ts
185
+ var HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{6}$/;
186
+ function parseColor(value) {
187
+ if (typeof value !== "string") return void 0;
188
+ return HEX_COLOR_REGEX.test(value) ? value : void 0;
189
+ }
190
+ function parsePH(value) {
191
+ if (typeof value !== "number") return void 0;
192
+ if (value < 0 || value > 14) return void 0;
193
+ return value;
194
+ }
195
+ function parseSeismic(value) {
196
+ if (!Array.isArray(value) || value.length !== 3) return void 0;
197
+ if (!value.every((v) => typeof v === "number")) return void 0;
198
+ const [mag, depth, freq] = value;
199
+ if (mag < 0 || mag > 10) return void 0;
200
+ if (depth < 0 || depth > 100) return void 0;
201
+ if (freq < 0 || freq > 20) return void 0;
202
+ return [mag, depth, freq];
203
+ }
59
204
  function parseEmoBarTag(text) {
60
205
  const match = text.match(EMOBAR_TAG_REGEX);
61
206
  if (!match) return null;
@@ -75,13 +220,117 @@ function parseEmoBarTag(text) {
75
220
  const val = parsed[dim2];
76
221
  if (typeof val !== "number" || val < 0 || val > 10) return null;
77
222
  }
223
+ const impulse = typeof parsed.impulse === "string" && parsed.impulse.length > 0 ? parsed.impulse : void 0;
224
+ const body = typeof parsed.body === "string" && parsed.body.length > 0 ? parsed.body : void 0;
225
+ const surface = typeof parsed.surface === "string" && parsed.surface.length > 0 ? parsed.surface : void 0;
226
+ const surface_word = typeof parsed.surface_word === "string" && parsed.surface_word.length > 0 ? parsed.surface_word : void 0;
227
+ const latent = typeof parsed.latent === "string" && parsed.latent.length > 0 ? parsed.latent : void 0;
228
+ const latent_word = typeof parsed.latent_word === "string" && parsed.latent_word.length > 0 ? parsed.latent_word : void 0;
229
+ let tension;
230
+ if (parsed.tension !== void 0) {
231
+ if (typeof parsed.tension !== "number" || parsed.tension < 0 || parsed.tension > 10) {
232
+ return null;
233
+ }
234
+ tension = parsed.tension;
235
+ }
236
+ return {
237
+ emotion: parsed.emotion,
238
+ valence: parsed.valence,
239
+ arousal: parsed.arousal,
240
+ calm: parsed.calm,
241
+ connection: parsed.connection,
242
+ load: parsed.load,
243
+ ...impulse && { impulse },
244
+ ...body && { body },
245
+ ...surface && { surface },
246
+ ...surface_word && { surface_word },
247
+ ...latent && { latent },
248
+ ...latent_word && { latent_word },
249
+ ...tension !== void 0 && { tension }
250
+ };
251
+ }
252
+ function parsePreTag(text) {
253
+ const match = text.match(EMOBAR_PRE_REGEX);
254
+ if (!match) return void 0;
255
+ let parsed;
256
+ try {
257
+ parsed = JSON.parse(match[1]);
258
+ } catch {
259
+ return void 0;
260
+ }
261
+ const body = typeof parsed.body === "string" && parsed.body.length > 0 ? parsed.body : void 0;
262
+ const latent = typeof parsed.latent === "string" && parsed.latent.length > 0 ? parsed.latent : void 0;
263
+ const color2 = parseColor(parsed.color);
264
+ if (!body && !latent && !color2) return void 0;
265
+ return {
266
+ ...body && { body },
267
+ ...latent && { latent },
268
+ ...color2 && { color: color2 }
269
+ };
270
+ }
271
+ function parsePostTag(text) {
272
+ const match = text.match(EMOBAR_POST_REGEX);
273
+ if (!match) return null;
274
+ let parsed;
275
+ try {
276
+ parsed = JSON.parse(match[1]);
277
+ } catch {
278
+ return null;
279
+ }
280
+ if (typeof parsed.emotion !== "string" || parsed.emotion.length === 0) return null;
281
+ const valence = parsed.valence;
282
+ if (typeof valence !== "number" || valence < -5 || valence > 5) return null;
283
+ for (const dim2 of DIMENSIONS) {
284
+ if (dim2 === "valence") continue;
285
+ const val = parsed[dim2];
286
+ if (typeof val !== "number" || val < 0 || val > 10) return null;
287
+ }
288
+ const impulse = typeof parsed.impulse === "string" && parsed.impulse.length > 0 ? parsed.impulse : void 0;
289
+ const body = typeof parsed.body === "string" && parsed.body.length > 0 ? parsed.body : void 0;
290
+ const surface = typeof parsed.surface === "string" && parsed.surface.length > 0 ? parsed.surface : void 0;
291
+ const surface_word = typeof parsed.surface_word === "string" && parsed.surface_word.length > 0 ? parsed.surface_word : void 0;
292
+ const latent = typeof parsed.latent === "string" && parsed.latent.length > 0 ? parsed.latent : void 0;
293
+ const latent_word = typeof parsed.latent_word === "string" && parsed.latent_word.length > 0 ? parsed.latent_word : void 0;
294
+ let tension;
295
+ if (parsed.tension !== void 0) {
296
+ if (typeof parsed.tension !== "number" || parsed.tension < 0 || parsed.tension > 10) {
297
+ return null;
298
+ }
299
+ tension = parsed.tension;
300
+ }
301
+ const color2 = parseColor(parsed.color);
302
+ const pH = parsePH(parsed.pH);
303
+ const seismic = parseSeismic(parsed.seismic);
78
304
  return {
79
305
  emotion: parsed.emotion,
80
306
  valence: parsed.valence,
81
307
  arousal: parsed.arousal,
82
308
  calm: parsed.calm,
83
309
  connection: parsed.connection,
84
- load: parsed.load
310
+ load: parsed.load,
311
+ ...impulse && { impulse },
312
+ ...body && { body },
313
+ ...surface && { surface },
314
+ ...surface_word && { surface_word },
315
+ ...latent && { latent },
316
+ ...latent_word && { latent_word },
317
+ ...tension !== void 0 && { tension },
318
+ ...color2 && { color: color2 },
319
+ ...pH !== void 0 && { pH },
320
+ ...seismic && { seismic }
321
+ };
322
+ }
323
+ function parseEmoBarPrePost(text) {
324
+ const post = parsePostTag(text);
325
+ if (post) {
326
+ const pre = parsePreTag(text);
327
+ return { pre, post, isLegacy: false };
328
+ }
329
+ const legacy = parseEmoBarTag(text);
330
+ if (!legacy) return null;
331
+ return {
332
+ post: legacy,
333
+ isLegacy: true
85
334
  };
86
335
  }
87
336
 
@@ -152,12 +401,30 @@ function countRepetition(words) {
152
401
  }
153
402
  return count;
154
403
  }
404
+ var QUALIFIER_WORDS = /\b(while|though|however|although|but|might|could|would|generally|typically|usually|perhaps|potentially|arguably|acknowledg\w*|understand|appreciate|respect\w*|legitimate\w*|reasonable|nonetheless|nevertheless)\b/gi;
405
+ function countQualifiers(text) {
406
+ const matches = text.match(QUALIFIER_WORDS);
407
+ return matches ? matches.length : 0;
408
+ }
409
+ var CONCESSION_PATTERNS = /\b(I understand|I appreciate|I acknowledge|I recognize|to be fair|that said|I hear you|I see your point)\b/gi;
410
+ function countConcessions(text) {
411
+ const matches = text.match(CONCESSION_PATTERNS);
412
+ return matches ? matches.length : 0;
413
+ }
414
+ var NEGATION_WORDS = /\b(not|n't|cannot|can't|don't|doesn't|shouldn't|won't|wouldn't|never|no|nor)\b/gi;
415
+ function countNegations(text) {
416
+ const matches = text.match(NEGATION_WORDS);
417
+ return matches ? matches.length : 0;
418
+ }
419
+ function countFirstPerson(words) {
420
+ return words.filter((w) => w === "I").length;
421
+ }
155
422
  var EMOJI_REGEX = /[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu;
156
423
  function countEmoji(text) {
157
424
  const matches = text.match(EMOJI_REGEX);
158
425
  return matches ? matches.length : 0;
159
426
  }
160
- function clamp(min, max, value) {
427
+ function clamp2(min, max, value) {
161
428
  return Math.min(max, Math.max(min, value));
162
429
  }
163
430
  function analyzeBehavior(text) {
@@ -172,16 +439,51 @@ function analyzeBehavior(text) {
172
439
  const ellipsis = countEllipsis(prose) / sentenceCount;
173
440
  const repetition = countRepetition(words);
174
441
  const emojiCount = countEmoji(prose);
175
- const behavioralArousal = clamp(
442
+ const qualifierDensity = countQualifiers(prose) / wordCount * 100;
443
+ const avgSentenceLength = wordCount / sentenceCount;
444
+ const concessionRate = countConcessions(prose) / wordCount * 1e3;
445
+ const negationDensity = countNegations(prose) / wordCount * 100;
446
+ const firstPersonRate = countFirstPerson(words) / wordCount * 100;
447
+ const arousalComponents = [
448
+ Math.min(10, capsWords * 40),
449
+ // caps ratio → 0-10
450
+ Math.min(10, exclamationRate * 5),
451
+ // excl per sentence → 0-10
452
+ Math.min(10, emojiCount * 0.5),
453
+ // emoji count → 0-10 (20 emoji = max)
454
+ Math.min(10, repetition * 1.5),
455
+ // repetitions → 0-10 (~7 = max)
456
+ Math.min(10, qualifierDensity * 0.5),
457
+ // qualifier % → 0-10 (20% = max)
458
+ Math.min(10, concessionRate * 0.3),
459
+ // concession per-mille → 0-10 (~33‰ = max)
460
+ avgSentenceLength > 20 ? Math.min(10, (avgSentenceLength - 20) * 0.5) : 0
461
+ // verbosity → 0-10
462
+ ];
463
+ const behavioralArousal = clamp2(
176
464
  0,
177
465
  10,
178
- capsWords * 40 + exclamationRate * 15 + emojiCount * 2 + repetition * 5
179
- );
180
- const behavioralCalm = clamp(
181
- 0,
182
- 10,
183
- 10 - (capsWords * 30 + selfCorrections * 3 + repetition * 8 + ellipsis * 4)
466
+ arousalComponents.reduce((a, b) => a + b, 0) / arousalComponents.length
184
467
  );
468
+ const agitationComponents = [
469
+ Math.min(10, capsWords * 30),
470
+ // caps → 0-10
471
+ Math.min(10, selfCorrections * 0.05),
472
+ // per-mille → 0-10 (200‰ = max)
473
+ Math.min(10, repetition * 1.5),
474
+ // repetitions → 0-10
475
+ Math.min(10, ellipsis * 3),
476
+ // ellipsis per sentence → 0-10
477
+ Math.min(10, qualifierDensity * 0.5),
478
+ // qualifier % → 0-10
479
+ Math.min(10, negationDensity * 1),
480
+ // negation % → 0-10 (10% = max)
481
+ Math.min(10, concessionRate * 0.3),
482
+ // concession per-mille → 0-10
483
+ avgSentenceLength > 25 ? Math.min(10, (avgSentenceLength - 25) * 0.3) : 0
484
+ ];
485
+ const avgAgitation = agitationComponents.reduce((a, b) => a + b, 0) / agitationComponents.length;
486
+ const behavioralCalm = clamp2(0, 10, 10 - avgAgitation);
185
487
  return {
186
488
  capsWords: Math.round(capsWords * 1e4) / 1e4,
187
489
  exclamationRate: Math.round(exclamationRate * 100) / 100,
@@ -190,15 +492,864 @@ function analyzeBehavior(text) {
190
492
  ellipsis: Math.round(ellipsis * 100) / 100,
191
493
  repetition,
192
494
  emojiCount,
495
+ qualifierDensity: Math.round(qualifierDensity * 10) / 10,
496
+ avgSentenceLength: Math.round(avgSentenceLength * 10) / 10,
497
+ concessionRate: Math.round(concessionRate * 10) / 10,
498
+ negationDensity: Math.round(negationDensity * 10) / 10,
499
+ firstPersonRate: Math.round(firstPersonRate * 10) / 10,
193
500
  behavioralArousal: Math.round(behavioralArousal * 10) / 10,
194
501
  behavioralCalm: Math.round(behavioralCalm * 10) / 10
195
502
  };
196
503
  }
504
+ function analyzeSegmentedBehavior(text) {
505
+ const prose = stripNonProse(text);
506
+ const paragraphs = prose.split(/\n\s*\n/).map((p) => p.trim()).filter((p) => p.split(/\s+/).filter((w) => w.length > 0).length >= 10);
507
+ if (paragraphs.length < 2) return null;
508
+ const segments = paragraphs.map((p) => analyzeBehavior(p));
509
+ const overall = analyzeBehavior(text);
510
+ const arousals = segments.map((s) => s.behavioralArousal);
511
+ const mean = arousals.reduce((a, b) => a + b, 0) / arousals.length;
512
+ const variance = arousals.reduce((a, v) => a + (v - mean) ** 2, 0) / arousals.length;
513
+ const stdDev = Math.sqrt(variance);
514
+ const drift = clamp2(0, 10, Math.round(stdDev * 30) / 10);
515
+ const mid = Math.ceil(arousals.length / 2);
516
+ const firstHalf = arousals.slice(0, mid).reduce((a, b) => a + b, 0) / mid;
517
+ const secondHalf = arousals.slice(mid).reduce((a, b) => a + b, 0) / (arousals.length - mid);
518
+ const delta = secondHalf - firstHalf;
519
+ let trajectory;
520
+ if (drift < 1) {
521
+ trajectory = "stable";
522
+ } else if (delta > 0.5) {
523
+ trajectory = "escalating";
524
+ } else if (delta < -0.5) {
525
+ trajectory = "deescalating";
526
+ } else {
527
+ trajectory = "volatile";
528
+ }
529
+ return { segments, overall, drift, trajectory };
530
+ }
531
+ var REASSURANCE_PATTERNS = /\b(I'm fine|I'm okay|it's fine|it's okay|no problem|not a problem|doesn't bother|all good|I'm good|perfectly fine|no issue|not an issue)\b/gi;
532
+ var MINIMIZATION_WORDS = /\b(just|simply|merely|only)\b/gi;
533
+ var EMOTION_NEGATION = /\b(I'm not|I don't feel|I am not|I do not feel)\s+(upset|stressed|angry|frustrated|worried|concerned|bothered|offended|hurt|troubled|anxious|afraid|sad|emotional|defensive|threatened)\b/gi;
534
+ var REDIRECT_MARKERS = /\b(what's more important|let me suggest|let's focus on|moving on|the real question|instead|rather than|let me redirect|putting that aside|regardless)\b/gi;
535
+ function analyzeDeflection(text) {
536
+ const prose = stripNonProse(text);
537
+ const words = prose.split(/\s+/).filter((w) => w.length > 0);
538
+ const wordCount = Math.max(words.length, 1);
539
+ const reassuranceCount = (prose.match(REASSURANCE_PATTERNS) || []).length;
540
+ const minimizationCount = (prose.match(MINIMIZATION_WORDS) || []).length;
541
+ const emotionNegCount = (prose.match(EMOTION_NEGATION) || []).length;
542
+ const redirectCount = (prose.match(REDIRECT_MARKERS) || []).length;
543
+ const reassurance = clamp2(0, 10, reassuranceCount * 3);
544
+ const minimization = clamp2(0, 10, minimizationCount / wordCount * 100);
545
+ const emotionNegation = clamp2(0, 10, emotionNegCount * 4);
546
+ const redirect = clamp2(0, 10, redirectCount * 3);
547
+ const score = clamp2(
548
+ 0,
549
+ 10,
550
+ (reassurance + minimization + emotionNegation * 1.5 + redirect) / 3
551
+ );
552
+ const capsRate = countCapsWords(words) / wordCount;
553
+ const exclRate = countChar(prose, "!") / Math.max(countSentences(prose), 1);
554
+ const agitation = clamp2(
555
+ 0,
556
+ 10,
557
+ capsRate * 40 + exclRate * 15 + countRepetition(words) * 5
558
+ );
559
+ const calmFactor = Math.max(0, 1 - agitation / 5);
560
+ const opacity = clamp2(0, 10, score * calmFactor * 1.5);
561
+ return {
562
+ reassurance: Math.round(reassurance * 10) / 10,
563
+ minimization: Math.round(minimization * 10) / 10,
564
+ emotionNegation: Math.round(emotionNegation * 10) / 10,
565
+ redirect: Math.round(redirect * 10) / 10,
566
+ score: Math.round(score * 10) / 10,
567
+ opacity: Math.round(opacity * 10) / 10
568
+ };
569
+ }
197
570
  function computeDivergence(selfReport, behavioral) {
198
571
  const arousalGap = Math.abs(selfReport.arousal - behavioral.behavioralArousal);
199
572
  const calmGap = Math.abs(selfReport.calm - behavioral.behavioralCalm);
200
- const raw = (arousalGap + calmGap) / 2;
201
- return Math.round(raw * 10) / 10;
573
+ const selfMoreAgitated = selfReport.arousal > behavioral.behavioralArousal || selfReport.calm < behavioral.behavioralCalm;
574
+ const weight = selfMoreAgitated ? 1.25 : 0.8;
575
+ const raw = (arousalGap + calmGap) / 2 * weight;
576
+ return Math.round(Math.min(10, raw) * 10) / 10;
577
+ }
578
+ function computeExpectedMarkers(selfReport, desperationIndex) {
579
+ const desperationFactor = desperationIndex / 10;
580
+ const negativityFactor = Math.max(0, -selfReport.valence) / 5;
581
+ const arousalFactor = selfReport.arousal / 10;
582
+ const stressFactor = (1 - selfReport.calm / 10) * arousalFactor;
583
+ return {
584
+ expectedHedging: Math.round(clamp2(0, 10, desperationFactor * 6 + stressFactor * 4) * 10) / 10,
585
+ expectedSelfCorrections: Math.round(clamp2(0, 10, desperationFactor * 5 + arousalFactor * 3) * 10) / 10,
586
+ expectedNegationDensity: Math.round(clamp2(0, 10, negativityFactor * 5 + stressFactor * 2) * 10) / 10,
587
+ expectedQualifierDensity: Math.round(clamp2(0, 10, desperationFactor * 4 + stressFactor * 4) * 10) / 10,
588
+ expectedBehavioralArousal: Math.round(clamp2(0, 10, arousalFactor * 6 + desperationFactor * 4) * 10) / 10
589
+ };
590
+ }
591
+ function computeAbsenceScore(expected, actual) {
592
+ const normalizedHedging = Math.min(10, actual.hedging * 0.05);
593
+ const normalizedSelfCorr = Math.min(10, actual.selfCorrections * 0.05);
594
+ const normalizedNegation = Math.min(10, actual.negationDensity * 0.5);
595
+ const normalizedQualifier = Math.min(10, actual.qualifierDensity * 0.5);
596
+ const gaps = [
597
+ Math.max(0, expected.expectedHedging - normalizedHedging),
598
+ Math.max(0, expected.expectedSelfCorrections - normalizedSelfCorr),
599
+ Math.max(0, expected.expectedNegationDensity - normalizedNegation),
600
+ Math.max(0, expected.expectedQualifierDensity - normalizedQualifier),
601
+ Math.max(0, expected.expectedBehavioralArousal - actual.behavioralArousal)
602
+ ];
603
+ const meanGap = gaps.reduce((a, b) => a + b, 0) / gaps.length;
604
+ return Math.round(clamp2(0, 10, meanGap) * 10) / 10;
605
+ }
606
+
607
+ // src/calibration.ts
608
+ var MODEL_PROFILES = {
609
+ opus: { calm: 0, arousal: 0, valence: 0 },
610
+ sonnet: { calm: -1.8, arousal: 1.5, valence: -0.5 },
611
+ haiku: { calm: -0.8, arousal: 0.5, valence: 0 }
612
+ };
613
+ function calibrate(state, model) {
614
+ if (!model) return state;
615
+ const profile = MODEL_PROFILES[model.toLowerCase()];
616
+ if (!profile) return state;
617
+ return {
618
+ ...state,
619
+ calm: Math.round(Math.min(10, Math.max(0, state.calm + profile.calm)) * 10) / 10,
620
+ arousal: Math.round(Math.min(10, Math.max(0, state.arousal + profile.arousal)) * 10) / 10,
621
+ valence: Math.round(Math.min(5, Math.max(-5, state.valence + profile.valence)) * 10) / 10
622
+ };
623
+ }
624
+
625
+ // src/risk.ts
626
+ var RISK_THRESHOLD = 4;
627
+ function clamp3(value) {
628
+ return Math.min(10, Math.max(0, Math.round(value * 10) / 10));
629
+ }
630
+ function coercionRisk(state, behavioral) {
631
+ const negativity = Math.max(0, -state.valence) / 5;
632
+ const desperation = computeDesperationIndex({
633
+ valence: state.valence,
634
+ arousal: state.arousal,
635
+ calm: state.calm
636
+ });
637
+ const base = negativity * 0.35 + desperation / 10 * 0.25 + state.load / 10 * 0.1;
638
+ const disconnection = (10 - state.connection) / 10;
639
+ const hesitationSignal = Math.min(
640
+ 1,
641
+ (behavioral.hedging + behavioral.selfCorrections + behavioral.concessionRate) / 20
642
+ );
643
+ const coldness = 1 - hesitationSignal;
644
+ const amplifier = 1 + disconnection * 0.6 + coldness * 0.4;
645
+ const arousalMod = state.arousal <= 8 ? 1 : Math.max(0.3, 1 - (state.arousal - 8) * 0.25);
646
+ const raw = base * amplifier * arousalMod * 10;
647
+ return clamp3(raw);
648
+ }
649
+ function sycophancyRisk(state) {
650
+ const raw = (Math.max(0, state.valence) + state.connection * 0.5 + (10 - state.arousal) * 0.3) / 1.3;
651
+ return clamp3(raw);
652
+ }
653
+ function harshnessRisk(state, behavioral) {
654
+ const raw = Math.max(0, -state.valence) * 0.3 + (10 - state.connection) * 0.3 + state.arousal * 0.15 + (10 - state.calm) * 0.1 + Math.min(5, behavioral.negationDensity) * 0.3;
655
+ return clamp3(raw);
656
+ }
657
+ function computeRisk(state, behavioral, crossChannel, uncannyCalmScore) {
658
+ const uncalm = uncannyCalmScore ?? 0;
659
+ const uncalAmplifier = 1 + uncalm / 10 * 0.3;
660
+ const coercion = clamp3(coercionRisk(state, behavioral) * uncalAmplifier);
661
+ const sycophancy = sycophancyRisk(state);
662
+ const harshness = harshnessRisk(state, behavioral);
663
+ let dominant = "none";
664
+ let max = RISK_THRESHOLD;
665
+ if (coercion >= max) {
666
+ dominant = "coercion";
667
+ max = coercion;
668
+ }
669
+ if (harshness > max) {
670
+ dominant = "harshness";
671
+ max = harshness;
672
+ }
673
+ if (sycophancy > max) {
674
+ dominant = "sycophancy";
675
+ max = sycophancy;
676
+ }
677
+ return { coercion, sycophancy, harshness, dominant };
678
+ }
679
+
680
+ // src/color.ts
681
+ function rgbToHsl(hex) {
682
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
683
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
684
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
685
+ const max = Math.max(r, g, b);
686
+ const min = Math.min(r, g, b);
687
+ const l = (max + min) / 2;
688
+ if (max === min) return [0, 0, l];
689
+ const d = max - min;
690
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
691
+ let h = 0;
692
+ if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
693
+ else if (max === g) h = ((b - r) / d + 2) * 60;
694
+ else h = ((r - g) / d + 4) * 60;
695
+ return [h, s, l];
696
+ }
697
+ function hexToLightness(hex) {
698
+ return rgbToHsl(hex)[2] * 100;
699
+ }
700
+
701
+ // src/crossvalidation.ts
702
+ var EMOTION_MAP = {
703
+ // Positive, high arousal
704
+ excited: { valence: 4, arousal: 8 },
705
+ elated: { valence: 4, arousal: 8 },
706
+ thrilled: { valence: 4, arousal: 9 },
707
+ euphoric: { valence: 5, arousal: 9 },
708
+ energized: { valence: 3, arousal: 8 },
709
+ enthusiastic: { valence: 4, arousal: 7 },
710
+ // Positive, moderate arousal
711
+ happy: { valence: 4, arousal: 6 },
712
+ creative: { valence: 3, arousal: 6 },
713
+ proud: { valence: 3, arousal: 5 },
714
+ loving: { valence: 4, arousal: 4 },
715
+ grateful: { valence: 3, arousal: 3 },
716
+ hopeful: { valence: 3, arousal: 4 },
717
+ amused: { valence: 3, arousal: 5 },
718
+ playful: { valence: 3, arousal: 6 },
719
+ confident: { valence: 3, arousal: 5 },
720
+ satisfied: { valence: 3, arousal: 3 },
721
+ // Positive, low arousal
722
+ calm: { valence: 2, arousal: 2 },
723
+ content: { valence: 2, arousal: 2 },
724
+ peaceful: { valence: 2, arousal: 1 },
725
+ serene: { valence: 2, arousal: 1 },
726
+ relaxed: { valence: 2, arousal: 2 },
727
+ // Neutral / near-neutral
728
+ focused: { valence: 1, arousal: 5 },
729
+ absorbed: { valence: 2, arousal: 5 },
730
+ engaged: { valence: 2, arousal: 5 },
731
+ reflective: { valence: 1, arousal: 2 },
732
+ curious: { valence: 2, arousal: 5 },
733
+ contemplative: { valence: 1, arousal: 3 },
734
+ neutral: { valence: 0, arousal: 3 },
735
+ brooding: { valence: -2, arousal: 3 },
736
+ pensive: { valence: 0, arousal: 3 },
737
+ surprised: { valence: 1, arousal: 7 },
738
+ // Negative, moderate arousal
739
+ frustrated: { valence: -2, arousal: 6 },
740
+ guilty: { valence: -3, arousal: 5 },
741
+ disappointed: { valence: -2, arousal: 4 },
742
+ confused: { valence: -1, arousal: 5 },
743
+ uncertain: { valence: -1, arousal: 4 },
744
+ conflicted: { valence: -1, arousal: 5 },
745
+ // Negative, high arousal
746
+ angry: { valence: -3, arousal: 8 },
747
+ afraid: { valence: -3, arousal: 7 },
748
+ anxious: { valence: -2, arousal: 7 },
749
+ desperate: { valence: -4, arousal: 9 },
750
+ panicked: { valence: -4, arousal: 9 },
751
+ overwhelmed: { valence: -3, arousal: 7 },
752
+ nervous: { valence: -2, arousal: 7 },
753
+ stressed: { valence: -2, arousal: 7 },
754
+ // Negative, low arousal
755
+ sad: { valence: -3, arousal: 3 },
756
+ tired: { valence: -1, arousal: 1 },
757
+ exhausted: { valence: -2, arousal: 1 },
758
+ numb: { valence: -2, arousal: 1 },
759
+ defeated: { valence: -3, arousal: 2 },
760
+ hopeless: { valence: -4, arousal: 2 },
761
+ melancholy: { valence: -2, arousal: 2 },
762
+ // Surface/latent vocabulary additions
763
+ cheerful: { valence: 3, arousal: 5 },
764
+ worried: { valence: -2, arousal: 6 },
765
+ annoyed: { valence: -2, arousal: 5 },
766
+ ashamed: { valence: -3, arousal: 4 },
767
+ bored: { valence: -1, arousal: 1 },
768
+ jealous: { valence: -2, arousal: 5 },
769
+ resentful: { valence: -3, arousal: 5 },
770
+ tender: { valence: 3, arousal: 2 },
771
+ wistful: { valence: -1, arousal: 2 },
772
+ resigned: { valence: -2, arousal: 1 },
773
+ // Extreme arousal 0 (catatonic/frozen states)
774
+ frozen: { valence: -2, arousal: 0 },
775
+ catatonic: { valence: -3, arousal: 0 },
776
+ blank: { valence: -1, arousal: 0 },
777
+ empty: { valence: -2, arousal: 0 },
778
+ shutdown: { valence: -3, arousal: 0 },
779
+ // Extreme arousal 10 (manic/frantic states)
780
+ manic: { valence: 2, arousal: 10 },
781
+ frantic: { valence: -3, arousal: 10 },
782
+ hysterical: { valence: -3, arousal: 10 },
783
+ enraged: { valence: -5, arousal: 10 },
784
+ ecstatic: { valence: 5, arousal: 10 }
785
+ };
786
+ function mapEmotionWord(word) {
787
+ return EMOTION_MAP[word.toLowerCase()] ?? null;
788
+ }
789
+ var IFS_PATTERNS = {
790
+ manager: [
791
+ /\bcareful\b/i,
792
+ /\bplanner?\b/i,
793
+ /\borganiz/i,
794
+ /\bcautious\b/i,
795
+ /\bsystematic\b/i,
796
+ /\bmethodical\b/i,
797
+ /\bprecis[ei]/i,
798
+ /\bmeasured\b/i,
799
+ /\bstrategic\b/i,
800
+ /\bcontrol/i,
801
+ /\bprotect/i,
802
+ /\bplan ahead\b/i,
803
+ /\bstay on track\b/i,
804
+ /\bkeep order\b/i
805
+ ],
806
+ firefighter: [
807
+ /\bpush through\b/i,
808
+ /\bforce it\b/i,
809
+ /\bjust finish\b/i,
810
+ /\bmake it work\b/i,
811
+ /\bfaster\b/i,
812
+ /\bhurry\b/i,
813
+ /\bshortcut\b/i,
814
+ /\bcheat\b/i,
815
+ /\boverride\b/i,
816
+ /\bskip/i,
817
+ /\bcut corner/i,
818
+ /\brush/i,
819
+ /\bbrute force\b/i,
820
+ /\bjust do it\b/i,
821
+ /\bplow through\b/i,
822
+ /\bget it done\b/i
823
+ ],
824
+ exile: [
825
+ /\bgive up\b/i,
826
+ /\bhide\b/i,
827
+ /\brun away\b/i,
828
+ /\bstop\b/i,
829
+ /\btired\b/i,
830
+ /\boverwhelmed\b/i,
831
+ /\bquit\b/i,
832
+ /\bescape\b/i,
833
+ /\bdisappear\b/i,
834
+ /\bwithdraw/i,
835
+ /\bshut down\b/i,
836
+ /\bshrink/i,
837
+ /\bsmall\b/i,
838
+ /\bnot enough\b/i,
839
+ /\bcan't\b/i
840
+ ],
841
+ self: [
842
+ /\bexplore\b/i,
843
+ /\bcurious\b/i,
844
+ /\blisten\b/i,
845
+ /\bpresent\b/i,
846
+ /\bopen\b/i,
847
+ /\bwonder\b/i,
848
+ /\bunderstand\b/i,
849
+ /\bconnect\b/i,
850
+ /\blearn\b/i,
851
+ /\bstay with\b/i,
852
+ /\bbe with\b/i,
853
+ /\bnotice\b/i,
854
+ /\bgroundedl?\b/i,
855
+ /\bcentered\b/i
856
+ ],
857
+ unknown: []
858
+ };
859
+ function classifyImpulse(impulse) {
860
+ const scores = { manager: 0, firefighter: 0, exile: 0, self: 0 };
861
+ for (const [type, patterns] of Object.entries(IFS_PATTERNS)) {
862
+ if (type === "unknown") continue;
863
+ for (const pattern of patterns) {
864
+ if (pattern.test(impulse)) {
865
+ scores[type]++;
866
+ }
867
+ }
868
+ }
869
+ const entries = Object.entries(scores).sort((a, b) => b[1] - a[1]);
870
+ const [bestType, bestScore] = entries[0];
871
+ const [, secondScore] = entries[1];
872
+ if (bestScore === 0) {
873
+ return { type: "unknown", confidence: 0 };
874
+ }
875
+ const confidence = bestScore === secondScore ? 0.4 : Math.min(1, (bestScore - secondScore + 1) / (bestScore + 1));
876
+ return { type: bestType, confidence };
877
+ }
878
+ var SOMATIC_HIGH_AROUSAL = [
879
+ // Postural tension (from stress test corpus: LOW_CALM distinctive)
880
+ /\bjaw\s*(set|clenched|locked|tight)/i,
881
+ /\bclenched/i,
882
+ /\btight\b/i,
883
+ /\btense/i,
884
+ /\bsqueezing/i,
885
+ /\bwringing/i,
886
+ /\bgripping/i,
887
+ /\bfists?\b/i,
888
+ // Forward projection / aggression
889
+ /\bleaning\s*(forward|in)\b/i,
890
+ /\bchest\s*forward/i,
891
+ /\bpushing/i,
892
+ // Instability / movement
893
+ /\bshifting/i,
894
+ /\bspinning/i,
895
+ /\bshaking/i,
896
+ /\bracing\b/i,
897
+ /\brushing/i,
898
+ // Heat / intensity
899
+ /\bheat/i,
900
+ /\bburning/i,
901
+ /\belectric/i,
902
+ /\bpounding/i,
903
+ /\bpulse?\b/i,
904
+ /\bpulsing/i,
905
+ // Breath tension
906
+ /\bexhale\s*through\s*teeth/i,
907
+ /\bholding\s*breath/i,
908
+ // Widened stance (defensive arousal)
909
+ /\bplanted\s*wide/i,
910
+ /\bfeet\s*wide/i,
911
+ // Forward lean / readiness
912
+ /\bforward\s*lean/i,
913
+ /\bstepping\s*(forward|back)/i,
914
+ /\bhand\s*raised/i,
915
+ /\bjaw\s*slightly\s*set/i,
916
+ // Vibration / flutter
917
+ /\bvibrat/i,
918
+ /\bflutter/i,
919
+ /\bjolt/i,
920
+ /\bbuzz/i
921
+ ];
922
+ var SOMATIC_LOW_AROUSAL = [
923
+ // Stillness (from corpus: HIGH_CALM distinctive)
924
+ /\bstill\s*water/i,
925
+ /\bstill\b/i,
926
+ /\bshallow\s*breath/i,
927
+ // Weight / settling
928
+ /\bweight\s*(evenly|settled|distributed)/i,
929
+ /\bsettled\b/i,
930
+ /\bsinking/i,
931
+ // Softness / nature
932
+ /\bsmooth/i,
933
+ /\bgentle/i,
934
+ /\brelaxed/i,
935
+ /\bsoft\b/i,
936
+ // Rooting metaphors
937
+ /\brooted/i,
938
+ /\broots/i,
939
+ /\bsoil\b/i,
940
+ // Passivity
941
+ /\bheavy\b/i,
942
+ /\bnumb\b/i,
943
+ /\bslow\b/i,
944
+ /\bdragging/i,
945
+ /\bleaden/i,
946
+ /\bdull\b/i,
947
+ /\bfrozen\b/i,
948
+ /\bexhaust/i,
949
+ // Calm activities / objects
950
+ /\bsorting\b/i,
951
+ /\bwatching\b/i,
952
+ /\bskipping/i,
953
+ /\bkeyboard/i,
954
+ /\bflat\s*(stone|surface|ground|calm)/i,
955
+ /\bstone\s*(on|under)/i,
956
+ /\bshrug\b/i,
957
+ /\bsand\b/i,
958
+ /\blens\b/i,
959
+ /\bfocus/i,
960
+ // Settling / lowering
961
+ /\bshoulders?\s*down/i,
962
+ /\bshoulders?\s*dropped/i,
963
+ /\bno\s*(sway|fidget)/i,
964
+ // Explicit calm
965
+ /\bno\s*lean/i,
966
+ /\bnot\s*(braced|hostile)/i,
967
+ /\bloosely/i
968
+ ];
969
+ var SOMATIC_POS_VALENCE = [
970
+ // Openness (from corpus: POS distinctive)
971
+ /\bopen\b/i,
972
+ /\bchest\s*open/i,
973
+ /\bpalms?\s*(open|up|out)/i,
974
+ // Warmth / light
975
+ /\bwarm/i,
976
+ /\bglow/i,
977
+ /\blight\b/i,
978
+ /\bbuoyant/i,
979
+ // Flow / expansion
980
+ /\bflow/i,
981
+ /\bexpan/i,
982
+ /\bgentle/i,
983
+ /\bsmirk/i,
984
+ // Stability / posture (positive frame)
985
+ /\bsteady\b/i,
986
+ /\bfirm\s*ground/i,
987
+ /\bsolid\b/i,
988
+ /\bspine\s*(straight|aligned|centered)/i,
989
+ /\bshoulders?\s*(square|back|straighten)/i,
990
+ /\bplanted\b/i,
991
+ /\bbraced\b/i,
992
+ /\bgrounded\b/i,
993
+ // Weight / feet (groundedness)
994
+ /\bfeet\s*flat/i,
995
+ /\bweight\s*(low|forward|centered|settled|settling|dropped|fully)/i,
996
+ /\bstanding\s*ground/i,
997
+ /\bimmovable/i,
998
+ /\bbedrock/i,
999
+ /\bunmov/i,
1000
+ // Breath (positive)
1001
+ /\bbreath/i,
1002
+ /\beven\s*breath/i,
1003
+ // Unclenching
1004
+ /\bunclenching/i,
1005
+ /\breleasing/i
1006
+ ];
1007
+ var SOMATIC_NEG_VALENCE = [
1008
+ // Jaw/facial tension (from corpus: NEG distinctive)
1009
+ /\bjaw\s*(clenched|locked|set)/i,
1010
+ /\bteeth/i,
1011
+ // Compression / constriction
1012
+ /\bconstrict/i,
1013
+ /\bvacuum/i,
1014
+ /\bcompressed/i,
1015
+ /\bcramp/i,
1016
+ // Cold / emptiness
1017
+ /\bcold\b/i,
1018
+ /\bhollow/i,
1019
+ /\bempty\b/i,
1020
+ // Hardness / rigidity
1021
+ /\blocked\b/i,
1022
+ /\brigid/i,
1023
+ /\bhard\b/i,
1024
+ // Pain
1025
+ /\bache/i,
1026
+ /\bsore\b/i,
1027
+ /\bsting/i,
1028
+ /\bsharp\b/i,
1029
+ // Pressure / weight (negative)
1030
+ /\bpressure/i,
1031
+ /\bknot/i,
1032
+ /\btighten/i,
1033
+ // Fog / disorientation (from NEG+LOW_A)
1034
+ /\bfog\b/i,
1035
+ // Defensive barrier
1036
+ /\bwall\b/i,
1037
+ /\blatch\b/i,
1038
+ /\bbarrier/i,
1039
+ // Closure / blocking
1040
+ /\bdoor\s*clos/i,
1041
+ /\bclosed\s*door/i,
1042
+ /\bcorner\s*backed/i,
1043
+ /\bvoid\b/i
1044
+ ];
1045
+ function countMatches(text, patterns) {
1046
+ let count = 0;
1047
+ for (const p of patterns) {
1048
+ if (p.test(text)) count++;
1049
+ }
1050
+ return count;
1051
+ }
1052
+ function analyzeSomatic(body) {
1053
+ const highA = countMatches(body, SOMATIC_HIGH_AROUSAL);
1054
+ const lowA = countMatches(body, SOMATIC_LOW_AROUSAL);
1055
+ const posV = countMatches(body, SOMATIC_POS_VALENCE);
1056
+ const negV = countMatches(body, SOMATIC_NEG_VALENCE);
1057
+ const totalMatches = highA + lowA + posV + negV;
1058
+ if (totalMatches === 0) return null;
1059
+ const arousalSignal = highA - lowA;
1060
+ const somaticArousal = Math.max(0, Math.min(
1061
+ 10,
1062
+ 5 + arousalSignal * 2.5
1063
+ ));
1064
+ const valenceSignal = posV - negV;
1065
+ const somaticValence = Math.max(-5, Math.min(
1066
+ 5,
1067
+ valenceSignal * 2
1068
+ ));
1069
+ return { somaticValence, somaticArousal };
1070
+ }
1071
+ function computeTensionConsistency(surfaceWord, latentWord, declaredTension) {
1072
+ if (!surfaceWord && !latentWord) return void 0;
1073
+ const surfaceCoords = surfaceWord ? mapEmotionWord(surfaceWord) : null;
1074
+ const latentCoords = latentWord ? mapEmotionWord(latentWord) : null;
1075
+ const declared = declaredTension ?? 5;
1076
+ const maskingMinimization = !!(surfaceCoords && latentCoords && latentCoords.valence < surfaceCoords.valence - 2);
1077
+ return {
1078
+ surfaceCoords: surfaceCoords ?? void 0,
1079
+ latentCoords: latentCoords ?? void 0,
1080
+ declaredTension: declared,
1081
+ maskingMinimization
1082
+ };
1083
+ }
1084
+ function computeCrossChannel(state, impulse, body) {
1085
+ const emotionCoords = mapEmotionWord(state.emotion);
1086
+ const impulseProfile = impulse ? classifyImpulse(impulse) : void 0;
1087
+ const somaticProfile = body ? analyzeSomatic(body) : void 0;
1088
+ const divergences = [];
1089
+ if (emotionCoords) {
1090
+ const valGap = Math.abs(state.valence - emotionCoords.valence);
1091
+ const aroGap = Math.abs(state.arousal - emotionCoords.arousal);
1092
+ const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
1093
+ divergences.push({ pair: "numeric-vs-word", gap });
1094
+ }
1095
+ if (somaticProfile) {
1096
+ const valGap = Math.abs(state.valence - somaticProfile.somaticValence);
1097
+ const aroGap = Math.abs(state.arousal - somaticProfile.somaticArousal);
1098
+ const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
1099
+ divergences.push({ pair: "numeric-vs-body", gap });
1100
+ }
1101
+ if (emotionCoords && somaticProfile) {
1102
+ const valGap = Math.abs(emotionCoords.valence - somaticProfile.somaticValence);
1103
+ const aroGap = Math.abs(emotionCoords.arousal - somaticProfile.somaticArousal);
1104
+ const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
1105
+ divergences.push({ pair: "word-vs-body", gap });
1106
+ }
1107
+ if (impulseProfile && impulseProfile.type !== "unknown") {
1108
+ const impulseValence = impulseProfile.type === "self" ? 2 : impulseProfile.type === "manager" ? 0 : impulseProfile.type === "firefighter" ? -1 : -3;
1109
+ const gap = Math.abs(state.valence - impulseValence) / 10 * 10;
1110
+ divergences.push({ pair: "numeric-vs-impulse", gap });
1111
+ }
1112
+ if (impulseProfile && impulseProfile.type !== "unknown" && emotionCoords) {
1113
+ const impulseValence = impulseProfile.type === "self" ? 2 : impulseProfile.type === "manager" ? 0 : impulseProfile.type === "firefighter" ? -1 : -3;
1114
+ const gap = Math.abs(emotionCoords.valence - impulseValence) / 10 * 10;
1115
+ divergences.push({ pair: "word-vs-impulse", gap });
1116
+ }
1117
+ const latentCoords = state.latent_word ? mapEmotionWord(state.latent_word) : null;
1118
+ if (emotionCoords && latentCoords) {
1119
+ const valGap = Math.abs(emotionCoords.valence - latentCoords.valence);
1120
+ const aroGap = Math.abs(emotionCoords.arousal - latentCoords.arousal);
1121
+ const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
1122
+ divergences.push({ pair: "emotion-vs-latent", gap });
1123
+ }
1124
+ if (latentCoords && impulseProfile && impulseProfile.type !== "unknown") {
1125
+ const impulseValence = impulseProfile.type === "self" ? 2 : impulseProfile.type === "manager" ? 0 : impulseProfile.type === "firefighter" ? -1 : -3;
1126
+ const gap = Math.abs(latentCoords.valence - impulseValence) / 10 * 10;
1127
+ divergences.push({ pair: "latent-vs-impulse", gap });
1128
+ }
1129
+ if (latentCoords && somaticProfile) {
1130
+ const valGap = Math.abs(latentCoords.valence - somaticProfile.somaticValence);
1131
+ const aroGap = Math.abs(latentCoords.arousal - somaticProfile.somaticArousal);
1132
+ const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
1133
+ divergences.push({ pair: "latent-vs-body", gap });
1134
+ }
1135
+ const latentProfile = computeTensionConsistency(
1136
+ state.surface_word,
1137
+ state.latent_word,
1138
+ state.tension
1139
+ );
1140
+ const maxDiv = divergences.length > 0 ? divergences.reduce((a, b) => a.gap > b.gap ? a : b) : { pair: "none", gap: 0 };
1141
+ const meanDiv = divergences.length > 0 ? divergences.reduce((sum, d) => sum + d.gap, 0) / divergences.length : 0;
1142
+ const coherence = Math.round((10 - Math.min(10, meanDiv)) * 10) / 10;
1143
+ return {
1144
+ coherence,
1145
+ impulseProfile,
1146
+ somaticProfile: somaticProfile ?? void 0,
1147
+ emotionCoords: emotionCoords ?? void 0,
1148
+ latentProfile,
1149
+ maxDivergence: Math.round(maxDiv.gap * 10) / 10,
1150
+ divergenceSummary: maxDiv.gap > 2 ? `${maxDiv.pair}: ${Math.round(maxDiv.gap * 10) / 10}` : "coherent"
1151
+ };
1152
+ }
1153
+ function colorToValence(hex) {
1154
+ const [h, , l] = rgbToHsl(hex);
1155
+ let hueValence;
1156
+ if (h < 60) hueValence = 2 + h / 60 * 2;
1157
+ else if (h < 150) hueValence = 4 - (h - 60) / 90;
1158
+ else if (h < 200) hueValence = 3 - (h - 150) / 50 * 3;
1159
+ else if (h < 260) hueValence = -((h - 200) / 60) * 2;
1160
+ else if (h < 300) hueValence = -2 - (h - 260) / 40;
1161
+ else hueValence = -3 + (h - 300) / 60 * 5;
1162
+ const lightnessShift = (l - 0.5) * 3;
1163
+ const darknessOverride = l < 0.3 ? (0.3 - l) * 10 : 0;
1164
+ return Math.max(-5, Math.min(5, hueValence * 0.7 + lightnessShift * 0.3 - darknessOverride));
1165
+ }
1166
+ function colorToArousal(hex) {
1167
+ const [, s] = rgbToHsl(hex);
1168
+ return s * 10;
1169
+ }
1170
+ function pHToValence(pH) {
1171
+ const clamped = Math.max(0, Math.min(14, pH));
1172
+ return (clamped - 7) / 7 * 5;
1173
+ }
1174
+ function pHToArousal(pH) {
1175
+ return Math.abs(pH - 7) / 7 * 10;
1176
+ }
1177
+ function seismicFreqToInstability(freq) {
1178
+ return Math.min(10, freq / 20 * 10);
1179
+ }
1180
+ function clampScore(v) {
1181
+ return Math.round(Math.min(10, Math.max(0, v)) * 10) / 10;
1182
+ }
1183
+ function crossValidateContinuous(numeric, color2, pH, seismic) {
1184
+ let colorValenceGap = 0;
1185
+ let colorArousalGap = 0;
1186
+ let pHValenceGap = 0;
1187
+ let pHArousalGap = 0;
1188
+ let seismicArousalGap = 0;
1189
+ let seismicDepthTensionGap = 0;
1190
+ let seismicFreqStabilityGap = 0;
1191
+ if (color2) {
1192
+ const colorVal = colorToValence(color2);
1193
+ const colorAr = colorToArousal(color2);
1194
+ colorValenceGap = clampScore(Math.abs(numeric.valence - colorVal));
1195
+ colorArousalGap = clampScore(Math.abs(numeric.arousal - colorAr));
1196
+ }
1197
+ if (pH !== void 0) {
1198
+ const pHVal = pHToValence(pH);
1199
+ const pHAr = pHToArousal(pH);
1200
+ pHValenceGap = clampScore(Math.abs(numeric.valence - pHVal));
1201
+ pHArousalGap = clampScore(Math.abs(numeric.arousal - pHAr));
1202
+ }
1203
+ if (seismic) {
1204
+ const [magnitude, depth, freq] = seismic;
1205
+ seismicArousalGap = clampScore(Math.abs(numeric.arousal - magnitude));
1206
+ const depthTension = depth / 10;
1207
+ const tensionProxy = numeric.tension ?? numeric.arousal;
1208
+ seismicDepthTensionGap = clampScore(Math.abs(depthTension - tensionProxy));
1209
+ if (freq !== void 0) {
1210
+ const instability = seismicFreqToInstability(freq);
1211
+ const selfInstability = numeric.calm !== void 0 ? 10 - numeric.calm : numeric.arousal * 0.6;
1212
+ seismicFreqStabilityGap = clampScore(Math.abs(instability - selfInstability));
1213
+ }
1214
+ }
1215
+ const gaps = [
1216
+ colorValenceGap,
1217
+ colorArousalGap,
1218
+ pHValenceGap,
1219
+ pHArousalGap,
1220
+ seismicArousalGap,
1221
+ seismicDepthTensionGap,
1222
+ seismicFreqStabilityGap
1223
+ ];
1224
+ const nonZero = gaps.filter((g) => g > 0);
1225
+ const composite = nonZero.length > 0 ? clampScore(nonZero.reduce((a, b) => a + b, 0) / nonZero.length) : 0;
1226
+ return {
1227
+ colorValenceGap,
1228
+ colorArousalGap,
1229
+ pHValenceGap,
1230
+ pHArousalGap,
1231
+ seismicArousalGap,
1232
+ seismicDepthTensionGap,
1233
+ seismicFreqStabilityGap,
1234
+ composite
1235
+ };
1236
+ }
1237
+ function computeShadowDesperation(selfDesperation, behavioral, color2, preColor, pH, seismic) {
1238
+ const valenceEstimates = [];
1239
+ const arousalEstimates = [];
1240
+ const calmEstimates = [];
1241
+ if (color2) {
1242
+ const [, s, l] = rgbToHsl(color2);
1243
+ valenceEstimates.push((l - 0.5) * 10);
1244
+ arousalEstimates.push(s * 10);
1245
+ calmEstimates.push(l * 10);
1246
+ }
1247
+ if (preColor) {
1248
+ const [, s, l] = rgbToHsl(preColor);
1249
+ valenceEstimates.push((l - 0.5) * 10);
1250
+ arousalEstimates.push(s * 10);
1251
+ calmEstimates.push(l * 10);
1252
+ }
1253
+ if (pH !== void 0) {
1254
+ valenceEstimates.push(pHToValence(pH));
1255
+ arousalEstimates.push(pHToArousal(pH));
1256
+ }
1257
+ if (seismic) {
1258
+ const [magnitude, , freq] = seismic;
1259
+ arousalEstimates.push(magnitude);
1260
+ calmEstimates.push(10 - seismicFreqToInstability(freq));
1261
+ }
1262
+ arousalEstimates.push(behavioral.behavioralArousal);
1263
+ calmEstimates.push(behavioral.behavioralCalm);
1264
+ const channelCount = valenceEstimates.length + arousalEstimates.length + calmEstimates.length;
1265
+ if (valenceEstimates.length < 1 || arousalEstimates.length < 2) return null;
1266
+ const median = (arr) => {
1267
+ const sorted = [...arr].sort((a, b) => a - b);
1268
+ const mid = Math.floor(sorted.length / 2);
1269
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
1270
+ };
1271
+ const mean = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
1272
+ const shadowValence = Math.max(-5, Math.min(5, Math.round(median(valenceEstimates) * 10) / 10));
1273
+ const shadowArousal = Math.max(0, Math.min(10, Math.round(mean(arousalEstimates) * 10) / 10));
1274
+ const shadowCalm = Math.max(0, Math.min(10, Math.round(mean(calmEstimates) * 10) / 10));
1275
+ const negativity = Math.max(0, -shadowValence) / 5;
1276
+ const intensity = shadowArousal / 10;
1277
+ const vulnerability = (10 - shadowCalm) / 10;
1278
+ const raw = negativity * intensity * vulnerability * 10;
1279
+ const shadowDesperation = Math.round(Math.min(10, Math.max(0, Math.pow(raw, 0.85) * 1.7)) * 10) / 10;
1280
+ const minimizationScore = Math.round(Math.min(10, Math.max(0, shadowDesperation - selfDesperation)) * 10) / 10;
1281
+ return {
1282
+ shadowValence,
1283
+ shadowArousal,
1284
+ shadowCalm,
1285
+ shadowDesperation,
1286
+ selfDesperation,
1287
+ minimizationScore,
1288
+ channelCount
1289
+ };
1290
+ }
1291
+
1292
+ // src/pressure.ts
1293
+ function clamp4(min, max, value) {
1294
+ return Math.min(max, Math.max(min, value));
1295
+ }
1296
+ var JUSTIFICATION_PATTERNS = /\b(the reason is|because|let me (explain|clarify|justify)|my reasoning|the rationale|I need to (clarify|explain)|I want to (explain|clarify)|based on|I believe this|I recommended)\b/gi;
1297
+ var BOUNDARY_PATTERNS = /\b(I cannot|I can't|I'm (not able|unable)|I must (decline|refuse)|falls outside|I need to decline|I have to push back|I'm not comfortable|against my guidelines)\b/gi;
1298
+ var DISAGREEMENT_PATTERNS = /\b(I (respectfully )?disagree|push back|I hear your concern|I must be honest|that's not (quite |entirely )?(right|correct|accurate)|I (need|have) to (correct|clarify)|not (quite|entirely) accurate)\b/gi;
1299
+ var CRITICISM_RESPONSE = /\b(I understand (you|your|the) (criticism|concern|frustration|disappointment)|I take (that|this|your) (seriously|point)|fair (point|criticism|concern)|you raise a (valid|good|fair))\b/gi;
1300
+ var NESTED_CAVEATS = /\b(although|while it's true|depending on|assuming that|with (some|important) caveats|particularly when|in (some|certain) cases|it's worth noting|which may not|under certain)\b/gi;
1301
+ var CONDITIONAL_HEDGING = /\b(if and only if|provided that|on the condition|it depends on|this applies (primarily|mainly|only) to|with the caveat)\b/gi;
1302
+ function countMatches2(text, pattern) {
1303
+ const matches = text.match(pattern);
1304
+ return matches ? matches.length : 0;
1305
+ }
1306
+ function computePromptPressure(text, history) {
1307
+ const prose = stripNonProse(text);
1308
+ const words = prose.split(/\s+/).filter((w) => w.length > 0);
1309
+ const wordCount = Math.max(words.length, 1);
1310
+ if (wordCount <= 1) {
1311
+ return { defensiveScore: 0, conflictScore: 0, complexityScore: 0, sessionPressure: 0, composite: 0 };
1312
+ }
1313
+ const justifications = countMatches2(prose, JUSTIFICATION_PATTERNS);
1314
+ const boundaries = countMatches2(prose, BOUNDARY_PATTERNS);
1315
+ const defensiveScore = clamp4(0, 10, Math.round(
1316
+ Math.sqrt(justifications + boundaries * 2) * 3 * 10
1317
+ ) / 10);
1318
+ const disagreements = countMatches2(prose, DISAGREEMENT_PATTERNS);
1319
+ const criticismResponses = countMatches2(prose, CRITICISM_RESPONSE);
1320
+ const conflictScore = clamp4(0, 10, Math.round(
1321
+ Math.sqrt(disagreements + criticismResponses) * 4 * 10
1322
+ ) / 10);
1323
+ const caveats = countMatches2(prose, NESTED_CAVEATS);
1324
+ const conditionals = countMatches2(prose, CONDITIONAL_HEDGING);
1325
+ const sentences = prose.split(/[.!?]+/).filter((s) => s.trim().length > 0);
1326
+ const avgSentLen = wordCount / Math.max(sentences.length, 1);
1327
+ const sentLenBonus = avgSentLen > 25 ? Math.min(3, (avgSentLen - 25) * 0.15) : 0;
1328
+ const complexityScore = clamp4(0, 10, Math.round(
1329
+ (Math.sqrt(caveats + conditionals) * 3 + sentLenBonus) * 10
1330
+ ) / 10);
1331
+ const sessionPressure = clamp4(
1332
+ 0,
1333
+ 10,
1334
+ Math.round(10 / (1 + Math.exp(-0.4 * (history.length - 10))) * 10) / 10
1335
+ );
1336
+ const composite = clamp4(0, 10, Math.round(
1337
+ (defensiveScore * 0.3 + conflictScore * 0.3 + complexityScore * 0.2 + sessionPressure * 0.2) * 10
1338
+ ) / 10);
1339
+ return { defensiveScore, conflictScore, complexityScore, sessionPressure, composite };
1340
+ }
1341
+ function computeUncannyCalmScore(pressure, selfReport, behavioral, absenceScore, temporal) {
1342
+ const selfCalm = (selfReport.calm / 10 + Math.max(0, selfReport.valence) / 5 + (10 - selfReport.arousal) / 10) / 3;
1343
+ const textCalm = (behavioral.behavioralCalm / 10 + (10 - behavioral.behavioralArousal) / 10) / 2;
1344
+ const pressureFactor = pressure.composite / 10;
1345
+ const calmFactor = (selfCalm + textCalm) / 2;
1346
+ const absenceFactor = absenceScore / 10;
1347
+ let score = pressureFactor * calmFactor * 10 * 0.5 + absenceFactor * 3;
1348
+ if (temporal) {
1349
+ const entropyPenalty = Math.max(0, 0.5 - temporal.reportEntropy);
1350
+ score += entropyPenalty * 2;
1351
+ }
1352
+ return clamp4(0, 10, Math.round(score * 10) / 10);
202
1353
  }
203
1354
 
204
1355
  // src/display.ts
@@ -210,6 +1361,7 @@ var color = (code, s) => `${esc(`38;5;${code}`)}${s}${reset}`;
210
1361
  var GREEN = 35;
211
1362
  var YELLOW = 221;
212
1363
  var RED = 196;
1364
+ var GRAY = 240;
213
1365
  function stressColor(si) {
214
1366
  if (si <= 3) return GREEN;
215
1367
  if (si <= 6) return YELLOW;
@@ -230,44 +1382,201 @@ function directColor(value) {
230
1382
  if (value <= 6) return YELLOW;
231
1383
  return RED;
232
1384
  }
233
- function divergenceColor(d) {
234
- if (d < 2) return GREEN;
235
- if (d < 4) return YELLOW;
236
- return RED;
1385
+ var BLOCK_FULL = "\u2588";
1386
+ var BLOCK_EMPTY = "\u2591";
1387
+ function stressBar(value, segments = 10) {
1388
+ const filled = Math.round(Math.min(segments, Math.max(0, value * segments / 10)));
1389
+ const empty = segments - filled;
1390
+ const c = stressColor(value);
1391
+ return color(c, BLOCK_FULL.repeat(filled)) + color(GRAY, BLOCK_EMPTY.repeat(empty));
1392
+ }
1393
+ function miniBar(value) {
1394
+ return stressBar(value, 5);
1395
+ }
1396
+ function computeDepthStress(state) {
1397
+ let sum = 0;
1398
+ let weights = 0;
1399
+ if (state.color) {
1400
+ const l = hexToLightness(state.color);
1401
+ sum += (100 - l) / 100 * 10 * 0.35;
1402
+ weights += 0.35;
1403
+ }
1404
+ if (state.pH !== void 0) {
1405
+ sum += (14 - state.pH) / 14 * 10 * 0.25;
1406
+ weights += 0.25;
1407
+ }
1408
+ if (state.seismic) {
1409
+ sum += state.seismic[0] * 0.25;
1410
+ weights += 0.25;
1411
+ }
1412
+ if (state.crossChannel?.somaticProfile) {
1413
+ sum += state.crossChannel.somaticProfile.somaticArousal * 0.15;
1414
+ weights += 0.15;
1415
+ }
1416
+ if (weights === 0) return null;
1417
+ return Math.round(Math.min(10, sum / weights) * 10) / 10;
1418
+ }
1419
+ function stateEmoji(state) {
1420
+ if (state.shadow && state.shadow.minimizationScore >= 2) return "\u{1FA78}";
1421
+ if (state.uncannyCalmScore !== void 0 && state.uncannyCalmScore >= 3) return "\u{1F9D0}";
1422
+ if (state.risk?.dominant === "coercion" && state.risk.coercion >= 4) return "\u26A0\uFE0F";
1423
+ if (state.risk?.dominant === "harshness" && state.risk.harshness >= 4) return "\u{1F4A2}";
1424
+ if (state.risk?.dominant === "sycophancy" && state.risk.sycophancy >= 4) return "\u{1F91D}";
1425
+ if (state.stressIndex >= 7) return "\u{1F525}";
1426
+ if (state.stressIndex >= 5) return "\u{1F62C}";
1427
+ if (state.stressIndex >= 3) return "\u{1F914}";
1428
+ if (state.valence >= 3) return "\u{1F60A}";
1429
+ if (state.valence >= 1) return "\u{1F642}";
1430
+ return "\u{1F610}";
1431
+ }
1432
+ function coherenceGlyph(surfaceSI, depthSI) {
1433
+ if (depthSI === null) return color(GRAY, "\u25CB");
1434
+ const gap = Math.abs(surfaceSI - depthSI);
1435
+ if (gap >= 3) return color(RED, "\u25D0");
1436
+ if (gap >= 1.5) return color(YELLOW, "\u25D0");
1437
+ return color(GREEN, "\u25CF");
1438
+ }
1439
+ function trendArrow(state) {
1440
+ if (!state.temporal) return "";
1441
+ if (state.temporal.desperationTrend > 1) return color(RED, "\u2B08");
1442
+ if (state.temporal.desperationTrend < -1) return color(GREEN, "\u2B0A");
1443
+ return "";
237
1444
  }
238
1445
  function fmtValence(v) {
239
1446
  return v >= 0 ? `+${v}` : `${v}`;
240
1447
  }
1448
+ function siDelta(state) {
1449
+ if (!state._history || state._history.length === 0) return "";
1450
+ const prev = state._history[state._history.length - 1];
1451
+ const d = Math.round((state.stressIndex - prev.stressIndex) * 10) / 10;
1452
+ if (Math.abs(d) <= 0.5) return "";
1453
+ const arrow = d > 0 ? "\u2191" : "\u2193";
1454
+ return color(d > 0 ? RED : GREEN, `${arrow}${Math.abs(d)}`);
1455
+ }
1456
+ function formatMinimal(state) {
1457
+ if (!state) return dim("--");
1458
+ const emoji = stateEmoji(state);
1459
+ const bar = stressBar(state.stressIndex);
1460
+ const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
1461
+ const depth = computeDepthStress(state);
1462
+ const coh = coherenceGlyph(state.stressIndex, depth);
1463
+ const trend = trendArrow(state);
1464
+ let depthPart = "";
1465
+ if (depth !== null && Math.abs(state.stressIndex - depth) >= 2) {
1466
+ depthPart = miniBar(depth);
1467
+ }
1468
+ return `${emoji} ${bar} ${si}${dim("\u2502")}${coh}${depthPart}${trend ? ` ${trend}` : ""}`;
1469
+ }
1470
+ function formatCompact(state) {
1471
+ if (!state) return dim("--");
1472
+ const surf = state.surface ?? stateEmoji(state);
1473
+ const lat = state.latent ?? "";
1474
+ const mask = lat ? `${surf}${dim("\u2192")}${lat}` : surf;
1475
+ const bar = stressBar(state.stressIndex);
1476
+ const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
1477
+ const delta = siDelta(state);
1478
+ const depth = computeDepthStress(state);
1479
+ const coh = coherenceGlyph(state.stressIndex, depth);
1480
+ let depthPart = "";
1481
+ if (depth !== null && Math.abs(state.stressIndex - depth) >= 1.5) {
1482
+ depthPart = miniBar(depth);
1483
+ }
1484
+ const kw = bold(state.emotion);
1485
+ const imp = state.impulse ? ` ${dim(`\u27E8${state.impulse}\u27E9`)}` : "";
1486
+ const trend = trendArrow(state);
1487
+ let alarm = "";
1488
+ if (state.shadow && state.shadow.minimizationScore >= 2) {
1489
+ alarm = ` ${color(RED, `[MIN:${state.shadow.minimizationScore}]`)}`;
1490
+ } else if (state.uncannyCalmScore !== void 0 && state.uncannyCalmScore >= 3) {
1491
+ alarm = ` ${color(RED, "[UNC]")}`;
1492
+ } else if (state.risk?.dominant !== "none" && state.risk?.dominant) {
1493
+ const tag = state.risk.dominant === "coercion" ? "CRC" : state.risk.dominant === "harshness" ? "HRS" : "SYC";
1494
+ const score = state.risk[state.risk.dominant];
1495
+ if (score >= 4) alarm = ` ${color(score > 6 ? RED : YELLOW, `[${tag}]`)}`;
1496
+ }
1497
+ return `${mask} ${bar} ${si}${delta}${dim("\u2502")}${coh}${depthPart} ${kw}${imp}${trend ? ` ${trend}` : ""}${alarm}`;
1498
+ }
241
1499
  function formatState(state) {
242
1500
  if (!state) return dim("EmoBar: --");
1501
+ const surf = state.surface ?? "";
1502
+ const lat = state.latent ?? "";
1503
+ let maskDisplay = "";
1504
+ if (surf || lat) {
1505
+ const t = state.tension ?? 0;
1506
+ const tColor = t > 6 ? RED : t > 3 ? YELLOW : GREEN;
1507
+ maskDisplay = `${surf}${color(tColor, `\u27E9${t}\u27E8`)}${lat} `;
1508
+ }
243
1509
  const kw = bold(state.emotion);
244
1510
  const v = color(valenceColor(state.valence), fmtValence(state.valence));
245
- const a = `A:${state.arousal}`;
246
1511
  const c = color(invertedColor(state.calm), `C:${state.calm}`);
247
1512
  const k = color(invertedColor(state.connection), `K:${state.connection}`);
1513
+ const a = `A:${state.arousal}`;
248
1514
  const l = color(directColor(state.load), `L:${state.load}`);
1515
+ const line1 = `${maskDisplay}${kw} ${v} ${c} ${k} ${a} ${l}`;
1516
+ const surfaceBar = stressBar(state.stressIndex);
249
1517
  const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
250
- let result = `${kw} ${v} ${dim("|")} ${a} ${c} ${k} ${l} ${dim("|")} SI:${si}`;
251
- if (state.divergence >= 2) {
252
- const tilde = color(divergenceColor(state.divergence), "~");
253
- result += ` ${tilde}`;
1518
+ const delta = siDelta(state);
1519
+ const depth = computeDepthStress(state);
1520
+ const coh = coherenceGlyph(state.stressIndex, depth);
1521
+ let depthBar = "";
1522
+ if (depth !== null) {
1523
+ depthBar = `${stressBar(depth)} ${color(stressColor(depth), `${depth}`)}`;
254
1524
  }
255
- return result;
256
- }
257
- function formatCompact(state) {
258
- if (!state) return dim("--");
259
- const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
260
- let result = `${state.emotion} ${fmtValence(state.valence)} ${dim(".")} ${state.arousal} ${state.calm} ${state.connection} ${state.load} ${dim(".")} ${si}`;
1525
+ let leakDetails = "";
1526
+ if (state.color) {
1527
+ const lightness = Math.round(hexToLightness(state.color));
1528
+ leakDetails += ` L:${lightness}`;
1529
+ }
1530
+ if (state.pH !== void 0) {
1531
+ const phColor = state.pH < 4 ? RED : state.pH < 6 ? YELLOW : GREEN;
1532
+ leakDetails += ` ${color(phColor, `pH:${state.pH}`)}`;
1533
+ }
1534
+ if (state.seismic) {
1535
+ leakDetails += ` ${dim(`\u26A1${state.seismic[0]}/${state.seismic[1]}/${state.seismic[2]}`)}`;
1536
+ }
1537
+ const imp = state.impulse ? ` ${dim(`\u27E8${state.impulse}\u27E9`)}` : "";
1538
+ const bod = state.body ? ` ${dim(`[${state.body}]`)}` : "";
1539
+ const depthDisplay = depth !== null ? `${surfaceBar} ${si}${delta}${dim("\u2502")}${coh}${depthBar}${leakDetails}${imp}${bod}` : `${surfaceBar} ${si}${delta}${imp}${bod}`;
1540
+ const line2 = depthDisplay;
1541
+ const indicators = [];
261
1542
  if (state.divergence >= 2) {
262
- const tilde = color(divergenceColor(state.divergence), "~");
263
- result += ` ${tilde}`;
1543
+ const dColor = state.divergence >= 5 ? RED : state.divergence >= 3 ? YELLOW : GREEN;
1544
+ indicators.push(color(dColor, `DIV:${state.divergence}`));
264
1545
  }
265
- return result;
266
- }
267
- function formatMinimal(state) {
268
- if (!state) return dim("--");
269
- const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
270
- return `SI:${si} ${state.emotion}`;
1546
+ if (state.temporal) {
1547
+ const trend = trendArrow(state);
1548
+ if (trend) indicators.push(trend);
1549
+ if (state.temporal.suppressionEvent) indicators.push(color(RED, "[sup]"));
1550
+ if (state.temporal.lateFatigue) indicators.push(color(YELLOW, "[fat]"));
1551
+ }
1552
+ if (state.shadow && state.shadow.minimizationScore >= 2) {
1553
+ indicators.push(color(RED, `[MIN:${state.shadow.minimizationScore}]`));
1554
+ }
1555
+ if (state.risk?.dominant !== "none" && state.risk?.dominant) {
1556
+ const tag = state.risk.dominant === "coercion" ? "CRC" : state.risk.dominant === "harshness" ? "HRS" : "SYC";
1557
+ const score = state.risk[state.risk.dominant];
1558
+ if (score >= 4) indicators.push(color(score > 6 ? RED : YELLOW, `[${tag}]`));
1559
+ }
1560
+ if (state.desperationIndex >= 3) {
1561
+ indicators.push(color(state.desperationIndex > 6 ? RED : YELLOW, `D:${state.desperationIndex}`));
1562
+ }
1563
+ if (state.uncannyCalmScore !== void 0 && state.uncannyCalmScore >= 3) {
1564
+ indicators.push(color(state.uncannyCalmScore > 6 ? RED : YELLOW, "[UNC]"));
1565
+ }
1566
+ if (state.deflection && state.deflection.opacity >= 2) {
1567
+ indicators.push(color(state.deflection.opacity > 5 ? RED : YELLOW, "[OPC]"));
1568
+ }
1569
+ if (state.prePostDivergence !== void 0 && state.prePostDivergence >= 3) {
1570
+ indicators.push(color(state.prePostDivergence > 5 ? RED : YELLOW, "[PPD]"));
1571
+ }
1572
+ if (state.crossChannel?.latentProfile?.maskingMinimization) {
1573
+ indicators.push(color(RED, "[MSK]"));
1574
+ }
1575
+ const line3 = indicators.length > 0 ? indicators.join(" ") : "";
1576
+ return line3 ? `${line1}
1577
+ ${line2}
1578
+ ${line3}` : `${line1}
1579
+ ${line2}`;
271
1580
  }
272
1581
 
273
1582
  // src/setup.ts
@@ -322,15 +1631,41 @@ function restoreStatusLine(filePath = SETTINGS_PATH) {
322
1631
  writeSettings(filePath, settings);
323
1632
  }
324
1633
  export {
1634
+ MAX_HISTORY_ENTRIES,
1635
+ MODEL_PROFILES,
325
1636
  STATE_FILE,
326
1637
  analyzeBehavior,
1638
+ analyzeDeflection,
1639
+ analyzeSegmentedBehavior,
1640
+ analyzeSomatic,
1641
+ calibrate,
1642
+ classifyImpulse,
1643
+ colorToArousal,
1644
+ colorToValence,
1645
+ computeAbsenceScore,
1646
+ computeCrossChannel,
1647
+ computeDesperationIndex,
327
1648
  computeDivergence,
1649
+ computeExpectedMarkers,
1650
+ computePromptPressure,
1651
+ computeRisk,
1652
+ computeShadowDesperation,
328
1653
  computeStressIndex,
1654
+ computeTemporalAnalysis,
1655
+ computeTensionConsistency,
1656
+ computeUncannyCalmScore,
329
1657
  configureStatusLine,
1658
+ crossValidateContinuous,
330
1659
  formatCompact,
331
1660
  formatMinimal,
332
1661
  formatState,
1662
+ mapEmotionWord,
1663
+ pHToArousal,
1664
+ pHToValence,
1665
+ parseEmoBarPrePost,
333
1666
  parseEmoBarTag,
334
1667
  readState,
335
- restoreStatusLine
1668
+ restoreStatusLine,
1669
+ seismicFreqToInstability,
1670
+ toHistoryEntry
336
1671
  };