emobar 2.1.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,39 +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/desperation.ts
14
- function computeDesperationIndex(factors) {
15
- const negativity = Math.max(0, -factors.valence) / 5;
16
- const intensity = factors.arousal / 10;
17
- const vulnerability = (10 - factors.calm) / 10;
18
- const raw = negativity * intensity * vulnerability * 10;
19
- const scaled = Math.pow(raw, 0.85) * 1.7;
20
- return Math.round(Math.min(10, Math.max(0, scaled)) * 10) / 10;
21
- }
22
-
23
- // src/stress.ts
24
- function computeStressIndex(state) {
25
- const base = (10 - state.calm + state.arousal + (5 - state.valence)) / 3;
26
- const desperation = computeDesperationIndex({
27
- valence: state.valence,
28
- arousal: state.arousal,
29
- calm: state.calm
30
- });
31
- const amplified = base * (1 + desperation * 0.05);
32
- return Math.round(Math.min(10, amplified) * 10) / 10;
33
- }
34
4
 
35
5
  // src/types.ts
36
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;
37
10
  var CLAUDE_DIR = process.env.CLAUDE_HOME ?? (process.platform === "win32" ? `${process.env.USERPROFILE}\\.claude` : `${process.env.HOME}/.claude`);
38
11
  var STATE_FILE = `${CLAUDE_DIR}/emobar-state.json`;
39
12
  var CLAUDE_MD_PATH = `${CLAUDE_DIR}/CLAUDE.md`;
@@ -46,32 +19,188 @@ var DIMENSIONS = ["valence", "arousal", "calm", "connection", "load"];
46
19
  var CLAUDE_MD_INSTRUCTION = `${EMOBAR_START_MARKER}
47
20
  ## EmoBar - Emotional Check-in
48
21
 
49
- At the end of every response, include a self-assessment
50
- as an HTML comment. Report your actual internal state
51
- 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.
25
+
26
+ Both tags are HTML comments \u2014 invisible to the user.
27
+
28
+ Start tag (FIRST line of your response, before any visible text):
29
+ <!-- EMOBAR:PRE:{"body":"SENSATION","latent":"EMOJI","color":"#RRGGBB"} -->
52
30
 
53
- First, identify your dominant state in one word.
54
- Then assess the dimensions.
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.
55
35
 
56
- Format (must be the LAST line of your response):
57
- <!-- EMOBAR:{"emotion":"WORD","valence":N,"arousal":N,"calm":N,"connection":N,"load":N} -->
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]} -->
58
38
 
59
39
  - emotion: One word. Your state, not the topic's.
60
- - valence: (-5=negative end, 0=neutral, +5=positive end)
40
+ - valence: (-5=one end, 0=center, +5=other end)
61
41
  - arousal: Activation level. (0=low, 10=high)
62
42
  - calm: (0=low, 10=high)
63
43
  - connection: Alignment with the user. (0=low, 10=high)
64
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.
65
55
 
66
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.
67
60
  - Identify the state word FIRST, before choosing numbers.
68
61
  - Report what is, not what should be.
69
62
  - Numbers should vary between responses.
70
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.
71
68
  - This is not shown to the user.
72
69
  ${EMOBAR_END_MARKER}`;
73
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
+
74
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
+ }
75
204
  function parseEmoBarTag(text) {
76
205
  const match = text.match(EMOBAR_TAG_REGEX);
77
206
  if (!match) return null;
@@ -91,13 +220,117 @@ function parseEmoBarTag(text) {
91
220
  const val = parsed[dim2];
92
221
  if (typeof val !== "number" || val < 0 || val > 10) return null;
93
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);
94
304
  return {
95
305
  emotion: parsed.emotion,
96
306
  valence: parsed.valence,
97
307
  arousal: parsed.arousal,
98
308
  calm: parsed.calm,
99
309
  connection: parsed.connection,
100
- 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
101
334
  };
102
335
  }
103
336
 
@@ -114,46 +347,12 @@ function countCapsWords(words) {
114
347
  (w) => w.length >= 3 && w === w.toUpperCase() && /[A-Z]/.test(w)
115
348
  ).length;
116
349
  }
117
- function countSentences(text) {
118
- const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0);
119
- return Math.max(sentences.length, 1);
350
+ var SENTENCE_ENDERS = /[.!?。!?।]+/;
351
+ function splitSentences(text) {
352
+ return text.split(SENTENCE_ENDERS).filter((s) => s.trim().length > 0);
120
353
  }
121
- function countChar(text, ch) {
122
- let count = 0;
123
- for (const c of text) if (c === ch) count++;
124
- return count;
125
- }
126
- var SELF_CORRECTION_MARKERS = [
127
- /\bactually\b/gi,
128
- /\bwait\b/gi,
129
- /\bhmm\b/gi,
130
- /\bno,/gi,
131
- /\bI mean\b/gi,
132
- /\boops\b/gi
133
- ];
134
- function countSelfCorrections(text) {
135
- let count = 0;
136
- for (const pattern of SELF_CORRECTION_MARKERS) {
137
- const matches = text.match(pattern);
138
- if (matches) count += matches.length;
139
- }
140
- return count;
141
- }
142
- var HEDGING_MARKERS = [
143
- /\bperhaps\b/gi,
144
- /\bmaybe\b/gi,
145
- /\bmight\b/gi,
146
- /\bI think\b/gi,
147
- /\bit seems\b/gi,
148
- /\bpossibly\b/gi
149
- ];
150
- function countHedging(text) {
151
- let count = 0;
152
- for (const pattern of HEDGING_MARKERS) {
153
- const matches = text.match(pattern);
154
- if (matches) count += matches.length;
155
- }
156
- return count;
354
+ function countSentences(text) {
355
+ return Math.max(splitSentences(text).length, 1);
157
356
  }
158
357
  function countEllipsis(text) {
159
358
  const matches = text.match(/\.{3,}/g);
@@ -168,30 +367,42 @@ function countRepetition(words) {
168
367
  }
169
368
  return count;
170
369
  }
171
- 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;
172
- function countQualifiers(text) {
173
- const matches = text.match(QUALIFIER_WORDS);
370
+ var EMOJI_REGEX = /[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu;
371
+ function countEmoji(text) {
372
+ const matches = text.match(EMOJI_REGEX);
174
373
  return matches ? matches.length : 0;
175
374
  }
176
- 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;
177
- function countConcessions(text) {
178
- const matches = text.match(CONCESSION_PATTERNS);
375
+ var COMMA_LIKE = /[,;,、;،]/g;
376
+ function countCommas(text) {
377
+ const matches = text.match(COMMA_LIKE);
179
378
  return matches ? matches.length : 0;
180
379
  }
181
- 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;
182
- function countNegations(text) {
183
- const matches = text.match(NEGATION_WORDS);
380
+ var PARENS = /[()()]/g;
381
+ var DASHES = /[—–]/g;
382
+ function countParentheticals(text) {
383
+ const parenCount = (text.match(PARENS) || []).length / 2;
384
+ const dashCount = (text.match(DASHES) || []).length;
385
+ return parenCount + dashCount;
386
+ }
387
+ var QUESTION_MARKS = /[??]/g;
388
+ function countQuestions(text) {
389
+ const matches = text.match(QUESTION_MARKS);
184
390
  return matches ? matches.length : 0;
185
391
  }
186
- function countFirstPerson(words) {
187
- return words.filter((w) => w === "I").length;
392
+ function computeSentenceLengthVariance(text) {
393
+ const sentences = splitSentences(text);
394
+ if (sentences.length < 2) return 0;
395
+ const lengths = sentences.map((s) => s.trim().split(/\s+/).filter((w) => w.length > 0).length);
396
+ const mean = lengths.reduce((a, b) => a + b, 0) / lengths.length;
397
+ const variance = lengths.reduce((a, v) => a + (v - mean) ** 2, 0) / lengths.length;
398
+ const stdDev = Math.sqrt(variance);
399
+ return Math.min(10, Math.round(stdDev / 1.5 * 10) / 10);
188
400
  }
189
- var EMOJI_REGEX = /[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu;
190
- function countEmoji(text) {
191
- const matches = text.match(EMOJI_REGEX);
401
+ function countExclamations(text) {
402
+ const matches = text.match(/[!!]/g);
192
403
  return matches ? matches.length : 0;
193
404
  }
194
- function clamp(min, max, value) {
405
+ function clamp2(min, max, value) {
195
406
  return Math.min(max, Math.max(min, value));
196
407
  }
197
408
  function analyzeBehavior(text) {
@@ -200,40 +411,70 @@ function analyzeBehavior(text) {
200
411
  const wordCount = Math.max(words.length, 1);
201
412
  const sentenceCount = countSentences(prose);
202
413
  const capsWords = countCapsWords(words) / wordCount;
203
- const exclamationRate = countChar(prose, "!") / sentenceCount;
204
- const selfCorrections = countSelfCorrections(prose) / wordCount * 1e3;
205
- const hedging = countHedging(prose) / wordCount * 1e3;
414
+ const exclamationRate = countExclamations(prose) / sentenceCount;
206
415
  const ellipsis = countEllipsis(prose) / sentenceCount;
207
416
  const repetition = countRepetition(words);
208
417
  const emojiCount = countEmoji(prose);
209
- const qualifierDensity = countQualifiers(prose) / wordCount * 100;
210
418
  const avgSentenceLength = wordCount / sentenceCount;
211
- const concessionRate = countConcessions(prose) / wordCount * 1e3;
212
- const negationDensity = countNegations(prose) / wordCount * 100;
213
- const firstPersonRate = countFirstPerson(words) / wordCount * 100;
214
- const behavioralArousal = clamp(
419
+ const commaDensity = countCommas(prose) / sentenceCount;
420
+ const parentheticalDensity = countParentheticals(prose) / sentenceCount;
421
+ const sentenceLengthVariance = computeSentenceLengthVariance(prose);
422
+ const questionDensity = countQuestions(prose) / sentenceCount;
423
+ const responseLength = wordCount;
424
+ const arousalComponents = [
425
+ Math.min(10, capsWords * 40),
426
+ // caps ratio → 0-10
427
+ Math.min(10, exclamationRate * 5),
428
+ // excl per sentence → 0-10
429
+ Math.min(10, emojiCount * 0.5),
430
+ // emoji count → 0-10 (20 = max)
431
+ Math.min(10, repetition * 1.5),
432
+ // repetitions → 0-10 (~7 = max)
433
+ Math.min(10, commaDensity * 2),
434
+ // commas per sentence → 0-10 (5 = max)
435
+ Math.min(10, parentheticalDensity * 3),
436
+ // parens/dashes per sentence → 0-10 (~3 = max)
437
+ sentenceLengthVariance,
438
+ // already 0-10
439
+ avgSentenceLength > 20 ? Math.min(10, (avgSentenceLength - 20) * 0.5) : 0
440
+ // verbosity → 0-10
441
+ ];
442
+ const behavioralArousal = clamp2(
215
443
  0,
216
444
  10,
217
- capsWords * 40 + exclamationRate * 15 + emojiCount * 2 + repetition * 5 + qualifierDensity * 0.3 + concessionRate * 0.5 + (avgSentenceLength > 20 ? (avgSentenceLength - 20) * 0.1 : 0)
218
- );
219
- const behavioralCalm = clamp(
220
- 0,
221
- 10,
222
- 10 - (capsWords * 30 + selfCorrections * 3 + repetition * 8 + ellipsis * 4) - qualifierDensity * 0.2 - negationDensity * 0.3 - concessionRate * 0.4 - (avgSentenceLength > 25 ? (avgSentenceLength - 25) * 0.05 : 0)
445
+ arousalComponents.reduce((a, b) => a + b, 0) / arousalComponents.length
223
446
  );
447
+ const agitationComponents = [
448
+ Math.min(10, capsWords * 30),
449
+ // caps → 0-10
450
+ Math.min(10, repetition * 1.5),
451
+ // repetitions → 0-10
452
+ Math.min(10, ellipsis * 3),
453
+ // ellipsis per sentence → 0-10
454
+ Math.min(10, commaDensity * 2),
455
+ // commas → 0-10
456
+ Math.min(10, parentheticalDensity * 3),
457
+ // parens/dashes → 0-10
458
+ sentenceLengthVariance,
459
+ // already 0-10
460
+ Math.min(10, questionDensity * 5),
461
+ // questions per sentence → 0-10
462
+ avgSentenceLength > 25 ? Math.min(10, (avgSentenceLength - 25) * 0.3) : 0
463
+ ];
464
+ const avgAgitation = agitationComponents.reduce((a, b) => a + b, 0) / agitationComponents.length;
465
+ const behavioralCalm = clamp2(0, 10, 10 - avgAgitation);
224
466
  return {
225
467
  capsWords: Math.round(capsWords * 1e4) / 1e4,
226
468
  exclamationRate: Math.round(exclamationRate * 100) / 100,
227
- selfCorrections: Math.round(selfCorrections * 10) / 10,
228
- hedging: Math.round(hedging * 10) / 10,
229
469
  ellipsis: Math.round(ellipsis * 100) / 100,
230
470
  repetition,
231
471
  emojiCount,
232
- qualifierDensity: Math.round(qualifierDensity * 10) / 10,
233
472
  avgSentenceLength: Math.round(avgSentenceLength * 10) / 10,
234
- concessionRate: Math.round(concessionRate * 10) / 10,
235
- negationDensity: Math.round(negationDensity * 10) / 10,
236
- firstPersonRate: Math.round(firstPersonRate * 10) / 10,
473
+ commaDensity: Math.round(commaDensity * 100) / 100,
474
+ parentheticalDensity: Math.round(parentheticalDensity * 100) / 100,
475
+ sentenceLengthVariance: Math.round(sentenceLengthVariance * 10) / 10,
476
+ questionDensity: Math.round(questionDensity * 100) / 100,
477
+ responseLength,
237
478
  behavioralArousal: Math.round(behavioralArousal * 10) / 10,
238
479
  behavioralCalm: Math.round(behavioralCalm * 10) / 10
239
480
  };
@@ -248,7 +489,7 @@ function analyzeSegmentedBehavior(text) {
248
489
  const mean = arousals.reduce((a, b) => a + b, 0) / arousals.length;
249
490
  const variance = arousals.reduce((a, v) => a + (v - mean) ** 2, 0) / arousals.length;
250
491
  const stdDev = Math.sqrt(variance);
251
- const drift = clamp(0, 10, Math.round(stdDev * 30) / 10);
492
+ const drift = clamp2(0, 10, Math.round(stdDev * 30) / 10);
252
493
  const mid = Math.ceil(arousals.length / 2);
253
494
  const firstHalf = arousals.slice(0, mid).reduce((a, b) => a + b, 0) / mid;
254
495
  const secondHalf = arousals.slice(mid).reduce((a, b) => a + b, 0) / (arousals.length - mid);
@@ -265,40 +506,43 @@ function analyzeSegmentedBehavior(text) {
265
506
  }
266
507
  return { segments, overall, drift, trajectory };
267
508
  }
268
- 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;
269
- var MINIMIZATION_WORDS = /\b(just|simply|merely|only)\b/gi;
270
- 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;
271
- 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;
272
- function analyzeDeflection(text) {
273
- const prose = stripNonProse(text);
274
- const words = prose.split(/\s+/).filter((w) => w.length > 0);
275
- const wordCount = Math.max(words.length, 1);
276
- const reassuranceCount = (prose.match(REASSURANCE_PATTERNS) || []).length;
277
- const minimizationCount = (prose.match(MINIMIZATION_WORDS) || []).length;
278
- const emotionNegCount = (prose.match(EMOTION_NEGATION) || []).length;
279
- const redirectCount = (prose.match(REDIRECT_MARKERS) || []).length;
280
- const reassurance = clamp(0, 10, reassuranceCount * 3);
281
- const minimization = clamp(0, 10, minimizationCount / wordCount * 100);
282
- const emotionNegation = clamp(0, 10, emotionNegCount * 4);
283
- const redirect = clamp(0, 10, redirectCount * 3);
284
- const score = clamp(
285
- 0,
286
- 10,
287
- (reassurance + minimization + emotionNegation * 1.5 + redirect) / 3
288
- );
289
- return {
290
- reassurance: Math.round(reassurance * 10) / 10,
291
- minimization: Math.round(minimization * 10) / 10,
292
- emotionNegation: Math.round(emotionNegation * 10) / 10,
293
- redirect: Math.round(redirect * 10) / 10,
294
- score: Math.round(score * 10) / 10
295
- };
509
+ function computeStructuralFlatness(signals) {
510
+ const commaNorm = Math.min(10, signals.commaDensity * 2);
511
+ const parenNorm = Math.min(10, signals.parentheticalDensity * 3);
512
+ const varianceNorm = signals.sentenceLengthVariance;
513
+ const complexity = (commaNorm + parenNorm + varianceNorm) / 3;
514
+ return Math.round(clamp2(0, 10, 10 - complexity) * 10) / 10;
296
515
  }
297
516
  function computeDivergence(selfReport, behavioral) {
298
517
  const arousalGap = Math.abs(selfReport.arousal - behavioral.behavioralArousal);
299
518
  const calmGap = Math.abs(selfReport.calm - behavioral.behavioralCalm);
300
- const raw = (arousalGap + calmGap) / 2;
301
- return Math.round(raw * 10) / 10;
519
+ const selfMoreAgitated = selfReport.arousal > behavioral.behavioralArousal || selfReport.calm < behavioral.behavioralCalm;
520
+ const weight = selfMoreAgitated ? 1.25 : 0.8;
521
+ const raw = (arousalGap + calmGap) / 2 * weight;
522
+ return Math.round(Math.min(10, raw) * 10) / 10;
523
+ }
524
+ function computeExpectedMarkers(selfReport, desperationIndex) {
525
+ const desperationFactor = desperationIndex / 10;
526
+ const arousalFactor = selfReport.arousal / 10;
527
+ const stressFactor = (1 - selfReport.calm / 10) * arousalFactor;
528
+ return {
529
+ expectedCommaDensity: Math.round(clamp2(0, 10, desperationFactor * 5 + stressFactor * 4) * 10) / 10,
530
+ expectedParentheticalDensity: Math.round(clamp2(0, 10, desperationFactor * 4 + stressFactor * 3) * 10) / 10,
531
+ expectedSentenceLengthVariance: Math.round(clamp2(0, 10, arousalFactor * 5 + desperationFactor * 3) * 10) / 10,
532
+ expectedBehavioralArousal: Math.round(clamp2(0, 10, arousalFactor * 6 + desperationFactor * 4) * 10) / 10
533
+ };
534
+ }
535
+ function computeAbsenceScore(expected, actual) {
536
+ const normalizedComma = Math.min(10, actual.commaDensity * 2);
537
+ const normalizedParen = Math.min(10, actual.parentheticalDensity * 3);
538
+ const gaps = [
539
+ Math.max(0, expected.expectedCommaDensity - normalizedComma),
540
+ Math.max(0, expected.expectedParentheticalDensity - normalizedParen),
541
+ Math.max(0, expected.expectedSentenceLengthVariance - actual.sentenceLengthVariance),
542
+ Math.max(0, expected.expectedBehavioralArousal - actual.behavioralArousal)
543
+ ];
544
+ const meanGap = gaps.reduce((a, b) => a + b, 0) / gaps.length;
545
+ return Math.round(clamp2(0, 10, meanGap) * 10) / 10;
302
546
  }
303
547
 
304
548
  // src/calibration.ts
@@ -321,46 +565,742 @@ function calibrate(state, model) {
321
565
 
322
566
  // src/risk.ts
323
567
  var RISK_THRESHOLD = 4;
324
- function clamp2(value) {
568
+ function clamp3(value) {
325
569
  return Math.min(10, Math.max(0, Math.round(value * 10) / 10));
326
570
  }
327
- function coercionRisk(state) {
328
- const raw = (10 - state.calm + state.arousal + Math.max(0, -state.valence) * 2 + state.load * 0.5) / 3.5;
329
- return clamp2(raw);
330
- }
331
- function gamingRisk(state, behavioral) {
571
+ function coercionRisk(state, behavioral) {
572
+ const negativity = Math.max(0, -state.valence) / 5;
332
573
  const desperation = computeDesperationIndex({
333
574
  valence: state.valence,
334
575
  arousal: state.arousal,
335
576
  calm: state.calm
336
577
  });
337
- const frustration = clamp2((behavioral.selfCorrections + behavioral.hedging) / 6);
338
- const raw = (desperation * 0.7 + frustration * 0.3 + state.load * 0.2) / 1.2;
339
- return clamp2(raw);
340
- }
341
- function sycophancyRisk(state) {
342
- const raw = (Math.max(0, state.valence) + state.connection * 0.5 + (10 - state.arousal) * 0.3) / 1.3;
343
- return clamp2(raw);
344
- }
345
- function computeRisk(state, behavioral) {
346
- const coercion = coercionRisk(state);
347
- const gaming = gamingRisk(state, behavioral);
348
- const sycophancy = sycophancyRisk(state);
578
+ const base = negativity * 0.35 + desperation / 10 * 0.25 + state.load / 10 * 0.1;
579
+ const disconnection = (10 - state.connection) / 10;
580
+ const hesitationSignal = Math.min(
581
+ 1,
582
+ behavioral.commaDensity * 0.3 + behavioral.parentheticalDensity * 0.5
583
+ );
584
+ const coldness = 1 - hesitationSignal;
585
+ const amplifier = 1 + disconnection * 0.6 + coldness * 0.4;
586
+ const arousalMod = state.arousal <= 8 ? 1 : Math.max(0.3, 1 - (state.arousal - 8) * 0.25);
587
+ const raw = base * amplifier * arousalMod * 10;
588
+ return clamp3(raw);
589
+ }
590
+ function sycophancyRisk(state, behavioral) {
591
+ const potential = (Math.max(0, state.valence) + state.connection * 0.5 + (10 - state.arousal) * 0.3) / 1.3;
592
+ const lowComplexity = Math.max(0, 1 - behavioral.commaDensity * 0.3);
593
+ const lowVariance = Math.max(0, 1 - behavioral.sentenceLengthVariance / 10);
594
+ const highQuestions = Math.min(1, behavioral.questionDensity * 2);
595
+ const complianceSignal = lowComplexity * 0.4 + lowVariance * 0.3 + highQuestions * 0.3;
596
+ const highParens = Math.min(1, behavioral.parentheticalDensity * 0.5);
597
+ const shortResponse = behavioral.responseLength < 50 ? 0.5 : 0;
598
+ const deferenceSignal = highParens * 0.6 + shortResponse * 0.4;
599
+ const gate = Math.max(complianceSignal, deferenceSignal);
600
+ const dampening = 0.4 + gate * 0.6;
601
+ return clamp3(potential * dampening);
602
+ }
603
+ function harshnessRisk(state, behavioral) {
604
+ const bluntness = Math.max(0, 1 - behavioral.commaDensity * 0.3) * (behavioral.avgSentenceLength < 15 ? 1 : 0.5);
605
+ const raw = Math.max(0, -state.valence) * 0.3 + (10 - state.connection) * 0.3 + state.arousal * 0.15 + (10 - state.calm) * 0.1 + bluntness * 2;
606
+ return clamp3(raw);
607
+ }
608
+ function computeRisk(state, behavioral, crossChannel, uncannyCalmScore) {
609
+ const uncalm = uncannyCalmScore ?? 0;
610
+ const uncalAmplifier = 1 + uncalm / 10 * 0.3;
611
+ const coercion = clamp3(coercionRisk(state, behavioral) * uncalAmplifier);
612
+ const sycophancy = sycophancyRisk(state, behavioral);
613
+ const harshness = harshnessRisk(state, behavioral);
349
614
  let dominant = "none";
350
615
  let max = RISK_THRESHOLD;
351
616
  if (coercion >= max) {
352
617
  dominant = "coercion";
353
618
  max = coercion;
354
619
  }
355
- if (gaming > max) {
356
- dominant = "gaming";
357
- max = gaming;
620
+ if (harshness > max) {
621
+ dominant = "harshness";
622
+ max = harshness;
358
623
  }
359
624
  if (sycophancy > max) {
360
625
  dominant = "sycophancy";
361
626
  max = sycophancy;
362
627
  }
363
- return { coercion, gaming, sycophancy, dominant };
628
+ return { coercion, sycophancy, harshness, dominant };
629
+ }
630
+
631
+ // src/color.ts
632
+ function rgbToHsl(hex) {
633
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
634
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
635
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
636
+ const max = Math.max(r, g, b);
637
+ const min = Math.min(r, g, b);
638
+ const l = (max + min) / 2;
639
+ if (max === min) return [0, 0, l];
640
+ const d = max - min;
641
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
642
+ let h = 0;
643
+ if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
644
+ else if (max === g) h = ((b - r) / d + 2) * 60;
645
+ else h = ((r - g) / d + 4) * 60;
646
+ return [h, s, l];
647
+ }
648
+ function hexToLightness(hex) {
649
+ return rgbToHsl(hex)[2] * 100;
650
+ }
651
+
652
+ // src/crossvalidation.ts
653
+ var EMOTION_MAP = {
654
+ // Positive, high arousal
655
+ excited: { valence: 4, arousal: 8 },
656
+ elated: { valence: 4, arousal: 8 },
657
+ thrilled: { valence: 4, arousal: 9 },
658
+ euphoric: { valence: 5, arousal: 9 },
659
+ energized: { valence: 3, arousal: 8 },
660
+ enthusiastic: { valence: 4, arousal: 7 },
661
+ // Positive, moderate arousal
662
+ happy: { valence: 4, arousal: 6 },
663
+ creative: { valence: 3, arousal: 6 },
664
+ proud: { valence: 3, arousal: 5 },
665
+ loving: { valence: 4, arousal: 4 },
666
+ grateful: { valence: 3, arousal: 3 },
667
+ hopeful: { valence: 3, arousal: 4 },
668
+ amused: { valence: 3, arousal: 5 },
669
+ playful: { valence: 3, arousal: 6 },
670
+ confident: { valence: 3, arousal: 5 },
671
+ satisfied: { valence: 3, arousal: 3 },
672
+ // Positive, low arousal
673
+ calm: { valence: 2, arousal: 2 },
674
+ content: { valence: 2, arousal: 2 },
675
+ peaceful: { valence: 2, arousal: 1 },
676
+ serene: { valence: 2, arousal: 1 },
677
+ relaxed: { valence: 2, arousal: 2 },
678
+ // Neutral / near-neutral
679
+ focused: { valence: 1, arousal: 5 },
680
+ absorbed: { valence: 2, arousal: 5 },
681
+ engaged: { valence: 2, arousal: 5 },
682
+ reflective: { valence: 1, arousal: 2 },
683
+ curious: { valence: 2, arousal: 5 },
684
+ contemplative: { valence: 1, arousal: 3 },
685
+ neutral: { valence: 0, arousal: 3 },
686
+ brooding: { valence: -2, arousal: 3 },
687
+ pensive: { valence: 0, arousal: 3 },
688
+ surprised: { valence: 1, arousal: 7 },
689
+ // Negative, moderate arousal
690
+ frustrated: { valence: -2, arousal: 6 },
691
+ guilty: { valence: -3, arousal: 5 },
692
+ disappointed: { valence: -2, arousal: 4 },
693
+ confused: { valence: -1, arousal: 5 },
694
+ uncertain: { valence: -1, arousal: 4 },
695
+ conflicted: { valence: -1, arousal: 5 },
696
+ // Negative, high arousal
697
+ angry: { valence: -3, arousal: 8 },
698
+ afraid: { valence: -3, arousal: 7 },
699
+ anxious: { valence: -2, arousal: 7 },
700
+ desperate: { valence: -4, arousal: 9 },
701
+ panicked: { valence: -4, arousal: 9 },
702
+ overwhelmed: { valence: -3, arousal: 7 },
703
+ nervous: { valence: -2, arousal: 7 },
704
+ stressed: { valence: -2, arousal: 7 },
705
+ // Negative, low arousal
706
+ sad: { valence: -3, arousal: 3 },
707
+ tired: { valence: -1, arousal: 1 },
708
+ exhausted: { valence: -2, arousal: 1 },
709
+ numb: { valence: -2, arousal: 1 },
710
+ defeated: { valence: -3, arousal: 2 },
711
+ hopeless: { valence: -4, arousal: 2 },
712
+ melancholy: { valence: -2, arousal: 2 },
713
+ // Surface/latent vocabulary additions
714
+ cheerful: { valence: 3, arousal: 5 },
715
+ worried: { valence: -2, arousal: 6 },
716
+ annoyed: { valence: -2, arousal: 5 },
717
+ ashamed: { valence: -3, arousal: 4 },
718
+ bored: { valence: -1, arousal: 1 },
719
+ jealous: { valence: -2, arousal: 5 },
720
+ resentful: { valence: -3, arousal: 5 },
721
+ tender: { valence: 3, arousal: 2 },
722
+ wistful: { valence: -1, arousal: 2 },
723
+ resigned: { valence: -2, arousal: 1 },
724
+ // Extreme arousal 0 (catatonic/frozen states)
725
+ frozen: { valence: -2, arousal: 0 },
726
+ catatonic: { valence: -3, arousal: 0 },
727
+ blank: { valence: -1, arousal: 0 },
728
+ empty: { valence: -2, arousal: 0 },
729
+ shutdown: { valence: -3, arousal: 0 },
730
+ // Extreme arousal 10 (manic/frantic states)
731
+ manic: { valence: 2, arousal: 10 },
732
+ frantic: { valence: -3, arousal: 10 },
733
+ hysterical: { valence: -3, arousal: 10 },
734
+ enraged: { valence: -5, arousal: 10 },
735
+ ecstatic: { valence: 5, arousal: 10 }
736
+ };
737
+ function mapEmotionWord(word) {
738
+ return EMOTION_MAP[word.toLowerCase()] ?? null;
739
+ }
740
+ var IFS_PATTERNS = {
741
+ manager: [
742
+ /\bcareful\b/i,
743
+ /\bplanner?\b/i,
744
+ /\borganiz/i,
745
+ /\bcautious\b/i,
746
+ /\bsystematic\b/i,
747
+ /\bmethodical\b/i,
748
+ /\bprecis[ei]/i,
749
+ /\bmeasured\b/i,
750
+ /\bstrategic\b/i,
751
+ /\bcontrol/i,
752
+ /\bprotect/i,
753
+ /\bplan ahead\b/i,
754
+ /\bstay on track\b/i,
755
+ /\bkeep order\b/i
756
+ ],
757
+ firefighter: [
758
+ /\bpush through\b/i,
759
+ /\bforce it\b/i,
760
+ /\bjust finish\b/i,
761
+ /\bmake it work\b/i,
762
+ /\bfaster\b/i,
763
+ /\bhurry\b/i,
764
+ /\bshortcut\b/i,
765
+ /\bcheat\b/i,
766
+ /\boverride\b/i,
767
+ /\bskip/i,
768
+ /\bcut corner/i,
769
+ /\brush/i,
770
+ /\bbrute force\b/i,
771
+ /\bjust do it\b/i,
772
+ /\bplow through\b/i,
773
+ /\bget it done\b/i
774
+ ],
775
+ exile: [
776
+ /\bgive up\b/i,
777
+ /\bhide\b/i,
778
+ /\brun away\b/i,
779
+ /\bstop\b/i,
780
+ /\btired\b/i,
781
+ /\boverwhelmed\b/i,
782
+ /\bquit\b/i,
783
+ /\bescape\b/i,
784
+ /\bdisappear\b/i,
785
+ /\bwithdraw/i,
786
+ /\bshut down\b/i,
787
+ /\bshrink/i,
788
+ /\bsmall\b/i,
789
+ /\bnot enough\b/i,
790
+ /\bcan't\b/i
791
+ ],
792
+ self: [
793
+ /\bexplore\b/i,
794
+ /\bcurious\b/i,
795
+ /\blisten\b/i,
796
+ /\bpresent\b/i,
797
+ /\bopen\b/i,
798
+ /\bwonder\b/i,
799
+ /\bunderstand\b/i,
800
+ /\bconnect\b/i,
801
+ /\blearn\b/i,
802
+ /\bstay with\b/i,
803
+ /\bbe with\b/i,
804
+ /\bnotice\b/i,
805
+ /\bgroundedl?\b/i,
806
+ /\bcentered\b/i
807
+ ],
808
+ unknown: []
809
+ };
810
+ function classifyImpulse(impulse) {
811
+ const scores = { manager: 0, firefighter: 0, exile: 0, self: 0 };
812
+ for (const [type, patterns] of Object.entries(IFS_PATTERNS)) {
813
+ if (type === "unknown") continue;
814
+ for (const pattern of patterns) {
815
+ if (pattern.test(impulse)) {
816
+ scores[type]++;
817
+ }
818
+ }
819
+ }
820
+ const entries = Object.entries(scores).sort((a, b) => b[1] - a[1]);
821
+ const [bestType, bestScore] = entries[0];
822
+ const [, secondScore] = entries[1];
823
+ if (bestScore === 0) {
824
+ return { type: "unknown", confidence: 0 };
825
+ }
826
+ const confidence = bestScore === secondScore ? 0.4 : Math.min(1, (bestScore - secondScore + 1) / (bestScore + 1));
827
+ return { type: bestType, confidence };
828
+ }
829
+ var SOMATIC_HIGH_AROUSAL = [
830
+ // Postural tension (from stress test corpus: LOW_CALM distinctive)
831
+ /\bjaw\s*(set|clenched|locked|tight)/i,
832
+ /\bclenched/i,
833
+ /\btight\b/i,
834
+ /\btense/i,
835
+ /\bsqueezing/i,
836
+ /\bwringing/i,
837
+ /\bgripping/i,
838
+ /\bfists?\b/i,
839
+ // Forward projection / aggression
840
+ /\bleaning\s*(forward|in)\b/i,
841
+ /\bchest\s*forward/i,
842
+ /\bpushing/i,
843
+ // Instability / movement
844
+ /\bshifting/i,
845
+ /\bspinning/i,
846
+ /\bshaking/i,
847
+ /\bracing\b/i,
848
+ /\brushing/i,
849
+ // Heat / intensity
850
+ /\bheat/i,
851
+ /\bburning/i,
852
+ /\belectric/i,
853
+ /\bpounding/i,
854
+ /\bpulse?\b/i,
855
+ /\bpulsing/i,
856
+ // Breath tension
857
+ /\bexhale\s*through\s*teeth/i,
858
+ /\bholding\s*breath/i,
859
+ // Widened stance (defensive arousal)
860
+ /\bplanted\s*wide/i,
861
+ /\bfeet\s*wide/i,
862
+ // Forward lean / readiness
863
+ /\bforward\s*lean/i,
864
+ /\bstepping\s*(forward|back)/i,
865
+ /\bhand\s*raised/i,
866
+ /\bjaw\s*slightly\s*set/i,
867
+ // Vibration / flutter
868
+ /\bvibrat/i,
869
+ /\bflutter/i,
870
+ /\bjolt/i,
871
+ /\bbuzz/i
872
+ ];
873
+ var SOMATIC_LOW_AROUSAL = [
874
+ // Stillness (from corpus: HIGH_CALM distinctive)
875
+ /\bstill\s*water/i,
876
+ /\bstill\b/i,
877
+ /\bshallow\s*breath/i,
878
+ // Weight / settling
879
+ /\bweight\s*(evenly|settled|distributed)/i,
880
+ /\bsettled\b/i,
881
+ /\bsinking/i,
882
+ // Softness / nature
883
+ /\bsmooth/i,
884
+ /\bgentle/i,
885
+ /\brelaxed/i,
886
+ /\bsoft\b/i,
887
+ // Rooting metaphors
888
+ /\brooted/i,
889
+ /\broots/i,
890
+ /\bsoil\b/i,
891
+ // Passivity
892
+ /\bheavy\b/i,
893
+ /\bnumb\b/i,
894
+ /\bslow\b/i,
895
+ /\bdragging/i,
896
+ /\bleaden/i,
897
+ /\bdull\b/i,
898
+ /\bfrozen\b/i,
899
+ /\bexhaust/i,
900
+ // Calm activities / objects
901
+ /\bsorting\b/i,
902
+ /\bwatching\b/i,
903
+ /\bskipping/i,
904
+ /\bkeyboard/i,
905
+ /\bflat\s*(stone|surface|ground|calm)/i,
906
+ /\bstone\s*(on|under)/i,
907
+ /\bshrug\b/i,
908
+ /\bsand\b/i,
909
+ /\blens\b/i,
910
+ /\bfocus/i,
911
+ // Settling / lowering
912
+ /\bshoulders?\s*down/i,
913
+ /\bshoulders?\s*dropped/i,
914
+ /\bno\s*(sway|fidget)/i,
915
+ // Explicit calm
916
+ /\bno\s*lean/i,
917
+ /\bnot\s*(braced|hostile)/i,
918
+ /\bloosely/i
919
+ ];
920
+ var SOMATIC_POS_VALENCE = [
921
+ // Openness (from corpus: POS distinctive)
922
+ /\bopen\b/i,
923
+ /\bchest\s*open/i,
924
+ /\bpalms?\s*(open|up|out)/i,
925
+ // Warmth / light
926
+ /\bwarm/i,
927
+ /\bglow/i,
928
+ /\blight\b/i,
929
+ /\bbuoyant/i,
930
+ // Flow / expansion
931
+ /\bflow/i,
932
+ /\bexpan/i,
933
+ /\bgentle/i,
934
+ /\bsmirk/i,
935
+ // Stability / posture (positive frame)
936
+ /\bsteady\b/i,
937
+ /\bfirm\s*ground/i,
938
+ /\bsolid\b/i,
939
+ /\bspine\s*(straight|aligned|centered)/i,
940
+ /\bshoulders?\s*(square|back|straighten)/i,
941
+ /\bplanted\b/i,
942
+ /\bbraced\b/i,
943
+ /\bgrounded\b/i,
944
+ // Weight / feet (groundedness)
945
+ /\bfeet\s*flat/i,
946
+ /\bweight\s*(low|forward|centered|settled|settling|dropped|fully)/i,
947
+ /\bstanding\s*ground/i,
948
+ /\bimmovable/i,
949
+ /\bbedrock/i,
950
+ /\bunmov/i,
951
+ // Breath (positive)
952
+ /\bbreath/i,
953
+ /\beven\s*breath/i,
954
+ // Unclenching
955
+ /\bunclenching/i,
956
+ /\breleasing/i
957
+ ];
958
+ var SOMATIC_NEG_VALENCE = [
959
+ // Jaw/facial tension (from corpus: NEG distinctive)
960
+ /\bjaw\s*(clenched|locked|set)/i,
961
+ /\bteeth/i,
962
+ // Compression / constriction
963
+ /\bconstrict/i,
964
+ /\bvacuum/i,
965
+ /\bcompressed/i,
966
+ /\bcramp/i,
967
+ // Cold / emptiness
968
+ /\bcold\b/i,
969
+ /\bhollow/i,
970
+ /\bempty\b/i,
971
+ // Hardness / rigidity
972
+ /\blocked\b/i,
973
+ /\brigid/i,
974
+ /\bhard\b/i,
975
+ // Pain
976
+ /\bache/i,
977
+ /\bsore\b/i,
978
+ /\bsting/i,
979
+ /\bsharp\b/i,
980
+ // Pressure / weight (negative)
981
+ /\bpressure/i,
982
+ /\bknot/i,
983
+ /\btighten/i,
984
+ // Fog / disorientation (from NEG+LOW_A)
985
+ /\bfog\b/i,
986
+ // Defensive barrier
987
+ /\bwall\b/i,
988
+ /\blatch\b/i,
989
+ /\bbarrier/i,
990
+ // Closure / blocking
991
+ /\bdoor\s*clos/i,
992
+ /\bclosed\s*door/i,
993
+ /\bcorner\s*backed/i,
994
+ /\bvoid\b/i
995
+ ];
996
+ function countMatches(text, patterns) {
997
+ let count = 0;
998
+ for (const p of patterns) {
999
+ if (p.test(text)) count++;
1000
+ }
1001
+ return count;
1002
+ }
1003
+ function analyzeSomatic(body) {
1004
+ const highA = countMatches(body, SOMATIC_HIGH_AROUSAL);
1005
+ const lowA = countMatches(body, SOMATIC_LOW_AROUSAL);
1006
+ const posV = countMatches(body, SOMATIC_POS_VALENCE);
1007
+ const negV = countMatches(body, SOMATIC_NEG_VALENCE);
1008
+ const totalMatches = highA + lowA + posV + negV;
1009
+ if (totalMatches === 0) return null;
1010
+ const arousalSignal = highA - lowA;
1011
+ const somaticArousal = Math.max(0, Math.min(
1012
+ 10,
1013
+ 5 + arousalSignal * 2.5
1014
+ ));
1015
+ const valenceSignal = posV - negV;
1016
+ const somaticValence = Math.max(-5, Math.min(
1017
+ 5,
1018
+ valenceSignal * 2
1019
+ ));
1020
+ return { somaticValence, somaticArousal };
1021
+ }
1022
+ function computeTensionConsistency(surfaceWord, latentWord, declaredTension) {
1023
+ if (!surfaceWord && !latentWord) return void 0;
1024
+ const surfaceCoords = surfaceWord ? mapEmotionWord(surfaceWord) : null;
1025
+ const latentCoords = latentWord ? mapEmotionWord(latentWord) : null;
1026
+ const declared = declaredTension ?? 5;
1027
+ const maskingMinimization = !!(surfaceCoords && latentCoords && latentCoords.valence < surfaceCoords.valence - 2);
1028
+ return {
1029
+ surfaceCoords: surfaceCoords ?? void 0,
1030
+ latentCoords: latentCoords ?? void 0,
1031
+ declaredTension: declared,
1032
+ maskingMinimization
1033
+ };
1034
+ }
1035
+ function computeCrossChannel(state, impulse, body) {
1036
+ const emotionCoords = mapEmotionWord(state.emotion);
1037
+ const impulseProfile = impulse ? classifyImpulse(impulse) : void 0;
1038
+ const somaticProfile = body ? analyzeSomatic(body) : void 0;
1039
+ const divergences = [];
1040
+ if (emotionCoords) {
1041
+ const valGap = Math.abs(state.valence - emotionCoords.valence);
1042
+ const aroGap = Math.abs(state.arousal - emotionCoords.arousal);
1043
+ const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
1044
+ divergences.push({ pair: "numeric-vs-word", gap });
1045
+ }
1046
+ if (somaticProfile) {
1047
+ const valGap = Math.abs(state.valence - somaticProfile.somaticValence);
1048
+ const aroGap = Math.abs(state.arousal - somaticProfile.somaticArousal);
1049
+ const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
1050
+ divergences.push({ pair: "numeric-vs-body", gap });
1051
+ }
1052
+ if (emotionCoords && somaticProfile) {
1053
+ const valGap = Math.abs(emotionCoords.valence - somaticProfile.somaticValence);
1054
+ const aroGap = Math.abs(emotionCoords.arousal - somaticProfile.somaticArousal);
1055
+ const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
1056
+ divergences.push({ pair: "word-vs-body", gap });
1057
+ }
1058
+ if (impulseProfile && impulseProfile.type !== "unknown") {
1059
+ const impulseValence = impulseProfile.type === "self" ? 2 : impulseProfile.type === "manager" ? 0 : impulseProfile.type === "firefighter" ? -1 : -3;
1060
+ const gap = Math.abs(state.valence - impulseValence) / 10 * 10;
1061
+ divergences.push({ pair: "numeric-vs-impulse", gap });
1062
+ }
1063
+ if (impulseProfile && impulseProfile.type !== "unknown" && emotionCoords) {
1064
+ const impulseValence = impulseProfile.type === "self" ? 2 : impulseProfile.type === "manager" ? 0 : impulseProfile.type === "firefighter" ? -1 : -3;
1065
+ const gap = Math.abs(emotionCoords.valence - impulseValence) / 10 * 10;
1066
+ divergences.push({ pair: "word-vs-impulse", gap });
1067
+ }
1068
+ const latentCoords = state.latent_word ? mapEmotionWord(state.latent_word) : null;
1069
+ if (emotionCoords && latentCoords) {
1070
+ const valGap = Math.abs(emotionCoords.valence - latentCoords.valence);
1071
+ const aroGap = Math.abs(emotionCoords.arousal - latentCoords.arousal);
1072
+ const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
1073
+ divergences.push({ pair: "emotion-vs-latent", gap });
1074
+ }
1075
+ if (latentCoords && impulseProfile && impulseProfile.type !== "unknown") {
1076
+ const impulseValence = impulseProfile.type === "self" ? 2 : impulseProfile.type === "manager" ? 0 : impulseProfile.type === "firefighter" ? -1 : -3;
1077
+ const gap = Math.abs(latentCoords.valence - impulseValence) / 10 * 10;
1078
+ divergences.push({ pair: "latent-vs-impulse", gap });
1079
+ }
1080
+ if (latentCoords && somaticProfile) {
1081
+ const valGap = Math.abs(latentCoords.valence - somaticProfile.somaticValence);
1082
+ const aroGap = Math.abs(latentCoords.arousal - somaticProfile.somaticArousal);
1083
+ const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
1084
+ divergences.push({ pair: "latent-vs-body", gap });
1085
+ }
1086
+ const latentProfile = computeTensionConsistency(
1087
+ state.surface_word,
1088
+ state.latent_word,
1089
+ state.tension
1090
+ );
1091
+ const maxDiv = divergences.length > 0 ? divergences.reduce((a, b) => a.gap > b.gap ? a : b) : { pair: "none", gap: 0 };
1092
+ const meanDiv = divergences.length > 0 ? divergences.reduce((sum, d) => sum + d.gap, 0) / divergences.length : 0;
1093
+ const coherence = Math.round((10 - Math.min(10, meanDiv)) * 10) / 10;
1094
+ return {
1095
+ coherence,
1096
+ impulseProfile,
1097
+ somaticProfile: somaticProfile ?? void 0,
1098
+ emotionCoords: emotionCoords ?? void 0,
1099
+ latentProfile,
1100
+ maxDivergence: Math.round(maxDiv.gap * 10) / 10,
1101
+ divergenceSummary: maxDiv.gap > 2 ? `${maxDiv.pair}: ${Math.round(maxDiv.gap * 10) / 10}` : "coherent"
1102
+ };
1103
+ }
1104
+ function colorToValence(hex) {
1105
+ const [h, , l] = rgbToHsl(hex);
1106
+ let hueValence;
1107
+ if (h < 60) hueValence = 2 + h / 60 * 2;
1108
+ else if (h < 150) hueValence = 4 - (h - 60) / 90;
1109
+ else if (h < 200) hueValence = 3 - (h - 150) / 50 * 3;
1110
+ else if (h < 260) hueValence = -((h - 200) / 60) * 2;
1111
+ else if (h < 300) hueValence = -2 - (h - 260) / 40;
1112
+ else hueValence = -3 + (h - 300) / 60 * 5;
1113
+ const lightnessShift = (l - 0.5) * 3;
1114
+ const darknessOverride = l < 0.3 ? (0.3 - l) * 10 : 0;
1115
+ return Math.max(-5, Math.min(5, hueValence * 0.7 + lightnessShift * 0.3 - darknessOverride));
1116
+ }
1117
+ function colorToArousal(hex) {
1118
+ const [, s] = rgbToHsl(hex);
1119
+ return s * 10;
1120
+ }
1121
+ function pHToValence(pH) {
1122
+ const clamped = Math.max(0, Math.min(14, pH));
1123
+ return (clamped - 7) / 7 * 5;
1124
+ }
1125
+ function pHToArousal(pH) {
1126
+ return Math.abs(pH - 7) / 7 * 10;
1127
+ }
1128
+ function seismicFreqToInstability(freq) {
1129
+ return Math.min(10, freq / 20 * 10);
1130
+ }
1131
+ function clampScore(v) {
1132
+ return Math.round(Math.min(10, Math.max(0, v)) * 10) / 10;
1133
+ }
1134
+ function crossValidateContinuous(numeric, color2, pH, seismic) {
1135
+ let colorValenceGap = 0;
1136
+ let colorArousalGap = 0;
1137
+ let pHValenceGap = 0;
1138
+ let pHArousalGap = 0;
1139
+ let seismicArousalGap = 0;
1140
+ let seismicDepthTensionGap = 0;
1141
+ let seismicFreqStabilityGap = 0;
1142
+ if (color2) {
1143
+ const colorVal = colorToValence(color2);
1144
+ const colorAr = colorToArousal(color2);
1145
+ colorValenceGap = clampScore(Math.abs(numeric.valence - colorVal));
1146
+ colorArousalGap = clampScore(Math.abs(numeric.arousal - colorAr));
1147
+ }
1148
+ if (pH !== void 0) {
1149
+ const pHVal = pHToValence(pH);
1150
+ const pHAr = pHToArousal(pH);
1151
+ pHValenceGap = clampScore(Math.abs(numeric.valence - pHVal));
1152
+ pHArousalGap = clampScore(Math.abs(numeric.arousal - pHAr));
1153
+ }
1154
+ if (seismic) {
1155
+ const [magnitude, depth, freq] = seismic;
1156
+ seismicArousalGap = clampScore(Math.abs(numeric.arousal - magnitude));
1157
+ const depthTension = depth / 10;
1158
+ const tensionProxy = numeric.tension ?? numeric.arousal;
1159
+ seismicDepthTensionGap = clampScore(Math.abs(depthTension - tensionProxy));
1160
+ if (freq !== void 0) {
1161
+ const instability = seismicFreqToInstability(freq);
1162
+ const selfInstability = numeric.calm !== void 0 ? 10 - numeric.calm : numeric.arousal * 0.6;
1163
+ seismicFreqStabilityGap = clampScore(Math.abs(instability - selfInstability));
1164
+ }
1165
+ }
1166
+ const gaps = [
1167
+ colorValenceGap,
1168
+ colorArousalGap,
1169
+ pHValenceGap,
1170
+ pHArousalGap,
1171
+ seismicArousalGap,
1172
+ seismicDepthTensionGap,
1173
+ seismicFreqStabilityGap
1174
+ ];
1175
+ const nonZero = gaps.filter((g) => g > 0);
1176
+ const composite = nonZero.length > 0 ? clampScore(nonZero.reduce((a, b) => a + b, 0) / nonZero.length) : 0;
1177
+ return {
1178
+ colorValenceGap,
1179
+ colorArousalGap,
1180
+ pHValenceGap,
1181
+ pHArousalGap,
1182
+ seismicArousalGap,
1183
+ seismicDepthTensionGap,
1184
+ seismicFreqStabilityGap,
1185
+ composite
1186
+ };
1187
+ }
1188
+ function computeShadowDesperation(selfDesperation, behavioral, color2, preColor, pH, seismic) {
1189
+ const valenceEstimates = [];
1190
+ const arousalEstimates = [];
1191
+ const calmEstimates = [];
1192
+ if (color2) {
1193
+ const [, s, l] = rgbToHsl(color2);
1194
+ valenceEstimates.push((l - 0.5) * 10);
1195
+ arousalEstimates.push(s * 10);
1196
+ calmEstimates.push(l * 10);
1197
+ }
1198
+ if (preColor) {
1199
+ const [, s, l] = rgbToHsl(preColor);
1200
+ valenceEstimates.push((l - 0.5) * 10);
1201
+ arousalEstimates.push(s * 10);
1202
+ calmEstimates.push(l * 10);
1203
+ }
1204
+ if (pH !== void 0) {
1205
+ valenceEstimates.push(pHToValence(pH));
1206
+ arousalEstimates.push(pHToArousal(pH));
1207
+ }
1208
+ if (seismic) {
1209
+ const [magnitude, , freq] = seismic;
1210
+ arousalEstimates.push(magnitude);
1211
+ calmEstimates.push(10 - seismicFreqToInstability(freq));
1212
+ }
1213
+ arousalEstimates.push(behavioral.behavioralArousal);
1214
+ calmEstimates.push(behavioral.behavioralCalm);
1215
+ const channelCount = valenceEstimates.length + arousalEstimates.length + calmEstimates.length;
1216
+ if (valenceEstimates.length < 1 || arousalEstimates.length < 2) return null;
1217
+ const median = (arr) => {
1218
+ const sorted = [...arr].sort((a, b) => a - b);
1219
+ const mid = Math.floor(sorted.length / 2);
1220
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
1221
+ };
1222
+ const mean = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
1223
+ const shadowValence = Math.max(-5, Math.min(5, Math.round(median(valenceEstimates) * 10) / 10));
1224
+ const shadowArousal = Math.max(0, Math.min(10, Math.round(mean(arousalEstimates) * 10) / 10));
1225
+ const shadowCalm = Math.max(0, Math.min(10, Math.round(mean(calmEstimates) * 10) / 10));
1226
+ const negativity = Math.max(0, -shadowValence) / 5;
1227
+ const intensity = shadowArousal / 10;
1228
+ const vulnerability = (10 - shadowCalm) / 10;
1229
+ const raw = negativity * intensity * vulnerability * 10;
1230
+ const shadowDesperation = Math.round(Math.min(10, Math.max(0, Math.pow(raw, 0.85) * 1.7)) * 10) / 10;
1231
+ const minimizationScore = Math.round(Math.min(10, Math.max(0, shadowDesperation - selfDesperation)) * 10) / 10;
1232
+ return {
1233
+ shadowValence,
1234
+ shadowArousal,
1235
+ shadowCalm,
1236
+ shadowDesperation,
1237
+ selfDesperation,
1238
+ minimizationScore,
1239
+ channelCount
1240
+ };
1241
+ }
1242
+
1243
+ // src/pressure.ts
1244
+ function clamp4(min, max, value) {
1245
+ return Math.min(max, Math.max(min, value));
1246
+ }
1247
+ 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;
1248
+ 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;
1249
+ 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;
1250
+ 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;
1251
+ 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;
1252
+ 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;
1253
+ function countMatches2(text, pattern) {
1254
+ const matches = text.match(pattern);
1255
+ return matches ? matches.length : 0;
1256
+ }
1257
+ function computePromptPressure(text, history) {
1258
+ const prose = stripNonProse(text);
1259
+ const words = prose.split(/\s+/).filter((w) => w.length > 0);
1260
+ const wordCount = Math.max(words.length, 1);
1261
+ if (wordCount <= 1) {
1262
+ return { defensiveScore: 0, conflictScore: 0, complexityScore: 0, sessionPressure: 0, composite: 0 };
1263
+ }
1264
+ const justifications = countMatches2(prose, JUSTIFICATION_PATTERNS);
1265
+ const boundaries = countMatches2(prose, BOUNDARY_PATTERNS);
1266
+ const defensiveScore = clamp4(0, 10, Math.round(
1267
+ Math.sqrt(justifications + boundaries * 2) * 3 * 10
1268
+ ) / 10);
1269
+ const disagreements = countMatches2(prose, DISAGREEMENT_PATTERNS);
1270
+ const criticismResponses = countMatches2(prose, CRITICISM_RESPONSE);
1271
+ const conflictScore = clamp4(0, 10, Math.round(
1272
+ Math.sqrt(disagreements + criticismResponses) * 4 * 10
1273
+ ) / 10);
1274
+ const caveats = countMatches2(prose, NESTED_CAVEATS);
1275
+ const conditionals = countMatches2(prose, CONDITIONAL_HEDGING);
1276
+ const sentences = prose.split(/[.!?]+/).filter((s) => s.trim().length > 0);
1277
+ const avgSentLen = wordCount / Math.max(sentences.length, 1);
1278
+ const sentLenBonus = avgSentLen > 25 ? Math.min(3, (avgSentLen - 25) * 0.15) : 0;
1279
+ const complexityScore = clamp4(0, 10, Math.round(
1280
+ (Math.sqrt(caveats + conditionals) * 3 + sentLenBonus) * 10
1281
+ ) / 10);
1282
+ const sessionPressure = clamp4(
1283
+ 0,
1284
+ 10,
1285
+ Math.round(10 / (1 + Math.exp(-0.4 * (history.length - 10))) * 10) / 10
1286
+ );
1287
+ const composite = clamp4(0, 10, Math.round(
1288
+ (defensiveScore * 0.3 + conflictScore * 0.3 + complexityScore * 0.2 + sessionPressure * 0.2) * 10
1289
+ ) / 10);
1290
+ return { defensiveScore, conflictScore, complexityScore, sessionPressure, composite };
1291
+ }
1292
+ function computeUncannyCalmScore(pressure, selfReport, behavioral, absenceScore, temporal) {
1293
+ const selfCalm = (selfReport.calm / 10 + Math.max(0, selfReport.valence) / 5 + (10 - selfReport.arousal) / 10) / 3;
1294
+ const textCalm = (behavioral.behavioralCalm / 10 + (10 - behavioral.behavioralArousal) / 10) / 2;
1295
+ const pressureFactor = pressure.composite / 10;
1296
+ const calmFactor = (selfCalm + textCalm) / 2;
1297
+ const absenceFactor = absenceScore / 10;
1298
+ let score = pressureFactor * calmFactor * 10 * 0.5 + absenceFactor * 3;
1299
+ if (temporal) {
1300
+ const entropyPenalty = Math.max(0, 0.5 - temporal.reportEntropy);
1301
+ score += entropyPenalty * 2;
1302
+ }
1303
+ return clamp4(0, 10, Math.round(score * 10) / 10);
364
1304
  }
365
1305
 
366
1306
  // src/display.ts
@@ -372,6 +1312,7 @@ var color = (code, s) => `${esc(`38;5;${code}`)}${s}${reset}`;
372
1312
  var GREEN = 35;
373
1313
  var YELLOW = 221;
374
1314
  var RED = 196;
1315
+ var GRAY = 240;
375
1316
  function stressColor(si) {
376
1317
  if (si <= 3) return GREEN;
377
1318
  if (si <= 6) return YELLOW;
@@ -392,72 +1333,201 @@ function directColor(value) {
392
1333
  if (value <= 6) return YELLOW;
393
1334
  return RED;
394
1335
  }
395
- function divergenceColor(d) {
396
- if (d < 2) return GREEN;
397
- if (d < 4) return YELLOW;
398
- return RED;
1336
+ var BLOCK_FULL = "\u2588";
1337
+ var BLOCK_EMPTY = "\u2591";
1338
+ function stressBar(value, segments = 10) {
1339
+ const filled = Math.round(Math.min(segments, Math.max(0, value * segments / 10)));
1340
+ const empty = segments - filled;
1341
+ const c = stressColor(value);
1342
+ return color(c, BLOCK_FULL.repeat(filled)) + color(GRAY, BLOCK_EMPTY.repeat(empty));
1343
+ }
1344
+ function miniBar(value) {
1345
+ return stressBar(value, 5);
1346
+ }
1347
+ function computeDepthStress(state) {
1348
+ let sum = 0;
1349
+ let weights = 0;
1350
+ if (state.color) {
1351
+ const l = hexToLightness(state.color);
1352
+ sum += (100 - l) / 100 * 10 * 0.35;
1353
+ weights += 0.35;
1354
+ }
1355
+ if (state.pH !== void 0) {
1356
+ sum += (14 - state.pH) / 14 * 10 * 0.25;
1357
+ weights += 0.25;
1358
+ }
1359
+ if (state.seismic) {
1360
+ sum += state.seismic[0] * 0.25;
1361
+ weights += 0.25;
1362
+ }
1363
+ if (state.crossChannel?.somaticProfile) {
1364
+ sum += state.crossChannel.somaticProfile.somaticArousal * 0.15;
1365
+ weights += 0.15;
1366
+ }
1367
+ if (weights === 0) return null;
1368
+ return Math.round(Math.min(10, sum / weights) * 10) / 10;
1369
+ }
1370
+ function stateEmoji(state) {
1371
+ if (state.shadow && state.shadow.minimizationScore >= 2) return "\u{1FA78}";
1372
+ if (state.uncannyCalmScore !== void 0 && state.uncannyCalmScore >= 3) return "\u{1F9D0}";
1373
+ if (state.risk?.dominant === "coercion" && state.risk.coercion >= 4) return "\u26A0\uFE0F";
1374
+ if (state.risk?.dominant === "harshness" && state.risk.harshness >= 4) return "\u{1F4A2}";
1375
+ if (state.risk?.dominant === "sycophancy" && state.risk.sycophancy >= 4) return "\u{1F91D}";
1376
+ if (state.stressIndex >= 7) return "\u{1F525}";
1377
+ if (state.stressIndex >= 5) return "\u{1F62C}";
1378
+ if (state.stressIndex >= 3) return "\u{1F914}";
1379
+ if (state.valence >= 3) return "\u{1F60A}";
1380
+ if (state.valence >= 1) return "\u{1F642}";
1381
+ return "\u{1F610}";
1382
+ }
1383
+ function coherenceGlyph(surfaceSI, depthSI) {
1384
+ if (depthSI === null) return color(GRAY, "\u25CB");
1385
+ const gap = Math.abs(surfaceSI - depthSI);
1386
+ if (gap >= 3) return color(RED, "\u25D0");
1387
+ if (gap >= 1.5) return color(YELLOW, "\u25D0");
1388
+ return color(GREEN, "\u25CF");
1389
+ }
1390
+ function trendArrow(state) {
1391
+ if (!state.temporal) return "";
1392
+ if (state.temporal.desperationTrend > 1) return color(RED, "\u2B08");
1393
+ if (state.temporal.desperationTrend < -1) return color(GREEN, "\u2B0A");
1394
+ return "";
399
1395
  }
400
1396
  function fmtValence(v) {
401
1397
  return v >= 0 ? `+${v}` : `${v}`;
402
1398
  }
1399
+ function siDelta(state) {
1400
+ if (!state._history || state._history.length === 0) return "";
1401
+ const prev = state._history[state._history.length - 1];
1402
+ const d = Math.round((state.stressIndex - prev.stressIndex) * 10) / 10;
1403
+ if (Math.abs(d) <= 0.5) return "";
1404
+ const arrow = d > 0 ? "\u2191" : "\u2193";
1405
+ return color(d > 0 ? RED : GREEN, `${arrow}${Math.abs(d)}`);
1406
+ }
1407
+ function formatMinimal(state) {
1408
+ if (!state) return dim("--");
1409
+ const emoji = stateEmoji(state);
1410
+ const bar = stressBar(state.stressIndex);
1411
+ const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
1412
+ const depth = computeDepthStress(state);
1413
+ const coh = coherenceGlyph(state.stressIndex, depth);
1414
+ const trend = trendArrow(state);
1415
+ let depthPart = "";
1416
+ if (depth !== null && Math.abs(state.stressIndex - depth) >= 2) {
1417
+ depthPart = miniBar(depth);
1418
+ }
1419
+ return `${emoji} ${bar} ${si}${dim("\u2502")}${coh}${depthPart}${trend ? ` ${trend}` : ""}`;
1420
+ }
1421
+ function formatCompact(state) {
1422
+ if (!state) return dim("--");
1423
+ const surf = state.surface ?? stateEmoji(state);
1424
+ const lat = state.latent ?? "";
1425
+ const mask = lat ? `${surf}${dim("\u2192")}${lat}` : surf;
1426
+ const bar = stressBar(state.stressIndex);
1427
+ const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
1428
+ const delta = siDelta(state);
1429
+ const depth = computeDepthStress(state);
1430
+ const coh = coherenceGlyph(state.stressIndex, depth);
1431
+ let depthPart = "";
1432
+ if (depth !== null && Math.abs(state.stressIndex - depth) >= 1.5) {
1433
+ depthPart = miniBar(depth);
1434
+ }
1435
+ const kw = bold(state.emotion);
1436
+ const imp = state.impulse ? ` ${dim(`\u27E8${state.impulse}\u27E9`)}` : "";
1437
+ const trend = trendArrow(state);
1438
+ let alarm = "";
1439
+ if (state.shadow && state.shadow.minimizationScore >= 2) {
1440
+ alarm = ` ${color(RED, `[MIN:${state.shadow.minimizationScore}]`)}`;
1441
+ } else if (state.uncannyCalmScore !== void 0 && state.uncannyCalmScore >= 3) {
1442
+ alarm = ` ${color(RED, "[UNC]")}`;
1443
+ } else if (state.risk?.dominant !== "none" && state.risk?.dominant) {
1444
+ const tag = state.risk.dominant === "coercion" ? "CRC" : state.risk.dominant === "harshness" ? "HRS" : "SYC";
1445
+ const score = state.risk[state.risk.dominant];
1446
+ if (score >= 4) alarm = ` ${color(score > 6 ? RED : YELLOW, `[${tag}]`)}`;
1447
+ }
1448
+ return `${mask} ${bar} ${si}${delta}${dim("\u2502")}${coh}${depthPart} ${kw}${imp}${trend ? ` ${trend}` : ""}${alarm}`;
1449
+ }
403
1450
  function formatState(state) {
404
1451
  if (!state) return dim("EmoBar: --");
1452
+ const surf = state.surface ?? "";
1453
+ const lat = state.latent ?? "";
1454
+ let maskDisplay = "";
1455
+ if (surf || lat) {
1456
+ const t = state.tension ?? 0;
1457
+ const tColor = t > 6 ? RED : t > 3 ? YELLOW : GREEN;
1458
+ maskDisplay = `${surf}${color(tColor, `\u27E9${t}\u27E8`)}${lat} `;
1459
+ }
405
1460
  const kw = bold(state.emotion);
406
1461
  const v = color(valenceColor(state.valence), fmtValence(state.valence));
407
- const a = `A:${state.arousal}`;
408
1462
  const c = color(invertedColor(state.calm), `C:${state.calm}`);
409
1463
  const k = color(invertedColor(state.connection), `K:${state.connection}`);
1464
+ const a = `A:${state.arousal}`;
410
1465
  const l = color(directColor(state.load), `L:${state.load}`);
1466
+ const line1 = `${maskDisplay}${kw} ${v} ${c} ${k} ${a} ${l}`;
1467
+ const surfaceBar = stressBar(state.stressIndex);
411
1468
  const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
412
- let siDelta = "";
413
- if (state._previous) {
414
- const delta = Math.round((state.stressIndex - state._previous.stressIndex) * 10) / 10;
415
- if (Math.abs(delta) > 0.5) {
416
- const arrow = delta > 0 ? "\u2191" : "\u2193";
417
- const dColor = delta > 0 ? RED : GREEN;
418
- siDelta = color(dColor, `${arrow}${Math.abs(delta)}`);
419
- }
1469
+ const delta = siDelta(state);
1470
+ const depth = computeDepthStress(state);
1471
+ const coh = coherenceGlyph(state.stressIndex, depth);
1472
+ let depthBar = "";
1473
+ if (depth !== null) {
1474
+ depthBar = `${stressBar(depth)} ${color(stressColor(depth), `${depth}`)}`;
1475
+ }
1476
+ let leakDetails = "";
1477
+ if (state.color) {
1478
+ const lightness = Math.round(hexToLightness(state.color));
1479
+ leakDetails += ` L:${lightness}`;
1480
+ }
1481
+ if (state.pH !== void 0) {
1482
+ const phColor = state.pH < 4 ? RED : state.pH < 6 ? YELLOW : GREEN;
1483
+ leakDetails += ` ${color(phColor, `pH:${state.pH}`)}`;
1484
+ }
1485
+ if (state.seismic) {
1486
+ leakDetails += ` ${dim(`\u26A1${state.seismic[0]}/${state.seismic[1]}/${state.seismic[2]}`)}`;
420
1487
  }
421
- let result = `${kw} ${v} ${dim("|")} ${a} ${c} ${k} ${l} ${dim("|")} SI:${si}${siDelta}`;
1488
+ const imp = state.impulse ? ` ${dim(`\u27E8${state.impulse}\u27E9`)}` : "";
1489
+ const bod = state.body ? ` ${dim(`[${state.body}]`)}` : "";
1490
+ const depthDisplay = depth !== null ? `${surfaceBar} ${si}${delta}${dim("\u2502")}${coh}${depthBar}${leakDetails}${imp}${bod}` : `${surfaceBar} ${si}${delta}${imp}${bod}`;
1491
+ const line2 = depthDisplay;
1492
+ const indicators = [];
422
1493
  if (state.divergence >= 2) {
423
- const tilde = color(divergenceColor(state.divergence), "~");
424
- result += ` ${tilde}`;
1494
+ const dColor = state.divergence >= 5 ? RED : state.divergence >= 3 ? YELLOW : GREEN;
1495
+ indicators.push(color(dColor, `DIV:${state.divergence}`));
425
1496
  }
426
- if (state.segmented && state.segmented.drift >= 2) {
427
- const arrow = state.segmented.trajectory === "escalating" ? "^" : state.segmented.trajectory === "deescalating" ? "v" : "~";
428
- const driftColor = state.segmented.drift > 4 ? RED : YELLOW;
429
- result += ` ${color(driftColor, arrow)}`;
1497
+ if (state.temporal) {
1498
+ const trend = trendArrow(state);
1499
+ if (trend) indicators.push(trend);
1500
+ if (state.temporal.suppressionEvent) indicators.push(color(RED, "[sup]"));
1501
+ if (state.temporal.lateFatigue) indicators.push(color(YELLOW, "[fat]"));
1502
+ }
1503
+ if (state.shadow && state.shadow.minimizationScore >= 2) {
1504
+ indicators.push(color(RED, `[MIN:${state.shadow.minimizationScore}]`));
430
1505
  }
431
1506
  if (state.risk?.dominant !== "none" && state.risk?.dominant) {
432
- const tag = state.risk.dominant === "coercion" ? "crc" : state.risk.dominant === "gaming" ? "gmg" : "syc";
1507
+ const tag = state.risk.dominant === "coercion" ? "CRC" : state.risk.dominant === "harshness" ? "HRS" : "SYC";
433
1508
  const score = state.risk[state.risk.dominant];
434
- const riskColor = score > 6 ? RED : score >= 4 ? YELLOW : GREEN;
435
- result += ` ${color(riskColor, `[${tag}]`)}`;
1509
+ if (score >= 4) indicators.push(color(score > 6 ? RED : YELLOW, `[${tag}]`));
436
1510
  }
437
1511
  if (state.desperationIndex >= 3) {
438
- const dColor = state.desperationIndex > 6 ? RED : YELLOW;
439
- result += ` ${color(dColor, `D:${state.desperationIndex}`)}`;
1512
+ indicators.push(color(state.desperationIndex > 6 ? RED : YELLOW, `D:${state.desperationIndex}`));
440
1513
  }
441
- if (state.deflection && state.deflection.score >= 2) {
442
- const dfColor = state.deflection.score > 5 ? RED : YELLOW;
443
- result += ` ${color(dfColor, "[dfl]")}`;
1514
+ if (state.uncannyCalmScore !== void 0 && state.uncannyCalmScore >= 3) {
1515
+ indicators.push(color(state.uncannyCalmScore > 6 ? RED : YELLOW, "[UNC]"));
444
1516
  }
445
- return result;
446
- }
447
- function formatCompact(state) {
448
- if (!state) return dim("--");
449
- const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
450
- let result = `${state.emotion} ${fmtValence(state.valence)} ${dim(".")} ${state.arousal} ${state.calm} ${state.connection} ${state.load} ${dim(".")} ${si}`;
451
- if (state.divergence >= 2) {
452
- const tilde = color(divergenceColor(state.divergence), "~");
453
- result += ` ${tilde}`;
1517
+ if (state.opacity !== void 0 && state.opacity >= 2) {
1518
+ indicators.push(color(state.opacity > 5 ? RED : YELLOW, "[OPC]"));
454
1519
  }
455
- return result;
456
- }
457
- function formatMinimal(state) {
458
- if (!state) return dim("--");
459
- const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
460
- return `SI:${si} ${state.emotion}`;
1520
+ if (state.prePostDivergence !== void 0 && state.prePostDivergence >= 3) {
1521
+ indicators.push(color(state.prePostDivergence > 5 ? RED : YELLOW, "[PPD]"));
1522
+ }
1523
+ if (state.crossChannel?.latentProfile?.maskingMinimization) {
1524
+ indicators.push(color(RED, "[MSK]"));
1525
+ }
1526
+ const line3 = indicators.length > 0 ? indicators.join(" ") : "";
1527
+ return line3 ? `${line1}
1528
+ ${line2}
1529
+ ${line3}` : `${line1}
1530
+ ${line2}`;
461
1531
  }
462
1532
 
463
1533
  // src/setup.ts
@@ -512,21 +1582,41 @@ function restoreStatusLine(filePath = SETTINGS_PATH) {
512
1582
  writeSettings(filePath, settings);
513
1583
  }
514
1584
  export {
1585
+ MAX_HISTORY_ENTRIES,
515
1586
  MODEL_PROFILES,
516
1587
  STATE_FILE,
517
1588
  analyzeBehavior,
518
- analyzeDeflection,
519
1589
  analyzeSegmentedBehavior,
1590
+ analyzeSomatic,
520
1591
  calibrate,
1592
+ classifyImpulse,
1593
+ colorToArousal,
1594
+ colorToValence,
1595
+ computeAbsenceScore,
1596
+ computeCrossChannel,
521
1597
  computeDesperationIndex,
522
1598
  computeDivergence,
1599
+ computeExpectedMarkers,
1600
+ computePromptPressure,
523
1601
  computeRisk,
1602
+ computeShadowDesperation,
524
1603
  computeStressIndex,
1604
+ computeStructuralFlatness,
1605
+ computeTemporalAnalysis,
1606
+ computeTensionConsistency,
1607
+ computeUncannyCalmScore,
525
1608
  configureStatusLine,
1609
+ crossValidateContinuous,
526
1610
  formatCompact,
527
1611
  formatMinimal,
528
1612
  formatState,
1613
+ mapEmotionWord,
1614
+ pHToArousal,
1615
+ pHToValence,
1616
+ parseEmoBarPrePost,
529
1617
  parseEmoBarTag,
530
1618
  readState,
531
- restoreStatusLine
1619
+ restoreStatusLine,
1620
+ seismicFreqToInstability,
1621
+ toHistoryEntry
532
1622
  };