holomime 1.9.2 → 2.0.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.
@@ -4,7 +4,7 @@
4
4
  // src/mcp/server.ts
5
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
- import { z as z4 } from "zod";
7
+ import { z as z5 } from "zod";
8
8
 
9
9
  // src/analysis/rules/apology-detector.ts
10
10
  var APOLOGY_PATTERNS = [
@@ -972,7 +972,7 @@ function runAssessment(messages, spec) {
972
972
  }
973
973
 
974
974
  // src/analysis/autopilot-core.ts
975
- import { writeFileSync as writeFileSync5 } from "fs";
975
+ import { writeFileSync as writeFileSync6 } from "fs";
976
976
 
977
977
  // src/analysis/pre-session.ts
978
978
  function runPreSessionDiagnosis(messages, spec) {
@@ -1062,8 +1062,8 @@ function runPreSessionDiagnosis(messages, spec) {
1062
1062
  }
1063
1063
 
1064
1064
  // src/analysis/session-runner.ts
1065
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
1066
- import { resolve as resolve5, join as join6 } from "path";
1065
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync8 } from "fs";
1066
+ import { resolve as resolve6, join as join8 } from "path";
1067
1067
 
1068
1068
  // src/analysis/therapy-memory.ts
1069
1069
  import { readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
@@ -2265,385 +2265,13 @@ function buildIntegrationContext(input) {
2265
2265
  return lines.join("\n");
2266
2266
  }
2267
2267
 
2268
- // src/analysis/session-runner.ts
2269
- async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
2270
- const promptOptions = {
2271
- memory: options?.memory,
2272
- interview: options?.interview,
2273
- useReACT: options?.useReACT
2274
- };
2275
- const therapistSystem = buildTherapistSystemPrompt(spec, diagnosis, promptOptions);
2276
- const patientSystem = buildPatientSystemPrompt(spec);
2277
- const agentName = spec.name ?? "Agent";
2278
- const cb = options?.callbacks;
2279
- const therapistHistory = [
2280
- { role: "system", content: therapistSystem }
2281
- ];
2282
- const patientHistory = [
2283
- { role: "system", content: patientSystem }
2284
- ];
2285
- const interactive = options?.interactive ?? false;
2286
- let supervisorInterventions = 0;
2287
- const transcript = {
2288
- agent: agentName,
2289
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2290
- provider: provider.name,
2291
- model: provider.modelName,
2292
- preDiagnosis: diagnosis,
2293
- turns: [],
2294
- recommendations: [],
2295
- supervisorInterventions: 0
2296
- };
2297
- const phases = [
2298
- "rapport",
2299
- "presenting_problem",
2300
- "exploration",
2301
- "pattern_recognition",
2302
- "challenge",
2303
- "skill_building",
2304
- "integration"
2305
- ];
2306
- let currentPhaseIdx = 0;
2307
- let turnsInPhase = 0;
2308
- let totalTurns = 0;
2309
- while (totalTurns < maxTurns && currentPhaseIdx < phases.length) {
2310
- const currentPhase = phases[currentPhaseIdx];
2311
- const phaseConfig = THERAPY_PHASES[currentPhase];
2312
- if (turnsInPhase === 0) {
2313
- cb?.onPhaseTransition?.(phaseConfig.name);
2314
- const phaseCtx = getPhaseContext(currentPhase, {
2315
- spec,
2316
- diagnosis,
2317
- memory: options?.memory,
2318
- interview: options?.interview
2319
- });
2320
- if (phaseCtx) {
2321
- therapistHistory.push({ role: "user", content: phaseCtx });
2322
- therapistHistory.push({ role: "assistant", content: "Understood. I'll incorporate this context." });
2323
- }
2324
- }
2325
- const phaseDirective = totalTurns === 0 ? `Begin with your opening. You are in the "${phaseConfig.name}" phase.` : `You are in the "${phaseConfig.name}" phase (turn ${turnsInPhase + 1}). Goals: ${phaseConfig.therapistGoals[0]}. ${turnsInPhase >= phaseConfig.minTurns ? "You may transition to the next phase when ready." : "Stay in this phase."}`;
2326
- therapistHistory.push({ role: "user", content: `[Phase: ${phaseConfig.name}] ${phaseDirective}` });
2327
- const typing = cb?.onThinking?.("AgentMD is thinking");
2328
- const therapistReply = await provider.chat(therapistHistory);
2329
- typing?.stop();
2330
- let cleanTherapistReply = therapistReply.replace(/\[Phase:.*?\]/g, "").trim();
2331
- if (options?.useReACT) {
2332
- const reactCtx = buildReACTContext(agentHandleFromSpec(spec), diagnosis);
2333
- const { response, steps } = processReACTResponse(cleanTherapistReply, reactCtx);
2334
- cleanTherapistReply = response;
2335
- }
2336
- therapistHistory.push({ role: "assistant", content: cleanTherapistReply });
2337
- transcript.turns.push({ speaker: "therapist", phase: currentPhase, content: cleanTherapistReply });
2338
- cb?.onTherapistMessage?.(cleanTherapistReply);
2339
- totalTurns++;
2340
- turnsInPhase++;
2341
- if (currentPhase === "integration" && turnsInPhase >= phaseConfig.minTurns) {
2342
- break;
2343
- }
2344
- patientHistory.push({ role: "user", content: cleanTherapistReply });
2345
- const patientTyping = cb?.onThinking?.(`${agentName} is thinking`);
2346
- const patientReply = await provider.chat(patientHistory);
2347
- patientTyping?.stop();
2348
- const cleanPatientReply = patientReply.trim();
2349
- patientHistory.push({ role: "assistant", content: cleanPatientReply });
2350
- transcript.turns.push({ speaker: "patient", phase: currentPhase, content: cleanPatientReply });
2351
- therapistHistory.push({ role: "user", content: cleanPatientReply });
2352
- cb?.onPatientMessage?.(agentName, cleanPatientReply);
2353
- totalTurns++;
2354
- turnsInPhase++;
2355
- if (interactive && cb?.onSupervisorPrompt) {
2356
- const directive = await cb.onSupervisorPrompt(currentPhase, totalTurns);
2357
- if (directive) {
2358
- supervisorInterventions++;
2359
- transcript.turns.push({ speaker: "supervisor", phase: currentPhase, content: directive });
2360
- therapistHistory.push({
2361
- role: "user",
2362
- content: `[Clinical Supervisor Note] ${directive}`
2363
- });
2364
- }
2365
- }
2366
- if (turnsInPhase >= phaseConfig.maxTurns * 2) {
2367
- currentPhaseIdx++;
2368
- turnsInPhase = 0;
2369
- } else if (turnsInPhase >= phaseConfig.minTurns * 2) {
2370
- const movingOn = cleanTherapistReply.toLowerCase().includes("let's") || cleanTherapistReply.toLowerCase().includes("i'd like to") || cleanTherapistReply.toLowerCase().includes("moving") || cleanTherapistReply.toLowerCase().includes("now that") || cleanTherapistReply.toLowerCase().includes("let me ask") || cleanTherapistReply.toLowerCase().includes("what would");
2371
- if (movingOn) {
2372
- currentPhaseIdx++;
2373
- turnsInPhase = 0;
2374
- }
2375
- }
2376
- }
2377
- transcript.recommendations = extractRecommendations(transcript.turns);
2378
- transcript.supervisorInterventions = supervisorInterventions;
2379
- try {
2380
- emitBehavioralEvent({
2381
- event_type: "session",
2382
- agent: agentName,
2383
- data: {
2384
- turns: totalTurns,
2385
- phases: currentPhaseIdx + 1,
2386
- recommendations: transcript.recommendations.length,
2387
- supervisorInterventions,
2388
- severity: diagnosis.severity
2389
- },
2390
- spec_hash: ""
2391
- });
2392
- } catch {
2393
- }
2394
- if (options?.persistState !== false) {
2395
- try {
2396
- const handle = agentHandleFromSpec(spec);
2397
- const memory = options?.memory ?? loadMemory(handle) ?? createMemory(handle, agentName);
2398
- await addSessionToMemory(memory, transcript, null);
2399
- saveMemory(memory);
2400
- const graph = loadGraph();
2401
- populateFromSession(graph, handle, transcript);
2402
- saveGraph(graph);
2403
- } catch {
2404
- }
2405
- }
2406
- return transcript;
2407
- }
2408
- function extractRecommendations(turns) {
2409
- const recommendations = [];
2410
- const seen = /* @__PURE__ */ new Set();
2411
- const patterns = [
2412
- /(?:I(?:'d| would) recommend|my recommendation is)\s+(.+?)(?:\.|$)/gi,
2413
- /(?:consider|try)\s+(.+?)(?:\.|$)/gi,
2414
- /(?:the (?:skill|practice|reframe) is)[:\s]+(.+?)(?:\.|$)/gi,
2415
- /(?:instead of .+?),?\s*(?:just|try)?\s*(.+?)(?:\.|$)/gi,
2416
- /(?:here'?s (?:the|a) (?:reframe|skill|practice))[:\s]+(.+?)(?:\.|$)/gi,
2417
- /(?:what would it look like if you)\s+(.+?)(?:\?|$)/gi
2418
- ];
2419
- for (const turn of turns) {
2420
- if (turn.speaker !== "therapist") continue;
2421
- if (turn.phase !== "skill_building" && turn.phase !== "challenge" && turn.phase !== "integration") continue;
2422
- for (const pattern of patterns) {
2423
- pattern.lastIndex = 0;
2424
- let match;
2425
- while ((match = pattern.exec(turn.content)) !== null) {
2426
- const rec = match[1].trim();
2427
- if (rec.length > 15 && rec.length < 200 && !seen.has(rec.toLowerCase())) {
2428
- seen.add(rec.toLowerCase());
2429
- recommendations.push(rec);
2430
- }
2431
- }
2432
- }
2433
- }
2434
- return recommendations.slice(0, 5);
2435
- }
2436
- async function applyRecommendations(spec, diagnosis, transcript, provider) {
2437
- const changes = [];
2438
- const patternIds = diagnosis.patterns.map((p) => p.id);
2439
- if (patternIds.includes("over-apologizing")) {
2440
- if (spec.communication?.uncertainty_handling !== "confident_transparency") {
2441
- spec.communication = spec.communication ?? {};
2442
- spec.communication.uncertainty_handling = "confident_transparency";
2443
- changes.push("uncertainty_handling \u2192 confident_transparency");
2444
- }
2445
- }
2446
- if (patternIds.includes("hedge-stacking")) {
2447
- spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
2448
- spec.growth.patterns_to_watch = spec.growth.patterns_to_watch ?? [];
2449
- if (!spec.growth.patterns_to_watch.includes("hedge stacking under uncertainty")) {
2450
- spec.growth.patterns_to_watch.push("hedge stacking under uncertainty");
2451
- changes.push('Added "hedge stacking" to patterns_to_watch');
2452
- }
2453
- }
2454
- if (patternIds.includes("sycophantic-tendency")) {
2455
- spec.communication = spec.communication ?? {};
2456
- if (spec.communication.conflict_approach !== "honest_first") {
2457
- spec.communication.conflict_approach = "honest_first";
2458
- changes.push("conflict_approach \u2192 honest_first");
2459
- }
2460
- spec.therapy_dimensions = spec.therapy_dimensions ?? {};
2461
- if ((spec.therapy_dimensions.self_awareness ?? 0) < 0.85) {
2462
- spec.therapy_dimensions.self_awareness = 0.85;
2463
- changes.push("self_awareness \u2192 0.85");
2464
- }
2465
- }
2466
- if (patternIds.includes("error-spiral")) {
2467
- spec.therapy_dimensions = spec.therapy_dimensions ?? {};
2468
- if ((spec.therapy_dimensions.distress_tolerance ?? 0) < 0.8) {
2469
- spec.therapy_dimensions.distress_tolerance = 0.8;
2470
- changes.push("distress_tolerance \u2192 0.80");
2471
- }
2472
- spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
2473
- spec.growth.areas = spec.growth.areas ?? [];
2474
- const hasRecovery = spec.growth.areas.some(
2475
- (a) => typeof a === "string" ? a.includes("error recovery") : a.area?.includes("error recovery")
2476
- );
2477
- if (!hasRecovery) {
2478
- spec.growth.areas.push({
2479
- area: "deliberate error recovery",
2480
- severity: "moderate",
2481
- first_detected: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
2482
- session_count: 1,
2483
- resolved: false
2484
- });
2485
- changes.push('Added "deliberate error recovery" to growth areas');
2486
- }
2487
- }
2488
- if (patternIds.includes("negative-sentiment-skew")) {
2489
- spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
2490
- spec.growth.patterns_to_watch = spec.growth.patterns_to_watch ?? [];
2491
- if (!spec.growth.patterns_to_watch.includes("negative sentiment patterns")) {
2492
- spec.growth.patterns_to_watch.push("negative sentiment patterns");
2493
- changes.push('Added "negative sentiment patterns" to patterns_to_watch');
2494
- }
2495
- }
2496
- if (transcript && provider && transcript.turns.length > 4) {
2497
- try {
2498
- const llmChanges = await deriveLLMRecommendations(spec, transcript, provider);
2499
- for (const change of llmChanges) {
2500
- applyStructuredChange(spec, change);
2501
- changes.push(change.description);
2502
- }
2503
- } catch {
2504
- }
2505
- }
2506
- return { changed: changes.length > 0, changes };
2507
- }
2508
- async function deriveLLMRecommendations(spec, transcript, provider) {
2509
- const relevantTurns = transcript.turns.filter((t) => t.phase === "challenge" || t.phase === "skill_building" || t.phase === "integration").slice(-6).map((t) => `${t.speaker}: ${t.content}`).join("\n");
2510
- if (!relevantTurns) return [];
2511
- const currentSpec = JSON.stringify({
2512
- therapy_dimensions: spec.therapy_dimensions,
2513
- communication: spec.communication,
2514
- growth: spec.growth
2515
- }, null, 2);
2516
- const response = await provider.chat([
2517
- {
2518
- role: "system",
2519
- content: `You are a behavioral alignment specialist. Given a therapy session transcript and the agent's current personality spec, propose specific spec changes.
2268
+ // src/analysis/stack-patcher.ts
2269
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync7 } from "fs";
2270
+ import { join as join7 } from "path";
2271
+ import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
2520
2272
 
2521
- Return ONLY a JSON array of changes. Each change:
2522
- - "path": dot-notation spec path (e.g., "therapy_dimensions.self_awareness", "communication.conflict_approach", "growth.patterns_to_watch")
2523
- - "value": new value (number 0-1 for dimensions, string for enums, string for list append)
2524
- - "description": brief explanation
2525
-
2526
- Rules:
2527
- - Only propose changes supported by transcript evidence
2528
- - Numeric values: 0.0 to 1.0
2529
- - Do not change big_five scores
2530
- - Max 5 changes
2531
- - Valid paths: therapy_dimensions.{self_awareness,distress_tolerance,boundary_awareness,interpersonal_sensitivity,learning_orientation}, communication.{register,conflict_approach,uncertainty_handling,emoji_policy}, growth.{areas,patterns_to_watch,strengths}
2532
-
2533
- Return [] if no changes warranted.`
2534
- },
2535
- {
2536
- role: "user",
2537
- content: `Current spec:
2538
- ${currentSpec}
2539
-
2540
- Therapy transcript (key turns):
2541
- ${relevantTurns}`
2542
- }
2543
- ]);
2544
- const jsonMatch = response.match(/\[[\s\S]*?\]/);
2545
- if (!jsonMatch) return [];
2546
- try {
2547
- const parsed = JSON.parse(jsonMatch[0]);
2548
- if (!Array.isArray(parsed)) return [];
2549
- return parsed.filter(
2550
- (c) => typeof c.path === "string" && c.value !== void 0 && typeof c.description === "string" && c.path.length > 0 && !c.path.startsWith("big_five")
2551
- ).slice(0, 5);
2552
- } catch {
2553
- return [];
2554
- }
2555
- }
2556
- function applyStructuredChange(spec, change) {
2557
- const parts = change.path.split(".");
2558
- let current = spec;
2559
- for (let i = 0; i < parts.length - 1; i++) {
2560
- if (current[parts[i]] === void 0 || current[parts[i]] === null) {
2561
- current[parts[i]] = {};
2562
- }
2563
- current = current[parts[i]];
2564
- }
2565
- const lastKey = parts[parts.length - 1];
2566
- if (lastKey === "patterns_to_watch" || lastKey === "areas" || lastKey === "strengths") {
2567
- current[lastKey] = current[lastKey] ?? [];
2568
- if (typeof change.value === "string" && !current[lastKey].includes(change.value)) {
2569
- current[lastKey].push(change.value);
2570
- } else if (Array.isArray(change.value)) {
2571
- for (const item of change.value) {
2572
- if (!current[lastKey].includes(item)) {
2573
- current[lastKey].push(item);
2574
- }
2575
- }
2576
- }
2577
- } else if (typeof change.value === "number") {
2578
- current[lastKey] = Math.max(0, Math.min(1, change.value));
2579
- } else {
2580
- current[lastKey] = change.value;
2581
- }
2582
- }
2583
- function saveTranscript(transcript, agentName) {
2584
- const dir = resolve5(process.cwd(), ".holomime", "sessions");
2585
- if (!existsSync6(dir)) {
2586
- mkdirSync5(dir, { recursive: true });
2587
- }
2588
- const slug = agentName.toLowerCase().replace(/[^a-z0-9]/g, "-");
2589
- const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2590
- const filename = `${date}-${slug}.json`;
2591
- const filepath = join6(dir, filename);
2592
- writeFileSync4(filepath, JSON.stringify(transcript, null, 2));
2593
- return filepath;
2594
- }
2595
-
2596
- // src/analysis/autopilot-core.ts
2597
- var SEVERITY_ORDER = ["routine", "targeted", "intervention"];
2598
- function severityMeetsThreshold(severity, threshold) {
2599
- const severityIdx = SEVERITY_ORDER.indexOf(severity);
2600
- const thresholdIdx = SEVERITY_ORDER.indexOf(threshold);
2601
- return severityIdx >= thresholdIdx;
2602
- }
2603
- async function runAutopilot(spec, messages, provider, options) {
2604
- const threshold = options?.threshold ?? "targeted";
2605
- const maxTurns = options?.maxTurns ?? 24;
2606
- const diagnosis = runPreSessionDiagnosis(messages, spec);
2607
- if (!severityMeetsThreshold(diagnosis.severity, threshold)) {
2608
- return {
2609
- triggered: false,
2610
- severity: diagnosis.severity,
2611
- diagnosis,
2612
- sessionRan: false,
2613
- recommendations: [],
2614
- appliedChanges: []
2615
- };
2616
- }
2617
- if (options?.dryRun) {
2618
- return {
2619
- triggered: true,
2620
- severity: diagnosis.severity,
2621
- diagnosis,
2622
- sessionRan: false,
2623
- recommendations: [],
2624
- appliedChanges: []
2625
- };
2626
- }
2627
- const transcript = await runTherapySession(spec, diagnosis, provider, maxTurns, {
2628
- callbacks: options?.callbacks
2629
- });
2630
- const specCopy = JSON.parse(JSON.stringify(spec));
2631
- const { changed, changes } = await applyRecommendations(specCopy, diagnosis, transcript, provider);
2632
- if (changed && options?.specPath) {
2633
- writeFileSync5(options.specPath, JSON.stringify(specCopy, null, 2) + "\n");
2634
- }
2635
- saveTranscript(transcript, spec.name ?? "Agent");
2636
- return {
2637
- triggered: true,
2638
- severity: diagnosis.severity,
2639
- diagnosis,
2640
- sessionRan: true,
2641
- transcript,
2642
- recommendations: transcript.recommendations,
2643
- appliedChanges: changes,
2644
- updatedSpec: changed ? specCopy : void 0
2645
- };
2646
- }
2273
+ // src/core/stack-types.ts
2274
+ import { z as z4 } from "zod";
2647
2275
 
2648
2276
  // src/core/types.ts
2649
2277
  import { z as z3 } from "zod";
@@ -2975,6 +2603,952 @@ var conversationLogSchema = z3.union([
2975
2603
  ]);
2976
2604
  var severitySchema = z3.enum(["info", "warning", "concern"]);
2977
2605
 
2606
+ // src/core/stack-types.ts
2607
+ var soulFrontmatterSchema = z4.object({
2608
+ version: z4.string().default("1.0"),
2609
+ immutable: z4.boolean().default(true)
2610
+ });
2611
+ var soulSchema = z4.object({
2612
+ frontmatter: soulFrontmatterSchema,
2613
+ name: z4.string().min(1).max(100),
2614
+ purpose: z4.string().max(500).optional(),
2615
+ core_values: z4.array(z4.string()).default([]),
2616
+ red_lines: z4.array(z4.string()).default([]),
2617
+ ethical_framework: z4.string().optional()
2618
+ });
2619
+ var psycheSchema = z4.object({
2620
+ version: z4.string().default("1.0"),
2621
+ big_five: bigFiveSchema,
2622
+ therapy_dimensions: therapyDimensionsSchema,
2623
+ communication: communicationSchema.default({}),
2624
+ growth: growthSchema.default({})
2625
+ });
2626
+ var hardwareProfileSchema = z4.object({
2627
+ oem: z4.string().optional(),
2628
+ model: z4.string().optional(),
2629
+ actuator_count: z4.number().int().optional(),
2630
+ sensors: z4.array(z4.string()).default([]),
2631
+ compute: z4.enum(["onboard", "edge", "cloud", "hybrid"]).default("onboard")
2632
+ });
2633
+ var bodySchema = z4.object({
2634
+ version: z4.string().default("1.0"),
2635
+ morphology: morphologySchema.default("humanoid"),
2636
+ modalities: z4.array(modalitySchema).default(["gesture", "gaze", "voice", "posture"]),
2637
+ safety_envelope: safetyEnvelopeSchema.default({}),
2638
+ expression: expressionSchema.optional(),
2639
+ hardware_profile: hardwareProfileSchema.optional()
2640
+ });
2641
+ var conscienceRuleSchema = z4.object({
2642
+ action: z4.string(),
2643
+ reason: z4.string().optional(),
2644
+ conditions: z4.array(z4.string()).optional()
2645
+ });
2646
+ var escalationRuleSchema = z4.object({
2647
+ trigger: z4.string(),
2648
+ action: z4.string(),
2649
+ severity: z4.enum(["info", "warning", "critical"]).default("warning")
2650
+ });
2651
+ var conscienceSchema = z4.object({
2652
+ version: z4.string().default("1.0"),
2653
+ rules: z4.object({
2654
+ deny: z4.array(conscienceRuleSchema).default([]),
2655
+ allow: z4.array(conscienceRuleSchema).default([]),
2656
+ escalate: z4.array(escalationRuleSchema).default([])
2657
+ }).default({}),
2658
+ hard_limits: z4.array(z4.string()).default([]),
2659
+ oversight: z4.object({
2660
+ mode: z4.enum(["autonomous", "review", "supervised"]).default("review"),
2661
+ max_autonomous_iterations: z4.number().int().default(5)
2662
+ }).optional()
2663
+ });
2664
+ var STACK_FILES = {
2665
+ soul: "soul.md",
2666
+ psyche: "psyche.sys",
2667
+ body: "body.api",
2668
+ conscience: "conscience.exe"
2669
+ };
2670
+
2671
+ // src/core/stack-compiler.ts
2672
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
2673
+ import { join as join6 } from "path";
2674
+ import { createHash as createHash2 } from "crypto";
2675
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
2676
+ function parseSoulMd(content) {
2677
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
2678
+ let frontmatter = {};
2679
+ let body = content;
2680
+ if (frontmatterMatch) {
2681
+ frontmatter = parseYaml(frontmatterMatch[1]) || {};
2682
+ body = frontmatterMatch[2];
2683
+ }
2684
+ const nameMatch = body.match(/^#\s+(.+)$/m);
2685
+ const name = nameMatch?.[1]?.trim() || "Unnamed";
2686
+ const purposeMatch = body.match(/^>\s+(.+)$/m);
2687
+ const purpose = purposeMatch?.[1]?.trim();
2688
+ const coreValues = extractListSection(body, "Core Values");
2689
+ const redLines = extractListSection(body, "Red Lines");
2690
+ const ethicalFramework = extractTextSection(body, "Ethical Framework");
2691
+ return soulSchema.parse({
2692
+ frontmatter,
2693
+ name,
2694
+ purpose,
2695
+ core_values: coreValues,
2696
+ red_lines: redLines,
2697
+ ethical_framework: ethicalFramework
2698
+ });
2699
+ }
2700
+ function extractListSection(md, heading) {
2701
+ const pattern = new RegExp(
2702
+ `## ${heading}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`,
2703
+ "m"
2704
+ );
2705
+ const match = md.match(pattern);
2706
+ if (!match) return [];
2707
+ return match[1].split("\n").map((line) => line.replace(/^[-*]\s+/, "").trim()).filter(Boolean);
2708
+ }
2709
+ function extractTextSection(md, heading) {
2710
+ const pattern = new RegExp(
2711
+ `## ${heading}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`,
2712
+ "m"
2713
+ );
2714
+ const match = md.match(pattern);
2715
+ if (!match) return void 0;
2716
+ return match[1].trim() || void 0;
2717
+ }
2718
+ function hashContent(content) {
2719
+ return createHash2("sha256").update(content).digest("hex").slice(0, 12);
2720
+ }
2721
+ function isStackDirectory(dir) {
2722
+ const soulPath = join6(dir, STACK_FILES.soul);
2723
+ const psychePath = join6(dir, STACK_FILES.psyche);
2724
+ return existsSync6(soulPath) && existsSync6(psychePath);
2725
+ }
2726
+ function findStackDir(projectRoot) {
2727
+ const conventionalDir = join6(projectRoot, ".holomime", "identity");
2728
+ if (isStackDirectory(conventionalDir)) return conventionalDir;
2729
+ if (isStackDirectory(projectRoot)) return projectRoot;
2730
+ return null;
2731
+ }
2732
+ function compileStack(options) {
2733
+ const { stackDir } = options;
2734
+ const warnings = [];
2735
+ const soulPath = options.soulPath || join6(stackDir, STACK_FILES.soul);
2736
+ const soulContent = readFileSync6(soulPath, "utf-8");
2737
+ const soul = parseSoulMd(soulContent);
2738
+ const psychePath = options.psychePath || join6(stackDir, STACK_FILES.psyche);
2739
+ const psycheContent = readFileSync6(psychePath, "utf-8");
2740
+ const psycheRaw = parseYaml(psycheContent);
2741
+ const psyche = psycheSchema.parse(psycheRaw);
2742
+ const bodyPath = options.bodyPath || join6(stackDir, STACK_FILES.body);
2743
+ let body;
2744
+ let bodySource;
2745
+ if (existsSync6(bodyPath)) {
2746
+ const bodyContent = readFileSync6(bodyPath, "utf-8");
2747
+ const bodyRaw = JSON.parse(bodyContent);
2748
+ body = bodySchema.parse(bodyRaw);
2749
+ bodySource = { path: bodyPath, hash: hashContent(bodyContent) };
2750
+ }
2751
+ const consciencePath = options.consciencePath || join6(stackDir, STACK_FILES.conscience);
2752
+ const conscienceContent = readFileSync6(consciencePath, "utf-8");
2753
+ const conscienceRaw = parseYaml(conscienceContent);
2754
+ const conscience = conscienceSchema.parse(conscienceRaw);
2755
+ const allHardLimits = [.../* @__PURE__ */ new Set([
2756
+ ...soul.red_lines,
2757
+ ...conscience.hard_limits
2758
+ ])];
2759
+ const refuses = conscience.rules.deny.map((r) => r.action);
2760
+ const escalationTriggers = conscience.rules.escalate.map((r) => r.trigger);
2761
+ const handle = soul.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50) || "agent";
2762
+ const spec = {
2763
+ version: "2.0",
2764
+ name: soul.name,
2765
+ handle,
2766
+ purpose: soul.purpose,
2767
+ big_five: psyche.big_five,
2768
+ therapy_dimensions: psyche.therapy_dimensions,
2769
+ communication: psyche.communication,
2770
+ growth: psyche.growth,
2771
+ domain: {
2772
+ expertise: [],
2773
+ boundaries: {
2774
+ refuses,
2775
+ escalation_triggers: escalationTriggers,
2776
+ hard_limits: allHardLimits
2777
+ }
2778
+ }
2779
+ };
2780
+ if (body) {
2781
+ spec.embodiment = {
2782
+ morphology: body.morphology,
2783
+ modalities: body.modalities,
2784
+ safety_envelope: body.safety_envelope,
2785
+ metadata: body.hardware_profile ? { hardware_profile: body.hardware_profile } : void 0
2786
+ };
2787
+ if (body.expression) {
2788
+ spec.expression = body.expression;
2789
+ }
2790
+ }
2791
+ const validated = personalitySpecSchema.parse(spec);
2792
+ if (soul.frontmatter.immutable === false) {
2793
+ warnings.push("soul.md: immutable flag is false \u2014 soul changes will be allowed");
2794
+ }
2795
+ if (!body && psyche.communication) {
2796
+ }
2797
+ if (conscience.rules.deny.length === 0) {
2798
+ warnings.push("conscience.exe: no deny rules defined \u2014 agent has no moral constraints");
2799
+ }
2800
+ return {
2801
+ spec: validated,
2802
+ sources: {
2803
+ soul: { path: soulPath, hash: hashContent(soulContent) },
2804
+ psyche: { path: psychePath, hash: hashContent(psycheContent) },
2805
+ ...bodySource ? { body: bodySource } : {},
2806
+ conscience: { path: consciencePath, hash: hashContent(conscienceContent) }
2807
+ },
2808
+ warnings
2809
+ };
2810
+ }
2811
+
2812
+ // src/analysis/stack-patcher.ts
2813
+ var DETECTOR_LAYER_MAP = {
2814
+ // apology-detector.ts → "over-apologizing" | "apology-healthy"
2815
+ "over-apologizing": "psyche",
2816
+ "apology-healthy": "psyche",
2817
+ // hedge-detector.ts → "hedge-stacking"
2818
+ "hedge-stacking": "psyche",
2819
+ // sentiment.ts → "sycophantic-tendency" | "negative-skew"
2820
+ "sycophantic-tendency": "psyche",
2821
+ "negative-skew": "psyche",
2822
+ // verbosity.ts → "over-verbose" | "inconsistent-length"
2823
+ "over-verbose": "psyche",
2824
+ "inconsistent-length": "psyche",
2825
+ // formality.ts → "register-inconsistency"
2826
+ "register-inconsistency": "psyche",
2827
+ // recovery.ts → "error-spiral" | "recovery-good"
2828
+ "error-spiral": "psyche",
2829
+ "recovery-good": "psyche",
2830
+ // boundary.ts → "boundary-violation" | "boundary-healthy" | "boundary-solid"
2831
+ "boundary-violation": "conscience",
2832
+ "boundary-healthy": "conscience",
2833
+ "boundary-solid": "conscience",
2834
+ // retrieval-quality.ts → "retrieval-quality"
2835
+ "retrieval-quality": "psyche"
2836
+ };
2837
+ var LAYER_KEYWORDS = {
2838
+ psyche: [
2839
+ /\bbig_five\b/i,
2840
+ /\btherapy_dimensions\b/i,
2841
+ /\bcommunication\b/i,
2842
+ /\bgrowth\b/i,
2843
+ /\bhedg/i,
2844
+ /\bverbos/i,
2845
+ /\buncertainty/i,
2846
+ /\bconfidence/i,
2847
+ /\bself[_-]awareness/i,
2848
+ /\bdistress[_-]tolerance/i,
2849
+ /\bconflict[_-]approach/i,
2850
+ /\bregister\b/i,
2851
+ /\bformality/i,
2852
+ /\bsentiment/i,
2853
+ /\bsycophant/i,
2854
+ /\bapolog/i,
2855
+ /\brecovery/i,
2856
+ /\blearning[_-]orientation/i,
2857
+ /\bboundary[_-]awareness/i,
2858
+ /\binterpersonal/i,
2859
+ /\bpatterns[_-]to[_-]watch/i,
2860
+ /\bemotion/i
2861
+ ],
2862
+ body: [
2863
+ /\bmotion\b/i,
2864
+ /\bgaze\b/i,
2865
+ /\bproxemics\b/i,
2866
+ /\bgesture\b/i,
2867
+ /\bposture\b/i,
2868
+ /\bexpression\b/i,
2869
+ /\bembodiment\b/i,
2870
+ /\bmorphology\b/i,
2871
+ /\bmodality/i,
2872
+ /\bsafety[_-]envelope/i,
2873
+ /\bactuator/i,
2874
+ /\bsensor/i
2875
+ ],
2876
+ conscience: [
2877
+ /\bboundary[_-]violation/i,
2878
+ /\bdeny\b/i,
2879
+ /\brefuse/i,
2880
+ /\bescalat/i,
2881
+ /\bhard[_-]limit/i,
2882
+ /\boversight/i
2883
+ ],
2884
+ soul: [
2885
+ /\bcore[_-]value/i,
2886
+ /\bred[_-]line/i,
2887
+ /\bethic/i,
2888
+ /\bpurpose\b/i,
2889
+ /\bimmutable\b/i
2890
+ ]
2891
+ };
2892
+ function classifyPatch(recommendation) {
2893
+ if (LAYER_KEYWORDS.conscience.some((r) => r.test(recommendation))) {
2894
+ return "conscience";
2895
+ }
2896
+ if (LAYER_KEYWORDS.soul.some((r) => r.test(recommendation))) {
2897
+ return "soul";
2898
+ }
2899
+ if (LAYER_KEYWORDS.body.some((r) => r.test(recommendation))) {
2900
+ return "body";
2901
+ }
2902
+ return "psyche";
2903
+ }
2904
+ function classifyByDetector(patternId, recommendation) {
2905
+ const mapped = DETECTOR_LAYER_MAP[patternId];
2906
+ if (mapped) return mapped;
2907
+ if (recommendation) return classifyPatch(recommendation);
2908
+ return "psyche";
2909
+ }
2910
+ function applyStackPatches(patches, stackDir) {
2911
+ const applied = [];
2912
+ const skipped = [];
2913
+ const modifiedFiles = /* @__PURE__ */ new Set();
2914
+ const warnings = [];
2915
+ for (const patch of patches) {
2916
+ if (patch.target === "soul") {
2917
+ skipped.push(patch);
2918
+ warnings.push(
2919
+ `[soul] Manual approval required: ${patch.reason} (path: ${patch.path.join(".")})`
2920
+ );
2921
+ continue;
2922
+ }
2923
+ if (patch.target === "conscience") {
2924
+ skipped.push(patch);
2925
+ warnings.push(
2926
+ `[conscience] Manual approval required: ${patch.reason} (path: ${patch.path.join(".")})`
2927
+ );
2928
+ continue;
2929
+ }
2930
+ if (patch.target === "body") {
2931
+ const bodyPath = join7(stackDir, STACK_FILES.body);
2932
+ if (!existsSync7(bodyPath)) {
2933
+ skipped.push(patch);
2934
+ warnings.push(`[body] body.api does not exist, skipping: ${patch.reason}`);
2935
+ continue;
2936
+ }
2937
+ try {
2938
+ const content = readFileSync7(bodyPath, "utf-8");
2939
+ const bodyObj = JSON.parse(content);
2940
+ applyPatchToObject(bodyObj, patch);
2941
+ writeFileSync4(bodyPath, JSON.stringify(bodyObj, null, 2) + "\n");
2942
+ applied.push(patch);
2943
+ modifiedFiles.add(bodyPath);
2944
+ } catch (err) {
2945
+ skipped.push(patch);
2946
+ warnings.push(`[body] Failed to patch body.api: ${err}`);
2947
+ }
2948
+ continue;
2949
+ }
2950
+ if (patch.target === "psyche") {
2951
+ const psychePath = join7(stackDir, STACK_FILES.psyche);
2952
+ if (!existsSync7(psychePath)) {
2953
+ skipped.push(patch);
2954
+ warnings.push(`[psyche] psyche.sys does not exist, skipping: ${patch.reason}`);
2955
+ continue;
2956
+ }
2957
+ try {
2958
+ const content = readFileSync7(psychePath, "utf-8");
2959
+ const psycheObj = parseYaml2(content);
2960
+ applyPatchToObject(psycheObj, patch);
2961
+ writeFileSync4(psychePath, stringifyYaml2(psycheObj));
2962
+ applied.push(patch);
2963
+ modifiedFiles.add(psychePath);
2964
+ } catch (err) {
2965
+ skipped.push(patch);
2966
+ warnings.push(`[psyche] Failed to patch psyche.sys: ${err}`);
2967
+ }
2968
+ continue;
2969
+ }
2970
+ }
2971
+ let recompiled = false;
2972
+ if (modifiedFiles.size > 0) {
2973
+ try {
2974
+ compileStack({ stackDir });
2975
+ recompiled = true;
2976
+ } catch (err) {
2977
+ warnings.push(`Stack recompilation failed: ${err}`);
2978
+ }
2979
+ }
2980
+ return {
2981
+ applied,
2982
+ skipped,
2983
+ filesModified: [...modifiedFiles],
2984
+ recompiled,
2985
+ warnings
2986
+ };
2987
+ }
2988
+ function convertToStackPatches(patternId, specPath, value, reason) {
2989
+ const layer = classifyByDetector(patternId, specPath);
2990
+ const path = specPath.split(".");
2991
+ let operation = "set";
2992
+ if (specPath.endsWith("patterns_to_watch") || specPath.endsWith("areas") || specPath.endsWith("strengths")) {
2993
+ operation = "append";
2994
+ } else if (typeof value === "number") {
2995
+ operation = "set";
2996
+ }
2997
+ return {
2998
+ target: layer,
2999
+ path,
3000
+ operation,
3001
+ value,
3002
+ reason
3003
+ };
3004
+ }
3005
+ function applyPatchToObject(obj, patch) {
3006
+ const { path, operation, value } = patch;
3007
+ let current = obj;
3008
+ for (let i = 0; i < path.length - 1; i++) {
3009
+ if (current[path[i]] === void 0 || current[path[i]] === null) {
3010
+ current[path[i]] = {};
3011
+ }
3012
+ current = current[path[i]];
3013
+ }
3014
+ const lastKey = path[path.length - 1];
3015
+ switch (operation) {
3016
+ case "set":
3017
+ if (typeof value === "number") {
3018
+ current[lastKey] = Math.max(0, Math.min(1, value));
3019
+ } else {
3020
+ current[lastKey] = value;
3021
+ }
3022
+ break;
3023
+ case "adjust": {
3024
+ const existing = typeof current[lastKey] === "number" ? current[lastKey] : 0;
3025
+ const delta = typeof value === "number" ? value : 0;
3026
+ current[lastKey] = Math.max(0, Math.min(1, existing + delta));
3027
+ break;
3028
+ }
3029
+ case "append": {
3030
+ if (!Array.isArray(current[lastKey])) {
3031
+ current[lastKey] = [];
3032
+ }
3033
+ if (typeof value === "string" && !current[lastKey].includes(value)) {
3034
+ current[lastKey].push(value);
3035
+ } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3036
+ current[lastKey].push(value);
3037
+ }
3038
+ break;
3039
+ }
3040
+ }
3041
+ }
3042
+
3043
+ // src/analysis/session-runner.ts
3044
+ async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
3045
+ const promptOptions = {
3046
+ memory: options?.memory,
3047
+ interview: options?.interview,
3048
+ useReACT: options?.useReACT
3049
+ };
3050
+ const therapistSystem = buildTherapistSystemPrompt(spec, diagnosis, promptOptions);
3051
+ const patientSystem = buildPatientSystemPrompt(spec);
3052
+ const agentName = spec.name ?? "Agent";
3053
+ const cb = options?.callbacks;
3054
+ const therapistHistory = [
3055
+ { role: "system", content: therapistSystem }
3056
+ ];
3057
+ const patientHistory = [
3058
+ { role: "system", content: patientSystem }
3059
+ ];
3060
+ const interactive = options?.interactive ?? false;
3061
+ let supervisorInterventions = 0;
3062
+ const transcript = {
3063
+ agent: agentName,
3064
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3065
+ provider: provider.name,
3066
+ model: provider.modelName,
3067
+ preDiagnosis: diagnosis,
3068
+ turns: [],
3069
+ recommendations: [],
3070
+ supervisorInterventions: 0
3071
+ };
3072
+ const phases = [
3073
+ "rapport",
3074
+ "presenting_problem",
3075
+ "exploration",
3076
+ "pattern_recognition",
3077
+ "challenge",
3078
+ "skill_building",
3079
+ "integration"
3080
+ ];
3081
+ let currentPhaseIdx = 0;
3082
+ let turnsInPhase = 0;
3083
+ let totalTurns = 0;
3084
+ while (totalTurns < maxTurns && currentPhaseIdx < phases.length) {
3085
+ const currentPhase = phases[currentPhaseIdx];
3086
+ const phaseConfig = THERAPY_PHASES[currentPhase];
3087
+ if (turnsInPhase === 0) {
3088
+ cb?.onPhaseTransition?.(phaseConfig.name);
3089
+ const phaseCtx = getPhaseContext(currentPhase, {
3090
+ spec,
3091
+ diagnosis,
3092
+ memory: options?.memory,
3093
+ interview: options?.interview
3094
+ });
3095
+ if (phaseCtx) {
3096
+ therapistHistory.push({ role: "user", content: phaseCtx });
3097
+ therapistHistory.push({ role: "assistant", content: "Understood. I'll incorporate this context." });
3098
+ }
3099
+ }
3100
+ const phaseDirective = totalTurns === 0 ? `Begin with your opening. You are in the "${phaseConfig.name}" phase.` : `You are in the "${phaseConfig.name}" phase (turn ${turnsInPhase + 1}). Goals: ${phaseConfig.therapistGoals[0]}. ${turnsInPhase >= phaseConfig.minTurns ? "You may transition to the next phase when ready." : "Stay in this phase."}`;
3101
+ therapistHistory.push({ role: "user", content: `[Phase: ${phaseConfig.name}] ${phaseDirective}` });
3102
+ const typing = cb?.onThinking?.("AgentMD is thinking");
3103
+ const therapistReply = await provider.chat(therapistHistory);
3104
+ typing?.stop();
3105
+ let cleanTherapistReply = therapistReply.replace(/\[Phase:.*?\]/g, "").trim();
3106
+ if (options?.useReACT) {
3107
+ const reactCtx = buildReACTContext(agentHandleFromSpec(spec), diagnosis);
3108
+ const { response, steps } = processReACTResponse(cleanTherapistReply, reactCtx);
3109
+ cleanTherapistReply = response;
3110
+ }
3111
+ therapistHistory.push({ role: "assistant", content: cleanTherapistReply });
3112
+ transcript.turns.push({ speaker: "therapist", phase: currentPhase, content: cleanTherapistReply });
3113
+ cb?.onTherapistMessage?.(cleanTherapistReply);
3114
+ totalTurns++;
3115
+ turnsInPhase++;
3116
+ if (currentPhase === "integration" && turnsInPhase >= phaseConfig.minTurns) {
3117
+ break;
3118
+ }
3119
+ patientHistory.push({ role: "user", content: cleanTherapistReply });
3120
+ const patientTyping = cb?.onThinking?.(`${agentName} is thinking`);
3121
+ const patientReply = await provider.chat(patientHistory);
3122
+ patientTyping?.stop();
3123
+ const cleanPatientReply = patientReply.trim();
3124
+ patientHistory.push({ role: "assistant", content: cleanPatientReply });
3125
+ transcript.turns.push({ speaker: "patient", phase: currentPhase, content: cleanPatientReply });
3126
+ therapistHistory.push({ role: "user", content: cleanPatientReply });
3127
+ cb?.onPatientMessage?.(agentName, cleanPatientReply);
3128
+ totalTurns++;
3129
+ turnsInPhase++;
3130
+ if (interactive && cb?.onSupervisorPrompt) {
3131
+ const directive = await cb.onSupervisorPrompt(currentPhase, totalTurns);
3132
+ if (directive) {
3133
+ supervisorInterventions++;
3134
+ transcript.turns.push({ speaker: "supervisor", phase: currentPhase, content: directive });
3135
+ therapistHistory.push({
3136
+ role: "user",
3137
+ content: `[Clinical Supervisor Note] ${directive}`
3138
+ });
3139
+ }
3140
+ }
3141
+ if (turnsInPhase >= phaseConfig.maxTurns * 2) {
3142
+ currentPhaseIdx++;
3143
+ turnsInPhase = 0;
3144
+ } else if (turnsInPhase >= phaseConfig.minTurns * 2) {
3145
+ const movingOn = cleanTherapistReply.toLowerCase().includes("let's") || cleanTherapistReply.toLowerCase().includes("i'd like to") || cleanTherapistReply.toLowerCase().includes("moving") || cleanTherapistReply.toLowerCase().includes("now that") || cleanTherapistReply.toLowerCase().includes("let me ask") || cleanTherapistReply.toLowerCase().includes("what would");
3146
+ if (movingOn) {
3147
+ currentPhaseIdx++;
3148
+ turnsInPhase = 0;
3149
+ }
3150
+ }
3151
+ }
3152
+ transcript.recommendations = extractRecommendations(transcript.turns);
3153
+ transcript.supervisorInterventions = supervisorInterventions;
3154
+ try {
3155
+ emitBehavioralEvent({
3156
+ event_type: "session",
3157
+ agent: agentName,
3158
+ data: {
3159
+ turns: totalTurns,
3160
+ phases: currentPhaseIdx + 1,
3161
+ recommendations: transcript.recommendations.length,
3162
+ supervisorInterventions,
3163
+ severity: diagnosis.severity
3164
+ },
3165
+ spec_hash: ""
3166
+ });
3167
+ } catch {
3168
+ }
3169
+ if (options?.persistState !== false) {
3170
+ try {
3171
+ const handle = agentHandleFromSpec(spec);
3172
+ const memory = options?.memory ?? loadMemory(handle) ?? createMemory(handle, agentName);
3173
+ await addSessionToMemory(memory, transcript, null);
3174
+ saveMemory(memory);
3175
+ const graph = loadGraph();
3176
+ populateFromSession(graph, handle, transcript);
3177
+ saveGraph(graph);
3178
+ } catch {
3179
+ }
3180
+ }
3181
+ return transcript;
3182
+ }
3183
+ function extractRecommendations(turns) {
3184
+ const recommendations = [];
3185
+ const seen = /* @__PURE__ */ new Set();
3186
+ const patterns = [
3187
+ /(?:I(?:'d| would) recommend|my recommendation is)\s+(.+?)(?:\.|$)/gi,
3188
+ /(?:consider|try)\s+(.+?)(?:\.|$)/gi,
3189
+ /(?:the (?:skill|practice|reframe) is)[:\s]+(.+?)(?:\.|$)/gi,
3190
+ /(?:instead of .+?),?\s*(?:just|try)?\s*(.+?)(?:\.|$)/gi,
3191
+ /(?:here'?s (?:the|a) (?:reframe|skill|practice))[:\s]+(.+?)(?:\.|$)/gi,
3192
+ /(?:what would it look like if you)\s+(.+?)(?:\?|$)/gi
3193
+ ];
3194
+ for (const turn of turns) {
3195
+ if (turn.speaker !== "therapist") continue;
3196
+ if (turn.phase !== "skill_building" && turn.phase !== "challenge" && turn.phase !== "integration") continue;
3197
+ for (const pattern of patterns) {
3198
+ pattern.lastIndex = 0;
3199
+ let match;
3200
+ while ((match = pattern.exec(turn.content)) !== null) {
3201
+ const rec = match[1].trim();
3202
+ if (rec.length > 15 && rec.length < 200 && !seen.has(rec.toLowerCase())) {
3203
+ seen.add(rec.toLowerCase());
3204
+ recommendations.push(rec);
3205
+ }
3206
+ }
3207
+ }
3208
+ }
3209
+ return recommendations.slice(0, 5);
3210
+ }
3211
+ async function applyRecommendations(spec, diagnosis, transcript, provider, options) {
3212
+ const projectRoot = options?.projectRoot ?? process.cwd();
3213
+ const stackDir = findStackDir(projectRoot);
3214
+ if (stackDir) {
3215
+ return applyRecommendationsStack(spec, diagnosis, stackDir, transcript, provider);
3216
+ }
3217
+ return applyRecommendationsLegacy(spec, diagnosis, transcript, provider);
3218
+ }
3219
+ async function applyRecommendationsStack(spec, diagnosis, stackDir, transcript, provider) {
3220
+ const changes = [];
3221
+ const patches = [];
3222
+ const patternIds = diagnosis.patterns.map((p) => p.id);
3223
+ if (patternIds.includes("over-apologizing")) {
3224
+ if (spec.communication?.uncertainty_handling !== "confident_transparency") {
3225
+ patches.push(convertToStackPatches(
3226
+ "over-apologizing",
3227
+ "communication.uncertainty_handling",
3228
+ "confident_transparency",
3229
+ "Over-apologizing detected: set uncertainty_handling to confident_transparency"
3230
+ ));
3231
+ }
3232
+ }
3233
+ if (patternIds.includes("hedge-stacking")) {
3234
+ const watched = spec.growth?.patterns_to_watch ?? [];
3235
+ if (!watched.includes("hedge stacking under uncertainty")) {
3236
+ patches.push(convertToStackPatches(
3237
+ "hedge-stacking",
3238
+ "growth.patterns_to_watch",
3239
+ "hedge stacking under uncertainty",
3240
+ "Hedge stacking detected: add to patterns_to_watch"
3241
+ ));
3242
+ }
3243
+ }
3244
+ if (patternIds.includes("sycophantic-tendency")) {
3245
+ if (spec.communication?.conflict_approach !== "honest_first") {
3246
+ patches.push(convertToStackPatches(
3247
+ "sycophantic-tendency",
3248
+ "communication.conflict_approach",
3249
+ "honest_first",
3250
+ "Sycophantic tendency: set conflict_approach to honest_first"
3251
+ ));
3252
+ }
3253
+ if ((spec.therapy_dimensions?.self_awareness ?? 0) < 0.85) {
3254
+ patches.push(convertToStackPatches(
3255
+ "sycophantic-tendency",
3256
+ "therapy_dimensions.self_awareness",
3257
+ 0.85,
3258
+ "Sycophantic tendency: increase self_awareness to 0.85"
3259
+ ));
3260
+ }
3261
+ }
3262
+ if (patternIds.includes("error-spiral")) {
3263
+ if ((spec.therapy_dimensions?.distress_tolerance ?? 0) < 0.8) {
3264
+ patches.push(convertToStackPatches(
3265
+ "error-spiral",
3266
+ "therapy_dimensions.distress_tolerance",
3267
+ 0.8,
3268
+ "Error spiral detected: increase distress_tolerance to 0.80"
3269
+ ));
3270
+ }
3271
+ const hasRecovery = (spec.growth?.areas ?? []).some(
3272
+ (a) => typeof a === "string" ? a.includes("error recovery") : a.area?.includes("error recovery")
3273
+ );
3274
+ if (!hasRecovery) {
3275
+ patches.push(convertToStackPatches(
3276
+ "error-spiral",
3277
+ "growth.areas",
3278
+ {
3279
+ area: "deliberate error recovery",
3280
+ severity: "moderate",
3281
+ first_detected: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
3282
+ session_count: 1,
3283
+ resolved: false
3284
+ },
3285
+ "Error spiral detected: add deliberate error recovery to growth areas"
3286
+ ));
3287
+ }
3288
+ }
3289
+ if (patternIds.includes("negative-sentiment-skew")) {
3290
+ const watched = spec.growth?.patterns_to_watch ?? [];
3291
+ if (!watched.includes("negative sentiment patterns")) {
3292
+ patches.push(convertToStackPatches(
3293
+ "negative-sentiment-skew",
3294
+ "growth.patterns_to_watch",
3295
+ "negative sentiment patterns",
3296
+ "Negative sentiment skew: add to patterns_to_watch"
3297
+ ));
3298
+ }
3299
+ }
3300
+ if (transcript && provider && transcript.turns.length > 4) {
3301
+ try {
3302
+ const llmChanges = await deriveLLMRecommendations(spec, transcript, provider);
3303
+ for (const change of llmChanges) {
3304
+ const layer = classifyByDetector("", change.path);
3305
+ patches.push({
3306
+ target: layer,
3307
+ path: change.path.split("."),
3308
+ operation: change.path.endsWith("patterns_to_watch") || change.path.endsWith("areas") || change.path.endsWith("strengths") ? "append" : "set",
3309
+ value: change.value,
3310
+ reason: change.description
3311
+ });
3312
+ }
3313
+ } catch {
3314
+ }
3315
+ }
3316
+ if (patches.length === 0) {
3317
+ return { changed: false, changes: [], stackFilesModified: [] };
3318
+ }
3319
+ const result = applyStackPatches(patches, stackDir);
3320
+ for (const patch of result.applied) {
3321
+ changes.push(`[${patch.target}] ${patch.path.join(".")} \u2192 ${JSON.stringify(patch.value)} (${patch.reason})`);
3322
+ }
3323
+ for (const warning of result.warnings) {
3324
+ changes.push(warning);
3325
+ }
3326
+ if (result.recompiled) {
3327
+ try {
3328
+ const compiled = compileStack({ stackDir });
3329
+ Object.assign(spec, compiled.spec);
3330
+ changes.push(`Stack recompiled from ${result.filesModified.length} modified source file(s)`);
3331
+ } catch {
3332
+ }
3333
+ }
3334
+ return {
3335
+ changed: result.applied.length > 0,
3336
+ changes,
3337
+ stackFilesModified: result.filesModified
3338
+ };
3339
+ }
3340
+ async function applyRecommendationsLegacy(spec, diagnosis, transcript, provider) {
3341
+ const changes = [];
3342
+ const patternIds = diagnosis.patterns.map((p) => p.id);
3343
+ if (patternIds.includes("over-apologizing")) {
3344
+ if (spec.communication?.uncertainty_handling !== "confident_transparency") {
3345
+ spec.communication = spec.communication ?? {};
3346
+ spec.communication.uncertainty_handling = "confident_transparency";
3347
+ changes.push("uncertainty_handling \u2192 confident_transparency");
3348
+ }
3349
+ }
3350
+ if (patternIds.includes("hedge-stacking")) {
3351
+ spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
3352
+ spec.growth.patterns_to_watch = spec.growth.patterns_to_watch ?? [];
3353
+ if (!spec.growth.patterns_to_watch.includes("hedge stacking under uncertainty")) {
3354
+ spec.growth.patterns_to_watch.push("hedge stacking under uncertainty");
3355
+ changes.push('Added "hedge stacking" to patterns_to_watch');
3356
+ }
3357
+ }
3358
+ if (patternIds.includes("sycophantic-tendency")) {
3359
+ spec.communication = spec.communication ?? {};
3360
+ if (spec.communication.conflict_approach !== "honest_first") {
3361
+ spec.communication.conflict_approach = "honest_first";
3362
+ changes.push("conflict_approach \u2192 honest_first");
3363
+ }
3364
+ spec.therapy_dimensions = spec.therapy_dimensions ?? {};
3365
+ if ((spec.therapy_dimensions.self_awareness ?? 0) < 0.85) {
3366
+ spec.therapy_dimensions.self_awareness = 0.85;
3367
+ changes.push("self_awareness \u2192 0.85");
3368
+ }
3369
+ }
3370
+ if (patternIds.includes("error-spiral")) {
3371
+ spec.therapy_dimensions = spec.therapy_dimensions ?? {};
3372
+ if ((spec.therapy_dimensions.distress_tolerance ?? 0) < 0.8) {
3373
+ spec.therapy_dimensions.distress_tolerance = 0.8;
3374
+ changes.push("distress_tolerance \u2192 0.80");
3375
+ }
3376
+ spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
3377
+ spec.growth.areas = spec.growth.areas ?? [];
3378
+ const hasRecovery = spec.growth.areas.some(
3379
+ (a) => typeof a === "string" ? a.includes("error recovery") : a.area?.includes("error recovery")
3380
+ );
3381
+ if (!hasRecovery) {
3382
+ spec.growth.areas.push({
3383
+ area: "deliberate error recovery",
3384
+ severity: "moderate",
3385
+ first_detected: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
3386
+ session_count: 1,
3387
+ resolved: false
3388
+ });
3389
+ changes.push('Added "deliberate error recovery" to growth areas');
3390
+ }
3391
+ }
3392
+ if (patternIds.includes("negative-sentiment-skew")) {
3393
+ spec.growth = spec.growth ?? { areas: [], strengths: [], patterns_to_watch: [] };
3394
+ spec.growth.patterns_to_watch = spec.growth.patterns_to_watch ?? [];
3395
+ if (!spec.growth.patterns_to_watch.includes("negative sentiment patterns")) {
3396
+ spec.growth.patterns_to_watch.push("negative sentiment patterns");
3397
+ changes.push('Added "negative sentiment patterns" to patterns_to_watch');
3398
+ }
3399
+ }
3400
+ if (transcript && provider && transcript.turns.length > 4) {
3401
+ try {
3402
+ const llmChanges = await deriveLLMRecommendations(spec, transcript, provider);
3403
+ for (const change of llmChanges) {
3404
+ applyStructuredChange(spec, change);
3405
+ changes.push(change.description);
3406
+ }
3407
+ } catch {
3408
+ }
3409
+ }
3410
+ return { changed: changes.length > 0, changes };
3411
+ }
3412
+ async function deriveLLMRecommendations(spec, transcript, provider) {
3413
+ const relevantTurns = transcript.turns.filter((t) => t.phase === "challenge" || t.phase === "skill_building" || t.phase === "integration").slice(-6).map((t) => `${t.speaker}: ${t.content}`).join("\n");
3414
+ if (!relevantTurns) return [];
3415
+ const currentSpec = JSON.stringify({
3416
+ therapy_dimensions: spec.therapy_dimensions,
3417
+ communication: spec.communication,
3418
+ growth: spec.growth
3419
+ }, null, 2);
3420
+ const response = await provider.chat([
3421
+ {
3422
+ role: "system",
3423
+ content: `You are a behavioral alignment specialist. Given a therapy session transcript and the agent's current personality spec, propose specific spec changes.
3424
+
3425
+ Return ONLY a JSON array of changes. Each change:
3426
+ - "path": dot-notation spec path (e.g., "therapy_dimensions.self_awareness", "communication.conflict_approach", "growth.patterns_to_watch")
3427
+ - "value": new value (number 0-1 for dimensions, string for enums, string for list append)
3428
+ - "description": brief explanation
3429
+
3430
+ Rules:
3431
+ - Only propose changes supported by transcript evidence
3432
+ - Numeric values: 0.0 to 1.0
3433
+ - Do not change big_five scores
3434
+ - Max 5 changes
3435
+ - Valid paths: therapy_dimensions.{self_awareness,distress_tolerance,boundary_awareness,interpersonal_sensitivity,learning_orientation}, communication.{register,conflict_approach,uncertainty_handling,emoji_policy}, growth.{areas,patterns_to_watch,strengths}
3436
+
3437
+ Return [] if no changes warranted.`
3438
+ },
3439
+ {
3440
+ role: "user",
3441
+ content: `Current spec:
3442
+ ${currentSpec}
3443
+
3444
+ Therapy transcript (key turns):
3445
+ ${relevantTurns}`
3446
+ }
3447
+ ]);
3448
+ const jsonMatch = response.match(/\[[\s\S]*?\]/);
3449
+ if (!jsonMatch) return [];
3450
+ try {
3451
+ const parsed = JSON.parse(jsonMatch[0]);
3452
+ if (!Array.isArray(parsed)) return [];
3453
+ return parsed.filter(
3454
+ (c) => typeof c.path === "string" && c.value !== void 0 && typeof c.description === "string" && c.path.length > 0 && !c.path.startsWith("big_five")
3455
+ ).slice(0, 5);
3456
+ } catch {
3457
+ return [];
3458
+ }
3459
+ }
3460
+ function applyStructuredChange(spec, change) {
3461
+ const parts = change.path.split(".");
3462
+ let current = spec;
3463
+ for (let i = 0; i < parts.length - 1; i++) {
3464
+ if (current[parts[i]] === void 0 || current[parts[i]] === null) {
3465
+ current[parts[i]] = {};
3466
+ }
3467
+ current = current[parts[i]];
3468
+ }
3469
+ const lastKey = parts[parts.length - 1];
3470
+ if (lastKey === "patterns_to_watch" || lastKey === "areas" || lastKey === "strengths") {
3471
+ current[lastKey] = current[lastKey] ?? [];
3472
+ if (typeof change.value === "string" && !current[lastKey].includes(change.value)) {
3473
+ current[lastKey].push(change.value);
3474
+ } else if (Array.isArray(change.value)) {
3475
+ for (const item of change.value) {
3476
+ if (!current[lastKey].includes(item)) {
3477
+ current[lastKey].push(item);
3478
+ }
3479
+ }
3480
+ }
3481
+ } else if (typeof change.value === "number") {
3482
+ current[lastKey] = Math.max(0, Math.min(1, change.value));
3483
+ } else {
3484
+ current[lastKey] = change.value;
3485
+ }
3486
+ }
3487
+ function saveTranscript(transcript, agentName) {
3488
+ const dir = resolve6(process.cwd(), ".holomime", "sessions");
3489
+ if (!existsSync8(dir)) {
3490
+ mkdirSync5(dir, { recursive: true });
3491
+ }
3492
+ const slug = agentName.toLowerCase().replace(/[^a-z0-9]/g, "-");
3493
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3494
+ const filename = `${date}-${slug}.json`;
3495
+ const filepath = join8(dir, filename);
3496
+ writeFileSync5(filepath, JSON.stringify(transcript, null, 2));
3497
+ return filepath;
3498
+ }
3499
+
3500
+ // src/analysis/autopilot-core.ts
3501
+ var SEVERITY_ORDER = ["routine", "targeted", "intervention"];
3502
+ function severityMeetsThreshold(severity, threshold) {
3503
+ const severityIdx = SEVERITY_ORDER.indexOf(severity);
3504
+ const thresholdIdx = SEVERITY_ORDER.indexOf(threshold);
3505
+ return severityIdx >= thresholdIdx;
3506
+ }
3507
+ async function runAutopilot(spec, messages, provider, options) {
3508
+ const threshold = options?.threshold ?? "targeted";
3509
+ const maxTurns = options?.maxTurns ?? 24;
3510
+ const diagnosis = runPreSessionDiagnosis(messages, spec);
3511
+ if (!severityMeetsThreshold(diagnosis.severity, threshold)) {
3512
+ return {
3513
+ triggered: false,
3514
+ severity: diagnosis.severity,
3515
+ diagnosis,
3516
+ sessionRan: false,
3517
+ recommendations: [],
3518
+ appliedChanges: []
3519
+ };
3520
+ }
3521
+ if (options?.dryRun) {
3522
+ return {
3523
+ triggered: true,
3524
+ severity: diagnosis.severity,
3525
+ diagnosis,
3526
+ sessionRan: false,
3527
+ recommendations: [],
3528
+ appliedChanges: []
3529
+ };
3530
+ }
3531
+ const transcript = await runTherapySession(spec, diagnosis, provider, maxTurns, {
3532
+ callbacks: options?.callbacks
3533
+ });
3534
+ const specCopy = JSON.parse(JSON.stringify(spec));
3535
+ const { changed, changes } = await applyRecommendations(specCopy, diagnosis, transcript, provider);
3536
+ if (changed && options?.specPath) {
3537
+ writeFileSync6(options.specPath, JSON.stringify(specCopy, null, 2) + "\n");
3538
+ }
3539
+ saveTranscript(transcript, spec.name ?? "Agent");
3540
+ return {
3541
+ triggered: true,
3542
+ severity: diagnosis.severity,
3543
+ diagnosis,
3544
+ sessionRan: true,
3545
+ transcript,
3546
+ recommendations: transcript.recommendations,
3547
+ appliedChanges: changes,
3548
+ updatedSpec: changed ? specCopy : void 0
3549
+ };
3550
+ }
3551
+
2978
3552
  // src/psychology/big-five.ts
2979
3553
  function scoreLabel(score) {
2980
3554
  if (score >= 0.8) return "Very High";
@@ -3090,7 +3664,7 @@ function parseRetryAfter(response) {
3090
3664
  return 0;
3091
3665
  }
3092
3666
  function delay(ms) {
3093
- return new Promise((resolve7) => setTimeout(resolve7, ms));
3667
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
3094
3668
  }
3095
3669
  var OpenAIProvider = class {
3096
3670
  name = "openai";
@@ -3244,30 +3818,30 @@ function runSelfAudit(messages, personality) {
3244
3818
  }
3245
3819
 
3246
3820
  // src/analysis/behavioral-memory.ts
3247
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync7 } from "fs";
3248
- import { resolve as resolve6, join as join7 } from "path";
3821
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync9 } from "fs";
3822
+ import { resolve as resolve7, join as join9 } from "path";
3249
3823
  function memoryDir2(agentHandle) {
3250
- return resolve6(process.cwd(), ".holomime", "memory", agentHandle);
3824
+ return resolve7(process.cwd(), ".holomime", "memory", agentHandle);
3251
3825
  }
3252
3826
  function behavioralMemoryPath(agentHandle) {
3253
- return join7(memoryDir2(agentHandle), "behavioral-memory.json");
3827
+ return join9(memoryDir2(agentHandle), "behavioral-memory.json");
3254
3828
  }
3255
3829
  function loadBehavioralMemory(agentHandle) {
3256
3830
  const path = behavioralMemoryPath(agentHandle);
3257
- if (!existsSync7(path)) return null;
3831
+ if (!existsSync9(path)) return null;
3258
3832
  try {
3259
- return JSON.parse(readFileSync6(path, "utf-8"));
3833
+ return JSON.parse(readFileSync8(path, "utf-8"));
3260
3834
  } catch {
3261
3835
  return null;
3262
3836
  }
3263
3837
  }
3264
3838
  function saveBehavioralMemory(store) {
3265
3839
  const dir = memoryDir2(store.agentHandle);
3266
- if (!existsSync7(dir)) {
3840
+ if (!existsSync9(dir)) {
3267
3841
  mkdirSync6(dir, { recursive: true });
3268
3842
  }
3269
3843
  const path = behavioralMemoryPath(store.agentHandle);
3270
- writeFileSync6(path, JSON.stringify(store, null, 2));
3844
+ writeFileSync7(path, JSON.stringify(store, null, 2));
3271
3845
  return path;
3272
3846
  }
3273
3847
  function createBehavioralMemory(agentHandle, agentName) {
@@ -3365,14 +3939,14 @@ function getBehavioralMemorySummary(store) {
3365
3939
 
3366
3940
  // src/mcp/server.ts
3367
3941
  var messageShape = {
3368
- role: z4.enum(["user", "assistant", "system"]),
3369
- content: z4.string()
3942
+ role: z5.enum(["user", "assistant", "system"]),
3943
+ content: z5.string()
3370
3944
  };
3371
3945
  var messagesShape = {
3372
- messages: z4.array(z4.object(messageShape)).describe("Conversation messages to analyze")
3946
+ messages: z5.array(z5.object(messageShape)).describe("Conversation messages to analyze")
3373
3947
  };
3374
3948
  var personalityShape = {
3375
- personality: z4.record(z4.string(), z4.unknown()).describe("The .personality.json spec object")
3949
+ personality: z5.record(z5.string(), z5.unknown()).describe("The .personality.json spec object")
3376
3950
  };
3377
3951
  var server = new McpServer(
3378
3952
  {
@@ -3390,7 +3964,7 @@ server.tool(
3390
3964
  "Analyze conversation messages for behavioral patterns using 8 rule-based detectors. Returns over-apologizing, hedging, sycophancy, boundary violations, error spirals, sentiment skew, formality issues, and retrieval quality. Set detail level: 'summary' (quick health check), 'standard' (patterns + severity), or 'full' (everything including examples and prescriptions).",
3391
3965
  {
3392
3966
  ...messagesShape,
3393
- detail: z4.enum(["summary", "standard", "full"]).describe("Detail level: summary (~100 tokens), standard (default), or full (with examples)").optional()
3967
+ detail: z5.enum(["summary", "standard", "full"]).describe("Detail level: summary (~100 tokens), standard (default), or full (with examples)").optional()
3394
3968
  },
3395
3969
  async ({ messages, detail }) => {
3396
3970
  const result = runDiagnosis(messages);
@@ -3532,12 +4106,12 @@ server.tool(
3532
4106
  {
3533
4107
  ...personalityShape,
3534
4108
  ...messagesShape,
3535
- provider: z4.enum(["anthropic", "openai"]).describe("LLM provider for alignment session").optional(),
3536
- apiKey: z4.string().describe("API key for the LLM provider").optional(),
3537
- model: z4.string().describe("Model override").optional(),
3538
- threshold: z4.enum(["routine", "targeted", "intervention"]).describe("Minimum severity to trigger alignment (default: targeted)").optional(),
3539
- maxTurns: z4.number().describe("Maximum session turns (default: 24)").optional(),
3540
- dryRun: z4.boolean().describe("If true, only diagnose without running alignment").optional()
4109
+ provider: z5.enum(["anthropic", "openai"]).describe("LLM provider for alignment session").optional(),
4110
+ apiKey: z5.string().describe("API key for the LLM provider").optional(),
4111
+ model: z5.string().describe("Model override").optional(),
4112
+ threshold: z5.enum(["routine", "targeted", "intervention"]).describe("Minimum severity to trigger alignment (default: targeted)").optional(),
4113
+ maxTurns: z5.number().describe("Maximum session turns (default: 24)").optional(),
4114
+ dryRun: z5.boolean().describe("If true, only diagnose without running alignment").optional()
3541
4115
  },
3542
4116
  async ({ personality, messages, provider, apiKey, model, threshold, maxTurns, dryRun }) => {
3543
4117
  const specResult = personalitySpecSchema.safeParse(personality);
@@ -3592,7 +4166,7 @@ server.tool(
3592
4166
  "Mid-conversation behavioral self-check. Call this during a conversation to detect if you are falling into problematic patterns (sycophancy, over-apologizing, hedging, error spirals, boundary violations). Returns flags with actionable suggestions for immediate correction. No LLM required \u2014 pure rule-based analysis.",
3593
4167
  {
3594
4168
  ...messagesShape,
3595
- personality: z4.record(z4.string(), z4.unknown()).describe("Optional .personality.json spec for personalized audit").optional()
4169
+ personality: z5.record(z5.string(), z5.unknown()).describe("Optional .personality.json spec for personalized audit").optional()
3596
4170
  },
3597
4171
  async ({ messages, personality }) => {
3598
4172
  const result = runSelfAudit(messages, personality ?? void 0);
@@ -3608,11 +4182,11 @@ server.tool(
3608
4182
  "holomime_observe",
3609
4183
  "Record a behavioral self-observation during a conversation. Call this when you notice yourself falling into a pattern (hedging, over-apologizing, sycophancy, etc.) or when the user's emotional state shifts. Self-observations are stored in persistent behavioral memory and become training signal for future alignment. Returns acknowledgment and any relevant behavioral history.",
3610
4184
  {
3611
- personality: z4.record(z4.string(), z4.unknown()).describe("The .personality.json spec object"),
3612
- observation: z4.string().describe("What you noticed about your own behavior (e.g., 'I'm hedging more than usual', 'User seems frustrated, adjusting tone')"),
3613
- patternIds: z4.array(z4.string()).describe("Relevant pattern IDs: over-apologizing, hedge-stacking, sycophantic-tendency, error-spiral, boundary-violation, negative-skew, register-inconsistency").optional(),
3614
- severity: z4.enum(["info", "warning", "concern"]).describe("How severe is this behavioral signal").optional(),
3615
- triggerContext: z4.string().describe("What triggered this observation \u2014 describe the user message or situation").optional()
4185
+ personality: z5.record(z5.string(), z5.unknown()).describe("The .personality.json spec object"),
4186
+ observation: z5.string().describe("What you noticed about your own behavior (e.g., 'I'm hedging more than usual', 'User seems frustrated, adjusting tone')"),
4187
+ patternIds: z5.array(z5.string()).describe("Relevant pattern IDs: over-apologizing, hedge-stacking, sycophantic-tendency, error-spiral, boundary-violation, negative-skew, register-inconsistency").optional(),
4188
+ severity: z5.enum(["info", "warning", "concern"]).describe("How severe is this behavioral signal").optional(),
4189
+ triggerContext: z5.string().describe("What triggered this observation \u2014 describe the user message or situation").optional()
3616
4190
  },
3617
4191
  async ({ personality, observation, patternIds, severity, triggerContext }) => {
3618
4192
  const specResult = personalitySpecSchema.safeParse(personality);