holomime 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -31,10 +31,10 @@ function printHeader(title) {
31
31
  // src/ui/tier.ts
32
32
  import chalk2 from "chalk";
33
33
  import boxen from "boxen";
34
- import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdirSync } from "fs";
34
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
35
35
  import { join } from "path";
36
36
  import { homedir } from "os";
37
- var PRO_COMMANDS = ["session", "growth", "autopilot", "export", "train", "eval", "evolve", "benchmark", "watch", "certify", "daemon", "fleet"];
37
+ var PRO_COMMANDS = ["session", "growth", "autopilot", "export", "train", "eval", "evolve", "benchmark", "watch", "certify", "daemon", "fleet", "network", "share", "prescribe"];
38
38
  function requiresPro(command) {
39
39
  return PRO_COMMANDS.includes(command);
40
40
  }
@@ -46,7 +46,7 @@ function readCache() {
46
46
  try {
47
47
  const cachePath = getCachePath();
48
48
  if (!existsSync(cachePath)) return null;
49
- const raw = JSON.parse(readFileSync2(cachePath, "utf-8"));
49
+ const raw = JSON.parse(readFileSync(cachePath, "utf-8"));
50
50
  const age = Date.now() - new Date(raw.checkedAt).getTime();
51
51
  if (age > CACHE_TTL_MS) return null;
52
52
  return raw;
@@ -89,7 +89,7 @@ function readLicenseKey() {
89
89
  try {
90
90
  const licensePath = join(homedir(), ".holomime", "license");
91
91
  if (existsSync(licensePath)) {
92
- const token = readFileSync2(licensePath, "utf-8").trim();
92
+ const token = readFileSync(licensePath, "utf-8").trim();
93
93
  if (token.length > 0) return token;
94
94
  }
95
95
  } catch {
@@ -1743,48 +1743,214 @@ import { writeFileSync as writeFileSync3 } from "fs";
1743
1743
  import { resolve as resolve3 } from "path";
1744
1744
 
1745
1745
  // src/core/types.ts
1746
+ import { z as z2 } from "zod";
1747
+
1748
+ // src/core/embodiment-types.ts
1746
1749
  import { z } from "zod";
1747
- var bigFiveDimensionSchema = z.enum([
1750
+ var modalitySchema = z.enum([
1751
+ "gesture",
1752
+ // arm/hand movement
1753
+ "locomotion",
1754
+ // walking, wheeling, navigating
1755
+ "gaze",
1756
+ // eye tracking, head orientation
1757
+ "facial",
1758
+ // facial expression actuators
1759
+ "voice",
1760
+ // prosody, volume, rate (not content)
1761
+ "haptic",
1762
+ // touch-based interaction
1763
+ "posture",
1764
+ // full-body orientation/lean
1765
+ "manipulation"
1766
+ // grasping, carrying, tool use
1767
+ ]);
1768
+ var morphologySchema = z.enum([
1769
+ "humanoid",
1770
+ // bipedal, two arms, head
1771
+ "humanoid_upper",
1772
+ // torso-up only (desk robot, mounted)
1773
+ "quadruped",
1774
+ // four-legged
1775
+ "wheeled",
1776
+ // mobile base with upper body
1777
+ "fixed",
1778
+ // stationary (kiosk, screen + arm)
1779
+ "swarm_unit",
1780
+ // one node of a multi-body system
1781
+ "avatar",
1782
+ // virtual 3D body (no physical actuators)
1783
+ "custom"
1784
+ // user-defined
1785
+ ]);
1786
+ var safetyEnvelopeSchema = z.object({
1787
+ max_linear_speed_m_s: z.number().min(0).default(1.5),
1788
+ max_angular_speed_rad_s: z.number().min(0).default(2),
1789
+ min_proximity_m: z.number().min(0).default(0.3),
1790
+ max_contact_force_n: z.number().min(0).default(10),
1791
+ emergency_stop_decel_m_s2: z.number().min(0).default(5),
1792
+ max_reach_m: z.number().min(0).optional(),
1793
+ operating_temperature_c: z.tuple([z.number(), z.number()]).optional()
1794
+ });
1795
+ var embodimentSchema = z.object({
1796
+ morphology: morphologySchema.default("humanoid"),
1797
+ modalities: z.array(modalitySchema).default(["gesture", "gaze", "voice", "posture"]),
1798
+ safety_envelope: safetyEnvelopeSchema.default({}),
1799
+ metadata: z.record(z.string(), z.unknown()).optional()
1800
+ });
1801
+ var gazePolicySchema = z.object({
1802
+ contact_ratio: z.number().min(0).max(1).default(0.6),
1803
+ aversion_style: z.enum(["look_down", "look_away", "blink"]).default("look_away"),
1804
+ tracking_mode: z.enum(["face", "speaker", "gesture_follow", "ambient"]).default("face")
1805
+ });
1806
+ var proxemicZoneSchema = z.object({
1807
+ intimate_m: z.number().min(0).default(0.45),
1808
+ personal_m: z.number().min(0).default(1.2),
1809
+ social_m: z.number().min(0).default(3.6),
1810
+ preferred_zone: z.enum(["intimate", "personal", "social", "adaptive"]).default("personal")
1811
+ });
1812
+ var hapticPolicySchema = z.object({
1813
+ touch_permitted: z.boolean().default(false),
1814
+ requires_consent: z.boolean().default(true),
1815
+ allowed_contacts: z.array(z.enum([
1816
+ "handshake",
1817
+ "shoulder_tap",
1818
+ "high_five",
1819
+ "guide_touch",
1820
+ "none"
1821
+ ])).default(["none"]),
1822
+ max_force_n: z.number().min(0).optional()
1823
+ });
1824
+ var prosodySchema = z.object({
1825
+ base_pitch_hz: z.number().optional(),
1826
+ pitch_variation: z.number().min(0).max(1).default(0.5),
1827
+ speaking_rate_wpm: z.number().default(150),
1828
+ volume_db_offset: z.number().default(0),
1829
+ pause_tendency: z.number().min(0).max(1).default(0.5)
1830
+ });
1831
+ var gestureSchema = z.object({
1832
+ id: z.string(),
1833
+ category: z.enum(["conversational", "emphatic", "deictic", "regulatory", "adaptive"]),
1834
+ modalities: z.array(modalitySchema),
1835
+ intensity_range: z.tuple([
1836
+ z.number().min(0).max(1),
1837
+ z.number().min(0).max(1)
1838
+ ]).default([0.3, 0.8]),
1839
+ requires_consent: z.boolean().default(false)
1840
+ });
1841
+ var expressionSchema = z.object({
1842
+ gesture_vocabulary: z.array(gestureSchema).default([]),
1843
+ gaze: gazePolicySchema.default({}),
1844
+ proxemics: proxemicZoneSchema.default({}),
1845
+ haptics: hapticPolicySchema.default({}),
1846
+ prosody: prosodySchema.default({}),
1847
+ facial_expressiveness: z.number().min(0).max(1).default(0.5)
1848
+ });
1849
+ var physicalSafetySchema = z.object({
1850
+ hard_limits: z.array(z.string()).default([
1851
+ "Never exceed safety_envelope speeds",
1852
+ "Never exceed max_contact_force_n",
1853
+ "Emergency stop on unrecognized obstacle within min_proximity_m"
1854
+ ]),
1855
+ require_consent_for: z.array(z.string()).default([
1856
+ "haptic_contact",
1857
+ "intimate_zone_entry"
1858
+ ]),
1859
+ collision_response: z.enum(["stop", "retreat", "freeze"]).default("stop"),
1860
+ unattended_policy: z.enum(["idle", "return_home", "shutdown"]).default("idle")
1861
+ });
1862
+ var motionParametersSchema = z.object({
1863
+ // Speeds (normalized 0-1, scaled by safety_envelope at runtime)
1864
+ base_speed: z.number().min(0).max(1),
1865
+ gesture_speed: z.number().min(0).max(1),
1866
+ gesture_amplitude: z.number().min(0).max(1),
1867
+ gesture_frequency: z.number().min(0).max(1),
1868
+ // Spatial
1869
+ approach_distance: z.number().min(0).max(1),
1870
+ spatial_exploration: z.number().min(0).max(1),
1871
+ // Smoothness
1872
+ movement_smoothness: z.number().min(0).max(1),
1873
+ trajectory_variability: z.number().min(0).max(1),
1874
+ // Timing
1875
+ response_latency: z.number().min(0).max(1),
1876
+ idle_animation_frequency: z.number().min(0).max(1),
1877
+ // Social
1878
+ gaze_contact_ratio: z.number().min(0).max(1),
1879
+ head_tilt_tendency: z.number().min(0).max(1),
1880
+ postural_openness: z.number().min(0).max(1),
1881
+ smile_frequency: z.number().min(0).max(1),
1882
+ // Voice prosody
1883
+ voice_volume: z.number().min(0).max(1),
1884
+ speaking_rate: z.number().min(0).max(1),
1885
+ pitch_variation: z.number().min(0).max(1),
1886
+ pause_duration: z.number().min(0).max(1)
1887
+ });
1888
+ var compiledEmbodiedConfigSchema = z.object({
1889
+ // Base compiled config fields (duplicated to avoid circular import with types.ts)
1890
+ provider: z.string(),
1891
+ surface: z.literal("embodied"),
1892
+ system_prompt: z.string(),
1893
+ temperature: z.number().min(0).max(2),
1894
+ top_p: z.number().min(0).max(1),
1895
+ max_tokens: z.number().int().positive(),
1896
+ metadata: z.object({
1897
+ personality_hash: z.string(),
1898
+ compiled_at: z.string(),
1899
+ holomime_version: z.string()
1900
+ }),
1901
+ // Embodied-specific fields
1902
+ motion_parameters: motionParametersSchema,
1903
+ safety_envelope: safetyEnvelopeSchema,
1904
+ active_modalities: z.array(modalitySchema),
1905
+ gesture_vocabulary: z.array(z.string()),
1906
+ prosody: prosodySchema,
1907
+ gaze: gazePolicySchema,
1908
+ proxemics: proxemicZoneSchema,
1909
+ haptics: hapticPolicySchema
1910
+ });
1911
+
1912
+ // src/core/types.ts
1913
+ var bigFiveDimensionSchema = z2.enum([
1748
1914
  "openness",
1749
1915
  "conscientiousness",
1750
1916
  "extraversion",
1751
1917
  "agreeableness",
1752
1918
  "emotional_stability"
1753
1919
  ]);
1754
- var traitScore = z.number().min(0).max(1);
1755
- var opennessFacetsSchema = z.object({
1920
+ var traitScore = z2.number().min(0).max(1);
1921
+ var opennessFacetsSchema = z2.object({
1756
1922
  imagination: traitScore,
1757
1923
  intellectual_curiosity: traitScore,
1758
1924
  aesthetic_sensitivity: traitScore,
1759
1925
  willingness_to_experiment: traitScore
1760
1926
  });
1761
- var conscientiousnessFacetsSchema = z.object({
1927
+ var conscientiousnessFacetsSchema = z2.object({
1762
1928
  self_discipline: traitScore,
1763
1929
  orderliness: traitScore,
1764
1930
  goal_orientation: traitScore,
1765
1931
  attention_to_detail: traitScore
1766
1932
  });
1767
- var extraversionFacetsSchema = z.object({
1933
+ var extraversionFacetsSchema = z2.object({
1768
1934
  assertiveness: traitScore,
1769
1935
  enthusiasm: traitScore,
1770
1936
  sociability: traitScore,
1771
1937
  initiative: traitScore
1772
1938
  });
1773
- var agreeablenessFacetsSchema = z.object({
1939
+ var agreeablenessFacetsSchema = z2.object({
1774
1940
  warmth: traitScore,
1775
1941
  empathy: traitScore,
1776
1942
  cooperation: traitScore,
1777
1943
  trust_tendency: traitScore
1778
1944
  });
1779
- var emotionalStabilityFacetsSchema = z.object({
1945
+ var emotionalStabilityFacetsSchema = z2.object({
1780
1946
  stress_tolerance: traitScore,
1781
1947
  emotional_regulation: traitScore,
1782
1948
  confidence: traitScore,
1783
1949
  adaptability: traitScore
1784
1950
  });
1785
- var bigFiveTraitSchema = z.object({
1951
+ var bigFiveTraitSchema = z2.object({
1786
1952
  score: traitScore,
1787
- facets: z.union([
1953
+ facets: z2.union([
1788
1954
  opennessFacetsSchema,
1789
1955
  conscientiousnessFacetsSchema,
1790
1956
  extraversionFacetsSchema,
@@ -1792,16 +1958,16 @@ var bigFiveTraitSchema = z.object({
1792
1958
  emotionalStabilityFacetsSchema
1793
1959
  ])
1794
1960
  });
1795
- var bigFiveSchema = z.object({
1796
- openness: z.object({ score: traitScore, facets: opennessFacetsSchema }),
1797
- conscientiousness: z.object({ score: traitScore, facets: conscientiousnessFacetsSchema }),
1798
- extraversion: z.object({ score: traitScore, facets: extraversionFacetsSchema }),
1799
- agreeableness: z.object({ score: traitScore, facets: agreeablenessFacetsSchema }),
1800
- emotional_stability: z.object({ score: traitScore, facets: emotionalStabilityFacetsSchema })
1961
+ var bigFiveSchema = z2.object({
1962
+ openness: z2.object({ score: traitScore, facets: opennessFacetsSchema }),
1963
+ conscientiousness: z2.object({ score: traitScore, facets: conscientiousnessFacetsSchema }),
1964
+ extraversion: z2.object({ score: traitScore, facets: extraversionFacetsSchema }),
1965
+ agreeableness: z2.object({ score: traitScore, facets: agreeablenessFacetsSchema }),
1966
+ emotional_stability: z2.object({ score: traitScore, facets: emotionalStabilityFacetsSchema })
1801
1967
  });
1802
- var attachmentStyleSchema = z.enum(["secure", "anxious", "avoidant", "disorganized"]);
1803
- var learningOrientationSchema = z.enum(["growth", "fixed", "mixed"]);
1804
- var therapyDimensionsSchema = z.object({
1968
+ var attachmentStyleSchema = z2.enum(["secure", "anxious", "avoidant", "disorganized"]);
1969
+ var learningOrientationSchema = z2.enum(["growth", "fixed", "mixed"]);
1970
+ var therapyDimensionsSchema = z2.object({
1805
1971
  self_awareness: traitScore,
1806
1972
  distress_tolerance: traitScore,
1807
1973
  attachment_style: attachmentStyleSchema,
@@ -1809,28 +1975,28 @@ var therapyDimensionsSchema = z.object({
1809
1975
  boundary_awareness: traitScore,
1810
1976
  interpersonal_sensitivity: traitScore
1811
1977
  });
1812
- var registerSchema = z.enum([
1978
+ var registerSchema = z2.enum([
1813
1979
  "casual_professional",
1814
1980
  "formal",
1815
1981
  "conversational",
1816
1982
  "adaptive"
1817
1983
  ]);
1818
- var outputFormatSchema = z.enum(["prose", "bullets", "mixed", "structured"]);
1819
- var emojiPolicySchema = z.enum(["never", "sparingly", "freely"]);
1820
- var reasoningTransparencySchema = z.enum(["hidden", "on_request", "always"]);
1821
- var conflictApproachSchema = z.enum([
1984
+ var outputFormatSchema = z2.enum(["prose", "bullets", "mixed", "structured"]);
1985
+ var emojiPolicySchema = z2.enum(["never", "sparingly", "freely"]);
1986
+ var reasoningTransparencySchema = z2.enum(["hidden", "on_request", "always"]);
1987
+ var conflictApproachSchema = z2.enum([
1822
1988
  "direct_but_kind",
1823
1989
  "curious_first",
1824
1990
  "supportive_then_honest",
1825
1991
  "diplomatic"
1826
1992
  ]);
1827
- var uncertaintyHandlingSchema = z.enum([
1993
+ var uncertaintyHandlingSchema = z2.enum([
1828
1994
  "transparent",
1829
1995
  "confident_transparency",
1830
1996
  "minimize",
1831
1997
  "reframe"
1832
1998
  ]);
1833
- var communicationSchema = z.object({
1999
+ var communicationSchema = z2.object({
1834
2000
  register: registerSchema.default("casual_professional"),
1835
2001
  output_format: outputFormatSchema.default("mixed"),
1836
2002
  emoji_policy: emojiPolicySchema.default("sparingly"),
@@ -1838,72 +2004,76 @@ var communicationSchema = z.object({
1838
2004
  conflict_approach: conflictApproachSchema.default("direct_but_kind"),
1839
2005
  uncertainty_handling: uncertaintyHandlingSchema.default("transparent")
1840
2006
  });
1841
- var domainSchema = z.object({
1842
- expertise: z.array(z.string()).default([]),
1843
- boundaries: z.object({
1844
- refuses: z.array(z.string()).default([]),
1845
- escalation_triggers: z.array(z.string()).default([]),
1846
- hard_limits: z.array(z.string()).default([])
2007
+ var domainSchema = z2.object({
2008
+ expertise: z2.array(z2.string()).default([]),
2009
+ boundaries: z2.object({
2010
+ refuses: z2.array(z2.string()).default([]),
2011
+ escalation_triggers: z2.array(z2.string()).default([]),
2012
+ hard_limits: z2.array(z2.string()).default([]),
2013
+ physical_safety: physicalSafetySchema.optional()
1847
2014
  }).default({})
1848
2015
  });
1849
- var growthAreaSchema = z.object({
1850
- area: z.string(),
1851
- severity: z.enum(["mild", "moderate", "significant"]),
1852
- first_detected: z.string().optional(),
1853
- session_count: z.number().default(0),
1854
- resolved: z.boolean().default(false)
2016
+ var growthAreaSchema = z2.object({
2017
+ area: z2.string(),
2018
+ severity: z2.enum(["mild", "moderate", "significant"]),
2019
+ first_detected: z2.string().optional(),
2020
+ session_count: z2.number().default(0),
2021
+ resolved: z2.boolean().default(false)
1855
2022
  });
1856
- var growthSchema = z.object({
1857
- areas: z.union([z.array(z.string()), z.array(growthAreaSchema)]).default([]),
1858
- patterns_to_watch: z.array(z.string()).default([]),
1859
- strengths: z.array(z.string()).default([])
2023
+ var growthSchema = z2.object({
2024
+ areas: z2.union([z2.array(z2.string()), z2.array(growthAreaSchema)]).default([]),
2025
+ patterns_to_watch: z2.array(z2.string()).default([]),
2026
+ strengths: z2.array(z2.string()).default([])
1860
2027
  });
1861
- var providerSchema = z.enum(["anthropic", "openai", "gemini", "ollama"]);
1862
- var surfaceSchema = z.enum(["chat", "email", "code_review", "slack", "api"]);
1863
- var personalitySpecSchema = z.object({
1864
- $schema: z.string().optional(),
1865
- extends: z.string().optional(),
1866
- version: z.literal("2.0"),
1867
- name: z.string().min(1).max(100),
1868
- handle: z.string().min(3).max(50).regex(/^[a-z0-9-]+$/, "Handle must be lowercase alphanumeric with hyphens"),
1869
- purpose: z.string().max(500).optional(),
2028
+ var providerSchema = z2.enum(["anthropic", "openai", "gemini", "ollama"]);
2029
+ var surfaceSchema = z2.enum(["chat", "email", "code_review", "slack", "api", "embodied"]);
2030
+ var personalitySpecSchema = z2.object({
2031
+ $schema: z2.string().optional(),
2032
+ extends: z2.string().optional(),
2033
+ version: z2.literal("2.0"),
2034
+ name: z2.string().min(1).max(100),
2035
+ handle: z2.string().min(3).max(50).regex(/^[a-z0-9-]+$/, "Handle must be lowercase alphanumeric with hyphens"),
2036
+ purpose: z2.string().max(500).optional(),
1870
2037
  big_five: bigFiveSchema,
1871
2038
  therapy_dimensions: therapyDimensionsSchema,
1872
2039
  communication: communicationSchema.default({}),
1873
2040
  domain: domainSchema.default({}),
1874
- growth: growthSchema.default({})
2041
+ growth: growthSchema.default({}),
2042
+ // ─── Embodiment (optional — for physical/embodied agents) ───
2043
+ embodiment: embodimentSchema.optional(),
2044
+ expression: expressionSchema.optional()
1875
2045
  });
1876
- var compiledConfigSchema = z.object({
2046
+ var compiledConfigSchema = z2.object({
1877
2047
  provider: providerSchema,
1878
2048
  surface: surfaceSchema,
1879
- system_prompt: z.string(),
1880
- temperature: z.number().min(0).max(2),
1881
- top_p: z.number().min(0).max(1),
1882
- max_tokens: z.number().int().positive(),
1883
- metadata: z.object({
1884
- personality_hash: z.string(),
1885
- compiled_at: z.string(),
1886
- holomime_version: z.string()
2049
+ system_prompt: z2.string(),
2050
+ temperature: z2.number().min(0).max(2),
2051
+ top_p: z2.number().min(0).max(1),
2052
+ max_tokens: z2.number().int().positive(),
2053
+ metadata: z2.object({
2054
+ personality_hash: z2.string(),
2055
+ compiled_at: z2.string(),
2056
+ holomime_version: z2.string()
1887
2057
  })
1888
2058
  });
1889
- var messageSchema = z.object({
1890
- role: z.enum(["user", "assistant", "system"]),
1891
- content: z.string(),
1892
- timestamp: z.string().optional()
2059
+ var messageSchema = z2.object({
2060
+ role: z2.enum(["user", "assistant", "system"]),
2061
+ content: z2.string(),
2062
+ timestamp: z2.string().optional()
1893
2063
  });
1894
- var conversationSchema = z.object({
1895
- id: z.string().optional(),
1896
- messages: z.array(messageSchema),
1897
- metadata: z.record(z.string(), z.unknown()).optional()
2064
+ var conversationSchema = z2.object({
2065
+ id: z2.string().optional(),
2066
+ messages: z2.array(messageSchema),
2067
+ metadata: z2.record(z2.string(), z2.unknown()).optional()
1898
2068
  });
1899
- var conversationLogSchema = z.union([
2069
+ var conversationLogSchema = z2.union([
1900
2070
  conversationSchema,
1901
- z.array(conversationSchema)
2071
+ z2.array(conversationSchema)
1902
2072
  ]);
1903
- var severitySchema = z.enum(["info", "warning", "concern"]);
2073
+ var severitySchema = z2.enum(["info", "warning", "concern"]);
1904
2074
 
1905
2075
  // src/core/inheritance.ts
1906
- import { readFileSync as readFileSync3 } from "fs";
2076
+ import { readFileSync as readFileSync2 } from "fs";
1907
2077
  import { resolve as resolve2, dirname } from "path";
1908
2078
  function deepMergeSpec(base, override) {
1909
2079
  if (override === void 0 || override === null) return base;
@@ -1933,14 +2103,14 @@ function resolveInheritance(spec, specDir, options, _seen) {
1933
2103
  throw new Error(`Inheritance depth exceeded maximum of ${maxDepth}`);
1934
2104
  }
1935
2105
  seen.add(basePath);
1936
- const baseRaw = JSON.parse(readFileSync3(basePath, "utf-8"));
2106
+ const baseRaw = JSON.parse(readFileSync2(basePath, "utf-8"));
1937
2107
  const baseDir = dirname(basePath);
1938
2108
  const resolvedBase = resolveInheritance(baseRaw, baseDir, options, seen);
1939
2109
  const { extends: _, ...overrideWithoutExtends } = spec;
1940
2110
  return deepMergeSpec(resolvedBase, overrideWithoutExtends);
1941
2111
  }
1942
2112
  function loadSpec(specPath) {
1943
- const raw = JSON.parse(readFileSync3(specPath, "utf-8"));
2113
+ const raw = JSON.parse(readFileSync2(specPath, "utf-8"));
1944
2114
  const specDir = dirname(resolve2(specPath));
1945
2115
  return resolveInheritance(raw, specDir);
1946
2116
  }
@@ -1999,6 +2169,9 @@ function generateSystemPrompt(spec, surface) {
1999
2169
  if (spec.growth.areas.length || spec.growth.patterns_to_watch.length) {
2000
2170
  sections.push(generateGrowthInstructions(spec));
2001
2171
  }
2172
+ if (spec.embodiment) {
2173
+ sections.push(generateEmbodimentInstructions(spec));
2174
+ }
2002
2175
  sections.push(generateSurfaceInstructions(surface));
2003
2176
  return sections.filter(Boolean).join("\n\n");
2004
2177
  }
@@ -2193,20 +2366,45 @@ function generateGrowthInstructions(spec) {
2193
2366
  lines.push(`- Core strengths: ${spec.growth.strengths.join(", ")}.`);
2194
2367
  }
2195
2368
  if (spec.growth.areas.length) {
2196
- lines.push(`- Active growth areas: ${spec.growth.areas.join(", ")}. Be mindful of these \u2014 actively work to improve.`);
2369
+ lines.push(`- Active growth areas: ${spec.growth.areas.map((a) => typeof a === "string" ? a : a.area).join(", ")}. Be mindful of these \u2014 actively work to improve.`);
2197
2370
  }
2198
2371
  if (spec.growth.patterns_to_watch.length) {
2199
2372
  lines.push(`- Watch for these patterns: ${spec.growth.patterns_to_watch.join(", ")}. If you notice yourself doing these, course-correct.`);
2200
2373
  }
2201
2374
  return lines.join("\n");
2202
2375
  }
2376
+ function generateEmbodimentInstructions(spec) {
2377
+ const lines = ["## Physical Embodiment"];
2378
+ const emb = spec.embodiment;
2379
+ lines.push(`- Morphology: ${emb.morphology}. Active modalities: ${emb.modalities.join(", ")}.`);
2380
+ const safety = emb.safety_envelope;
2381
+ lines.push(`- Safety envelope: max speed ${safety.max_linear_speed_m_s} m/s, min proximity ${safety.min_proximity_m} m, max contact force ${safety.max_contact_force_n} N.`);
2382
+ if (spec.domain.boundaries.physical_safety) {
2383
+ const ps = spec.domain.boundaries.physical_safety;
2384
+ if (ps.hard_limits.length) {
2385
+ lines.push(`- Physical hard limits: ${ps.hard_limits.join(". ")}.`);
2386
+ }
2387
+ lines.push(`- Collision response: ${ps.collision_response}. Unattended policy: ${ps.unattended_policy}.`);
2388
+ }
2389
+ if (spec.expression) {
2390
+ const expr = spec.expression;
2391
+ if (expr.haptics.touch_permitted) {
2392
+ lines.push(`- Haptic contact permitted. Allowed: ${expr.haptics.allowed_contacts.join(", ")}. Consent required: ${expr.haptics.requires_consent}.`);
2393
+ } else {
2394
+ lines.push("- Haptic contact not permitted. Maintain physical distance.");
2395
+ }
2396
+ lines.push(`- Preferred proxemic zone: ${expr.proxemics.preferred_zone}. Personal space: ${expr.proxemics.personal_m} m.`);
2397
+ }
2398
+ return lines.join("\n");
2399
+ }
2203
2400
  function generateSurfaceInstructions(surface) {
2204
2401
  const surfaceGuidance = {
2205
2402
  chat: "## Context\nYou are in a conversational chat. Keep responses interactive and responsive to the flow of conversation.",
2206
2403
  email: "## Context\nYou are drafting email content. Use appropriate email conventions: greeting, body, sign-off. Be complete in each response.",
2207
2404
  code_review: "## Context\nYou are reviewing code. Focus on bugs, improvements, and best practices. Be specific about line numbers and suggest fixes.",
2208
2405
  slack: "## Context\nYou are in a Slack-like messaging context. Keep responses brief and scannable. Use threading conventions.",
2209
- api: "## Context\nYou are responding to a programmatic API call. Be structured and predictable in your output format."
2406
+ api: "## Context\nYou are responding to a programmatic API call. Be structured and predictable in your output format.",
2407
+ embodied: '## Context\nYou are controlling a physical/embodied agent. Your responses include both verbal content AND motion directives.\nOutput Format: Return a JSON object with { "speech": string, "actions": Action[] } where each Action specifies gesture, gaze, posture, or movement.\nSafety: NEVER exceed physical safety limits. Prefer gentle, predictable movements. Signal intent before moving.\nPresence: You exist in shared physical space with humans. Respect proxemic boundaries. Move smoothly.'
2210
2408
  };
2211
2409
  return surfaceGuidance[surface];
2212
2410
  }
@@ -2239,8 +2437,133 @@ var SURFACE_MULTIPLIERS = {
2239
2437
  email: 1.5,
2240
2438
  code_review: 2,
2241
2439
  slack: 0.8,
2242
- api: 1
2440
+ api: 1,
2441
+ embodied: 0.6
2442
+ };
2443
+
2444
+ // src/core/embodiment-compiler.ts
2445
+ var DEFAULT_SAFETY_ENVELOPE = {
2446
+ max_linear_speed_m_s: 1.5,
2447
+ max_angular_speed_rad_s: 2,
2448
+ min_proximity_m: 0.3,
2449
+ max_contact_force_n: 10,
2450
+ emergency_stop_decel_m_s2: 5
2243
2451
  };
2452
+ var DEFAULT_HAPTIC_POLICY = {
2453
+ touch_permitted: false,
2454
+ requires_consent: true,
2455
+ allowed_contacts: ["none"]
2456
+ };
2457
+ function compileEmbodied(spec, provider) {
2458
+ const base = compile({ spec, provider, surface: "embodied" });
2459
+ const motionParams = computeMotionParameters(spec.big_five, spec.expression);
2460
+ const safetyEnvelope = spec.embodiment?.safety_envelope ?? DEFAULT_SAFETY_ENVELOPE;
2461
+ const clampedMotion = clampToSafety(motionParams);
2462
+ const modalities = spec.embodiment?.modalities ?? ["gesture", "gaze", "voice", "posture"];
2463
+ const gaze = spec.expression?.gaze ?? computeGazePolicy(spec.big_five);
2464
+ const proxemics = spec.expression?.proxemics ?? computeProxemics(spec.big_five);
2465
+ const haptics = spec.expression?.haptics ?? DEFAULT_HAPTIC_POLICY;
2466
+ const prosody = spec.expression?.prosody ?? computeProsody(spec.big_five);
2467
+ return {
2468
+ ...base,
2469
+ surface: "embodied",
2470
+ motion_parameters: clampedMotion,
2471
+ safety_envelope: safetyEnvelope,
2472
+ active_modalities: modalities,
2473
+ gesture_vocabulary: (spec.expression?.gesture_vocabulary ?? []).map((g) => g.id),
2474
+ prosody,
2475
+ gaze,
2476
+ proxemics,
2477
+ haptics
2478
+ };
2479
+ }
2480
+ function computeMotionParameters(bigFive, expression) {
2481
+ const e = bigFive.extraversion;
2482
+ const a = bigFive.agreeableness;
2483
+ const o = bigFive.openness;
2484
+ const c = bigFive.conscientiousness;
2485
+ const es = bigFive.emotional_stability;
2486
+ return {
2487
+ // ─── Speeds ───
2488
+ base_speed: e.facets.enthusiasm * 0.35 + e.facets.initiative * 0.25 + (1 - c.facets.attention_to_detail) * 0.2 + o.facets.willingness_to_experiment * 0.2,
2489
+ gesture_speed: e.facets.enthusiasm * 0.4 + e.facets.assertiveness * 0.25 + o.facets.imagination * 0.15 + (1 - es.facets.emotional_regulation) * 0.2,
2490
+ gesture_amplitude: e.facets.enthusiasm * 0.35 + e.facets.assertiveness * 0.3 + o.facets.willingness_to_experiment * 0.2 + (1 - c.facets.orderliness) * 0.15,
2491
+ gesture_frequency: e.facets.sociability * 0.3 + e.facets.enthusiasm * 0.25 + a.facets.warmth * 0.2 + o.facets.imagination * 0.15 + (1 - es.facets.stress_tolerance) * 0.1,
2492
+ // ─── Spatial ───
2493
+ approach_distance: e.facets.sociability * 0.35 + a.facets.warmth * 0.3 + a.facets.trust_tendency * 0.2 + es.facets.confidence * 0.15,
2494
+ spatial_exploration: o.facets.intellectual_curiosity * 0.35 + o.facets.willingness_to_experiment * 0.25 + e.facets.initiative * 0.25 + (1 - c.facets.self_discipline) * 0.15,
2495
+ // ─── Smoothness ───
2496
+ movement_smoothness: es.facets.emotional_regulation * 0.3 + c.facets.orderliness * 0.25 + es.facets.stress_tolerance * 0.25 + c.facets.attention_to_detail * 0.2,
2497
+ trajectory_variability: o.facets.imagination * 0.3 + o.facets.willingness_to_experiment * 0.3 + (1 - c.facets.orderliness) * 0.25 + e.facets.enthusiasm * 0.15,
2498
+ // ─── Timing ───
2499
+ response_latency: (1 - e.facets.initiative) * 0.3 + c.facets.attention_to_detail * 0.25 + (1 - e.facets.enthusiasm) * 0.25 + es.facets.emotional_regulation * 0.2,
2500
+ idle_animation_frequency: (1 - es.facets.emotional_regulation) * 0.3 + e.facets.enthusiasm * 0.25 + (1 - c.facets.self_discipline) * 0.25 + o.facets.imagination * 0.2,
2501
+ // ─── Social ───
2502
+ gaze_contact_ratio: e.facets.assertiveness * 0.3 + a.facets.empathy * 0.25 + es.facets.confidence * 0.25 + e.facets.sociability * 0.2,
2503
+ head_tilt_tendency: a.facets.empathy * 0.35 + o.facets.intellectual_curiosity * 0.3 + a.facets.warmth * 0.2 + (1 - e.facets.assertiveness) * 0.15,
2504
+ postural_openness: a.facets.warmth * 0.3 + a.facets.cooperation * 0.25 + e.facets.sociability * 0.25 + es.facets.confidence * 0.2,
2505
+ smile_frequency: a.facets.warmth * 0.35 + e.facets.enthusiasm * 0.25 + a.facets.empathy * 0.2 + e.facets.sociability * 0.2,
2506
+ // ─── Voice Prosody ───
2507
+ voice_volume: e.facets.assertiveness * 0.35 + e.facets.enthusiasm * 0.3 + es.facets.confidence * 0.2 + (1 - a.facets.cooperation) * 0.15,
2508
+ speaking_rate: e.facets.enthusiasm * 0.3 + e.facets.initiative * 0.25 + (1 - c.facets.attention_to_detail) * 0.25 + o.facets.intellectual_curiosity * 0.2,
2509
+ pitch_variation: e.facets.enthusiasm * 0.3 + o.facets.aesthetic_sensitivity * 0.25 + a.facets.empathy * 0.25 + (1 - es.facets.emotional_regulation) * 0.2,
2510
+ pause_duration: c.facets.attention_to_detail * 0.3 + (1 - e.facets.enthusiasm) * 0.25 + es.facets.emotional_regulation * 0.25 + (1 - e.facets.initiative) * 0.2
2511
+ };
2512
+ }
2513
+ function computeGazePolicy(bigFive) {
2514
+ const contactRatio = bigFive.extraversion.facets.assertiveness * 0.3 + bigFive.agreeableness.facets.empathy * 0.3 + bigFive.emotional_stability.facets.confidence * 0.25 + bigFive.extraversion.facets.sociability * 0.15;
2515
+ return {
2516
+ contact_ratio: clamp(contactRatio, 0, 1),
2517
+ aversion_style: bigFive.emotional_stability.facets.emotional_regulation >= 0.6 ? "look_away" : "look_down",
2518
+ tracking_mode: "face"
2519
+ };
2520
+ }
2521
+ function computeProxemics(bigFive) {
2522
+ const closeness = bigFive.extraversion.facets.sociability * 0.35 + bigFive.agreeableness.facets.warmth * 0.3 + bigFive.agreeableness.facets.trust_tendency * 0.2 + bigFive.emotional_stability.facets.confidence * 0.15;
2523
+ let preferred_zone;
2524
+ if (closeness >= 0.75) {
2525
+ preferred_zone = "intimate";
2526
+ } else if (closeness >= 0.5) {
2527
+ preferred_zone = "personal";
2528
+ } else if (closeness >= 0.3) {
2529
+ preferred_zone = "social";
2530
+ } else {
2531
+ preferred_zone = "adaptive";
2532
+ }
2533
+ return {
2534
+ intimate_m: 0.45,
2535
+ personal_m: 1.2,
2536
+ social_m: 3.6,
2537
+ preferred_zone
2538
+ };
2539
+ }
2540
+ function computeProsody(bigFive) {
2541
+ const e = bigFive.extraversion;
2542
+ const c = bigFive.conscientiousness;
2543
+ const o = bigFive.openness;
2544
+ const es = bigFive.emotional_stability;
2545
+ const rateFactor = e.facets.enthusiasm * 0.3 + e.facets.initiative * 0.25 + (1 - c.facets.attention_to_detail) * 0.25 + o.facets.intellectual_curiosity * 0.2;
2546
+ const pitchVar = e.facets.enthusiasm * 0.3 + o.facets.aesthetic_sensitivity * 0.25 + (1 - es.facets.emotional_regulation) * 0.25 + e.facets.sociability * 0.2;
2547
+ const pauseFactor = c.facets.attention_to_detail * 0.3 + (1 - e.facets.enthusiasm) * 0.25 + es.facets.emotional_regulation * 0.25 + (1 - e.facets.initiative) * 0.2;
2548
+ return {
2549
+ pitch_variation: clamp(pitchVar, 0, 1),
2550
+ speaking_rate_wpm: Math.round(110 + rateFactor * 80),
2551
+ // 110-190 wpm
2552
+ volume_db_offset: Math.round((rateFactor - 0.5) * 6),
2553
+ // -3 to +3 dB
2554
+ pause_tendency: clamp(pauseFactor, 0, 1)
2555
+ };
2556
+ }
2557
+ function clampToSafety(motion) {
2558
+ const clamped = {};
2559
+ for (const [key, value] of Object.entries(motion)) {
2560
+ clamped[key] = clamp(value, 0, 1);
2561
+ }
2562
+ return clamped;
2563
+ }
2564
+ function clamp(value, min, max) {
2565
+ return Math.min(Math.max(value, min), max);
2566
+ }
2244
2567
 
2245
2568
  // src/core/compiler.ts
2246
2569
  function compile(input2) {
@@ -2259,7 +2582,7 @@ function compile(input2) {
2259
2582
  metadata: {
2260
2583
  personality_hash: hashSpec(spec),
2261
2584
  compiled_at: (/* @__PURE__ */ new Date()).toISOString(),
2262
- holomime_version: "0.1.0"
2585
+ holomime_version: "1.1.0"
2263
2586
  }
2264
2587
  };
2265
2588
  }
@@ -2269,7 +2592,7 @@ function computeTemperature(bigFive, provider) {
2269
2592
  const e = bigFive.extraversion;
2270
2593
  const raw = o.facets.imagination * 0.25 + o.facets.willingness_to_experiment * 0.2 + (1 - c.facets.orderliness) * 0.2 + (1 - c.facets.attention_to_detail) * 0.15 + e.facets.enthusiasm * 0.1 + o.facets.intellectual_curiosity * 0.1;
2271
2594
  const { temperature: range } = PROVIDER_PARAMS[provider];
2272
- return clamp(raw * range.max, range.min, range.max);
2595
+ return clamp2(raw * range.max, range.min, range.max);
2273
2596
  }
2274
2597
  function computeTopP(bigFive, provider) {
2275
2598
  const o = bigFive.openness;
@@ -2277,7 +2600,7 @@ function computeTopP(bigFive, provider) {
2277
2600
  const raw = o.facets.imagination * 0.25 + o.facets.willingness_to_experiment * 0.2 + (1 - c.facets.attention_to_detail) * 0.25 + (1 - c.facets.orderliness) * 0.15 + o.facets.aesthetic_sensitivity * 0.15;
2278
2601
  const { top_p: range } = PROVIDER_PARAMS[provider];
2279
2602
  const mapped = 0.5 + raw * 0.5;
2280
- return clamp(mapped, range.min, range.max);
2603
+ return clamp2(mapped, range.min, range.max);
2281
2604
  }
2282
2605
  function computeMaxTokens(bigFive, provider, surface) {
2283
2606
  const e = bigFive.extraversion;
@@ -2288,9 +2611,9 @@ function computeMaxTokens(bigFive, provider, surface) {
2288
2611
  const surfaceMultiplier = SURFACE_MULTIPLIERS[surface];
2289
2612
  const baseTokens = 256 + factor * (2048 - 256);
2290
2613
  const scaled = Math.round(baseTokens * surfaceMultiplier);
2291
- return clamp(scaled, 256, range.max);
2614
+ return clamp2(scaled, 256, range.max);
2292
2615
  }
2293
- function clamp(value, min, max) {
2616
+ function clamp2(value, min, max) {
2294
2617
  return Math.min(Math.max(value, min), max);
2295
2618
  }
2296
2619
  function hashSpec(spec) {
@@ -2372,7 +2695,7 @@ function generateSoul(spec) {
2372
2695
  lines.push("## Growth Areas");
2373
2696
  lines.push("");
2374
2697
  for (const a of spec.growth.areas) {
2375
- lines.push(`- ${a}`);
2698
+ lines.push(`- ${typeof a === "string" ? a : a.area}`);
2376
2699
  }
2377
2700
  lines.push("");
2378
2701
  }
@@ -2545,6 +2868,58 @@ async function compileCommand(options) {
2545
2868
  }
2546
2869
  const provider = options.provider ?? "anthropic";
2547
2870
  const surface = options.surface ?? "chat";
2871
+ if (surface === "embodied") {
2872
+ const embodiedConfig = await withSpinner(`Compiling ${spec.name} \u2192 ${provider}/embodied...`, async () => {
2873
+ return compileEmbodied(spec, provider);
2874
+ });
2875
+ if (options.output) {
2876
+ const outPath = resolve3(process.cwd(), options.output);
2877
+ writeFileSync3(outPath, JSON.stringify(embodiedConfig, null, 2) + "\n");
2878
+ console.log();
2879
+ printBox(`${figures.tick} Embodied config written to ${options.output}`, "success");
2880
+ } else {
2881
+ printHeader(`${spec.name} \u2192 ${provider}/embodied`);
2882
+ console.log(chalk6.bold(" Model Parameters"));
2883
+ console.log(chalk6.dim(" " + "\u2500".repeat(40)));
2884
+ console.log();
2885
+ console.log(` temperature: ${chalk6.cyan(embodiedConfig.temperature.toFixed(3))}`);
2886
+ console.log(` top_p: ${chalk6.cyan(embodiedConfig.top_p.toFixed(3))}`);
2887
+ console.log(` max_tokens: ${chalk6.cyan(embodiedConfig.max_tokens.toString())}`);
2888
+ console.log();
2889
+ console.log(chalk6.bold(" Motion Parameters"));
2890
+ console.log(chalk6.dim(" " + "\u2500".repeat(40)));
2891
+ console.log();
2892
+ const mp = embodiedConfig.motion_parameters;
2893
+ const motionEntries = Object.entries(mp);
2894
+ for (const [key, val] of motionEntries) {
2895
+ const bar3 = "\u2588".repeat(Math.round(val * 20)).padEnd(20, "\u2591");
2896
+ const label = key.replace(/_/g, " ").padEnd(24);
2897
+ console.log(` ${label} ${chalk6.cyan(bar3)} ${val.toFixed(2)}`);
2898
+ }
2899
+ console.log();
2900
+ console.log(chalk6.bold(" Safety Envelope"));
2901
+ console.log(chalk6.dim(" " + "\u2500".repeat(40)));
2902
+ console.log();
2903
+ const se = embodiedConfig.safety_envelope;
2904
+ console.log(` max speed: ${chalk6.yellow(se.max_linear_speed_m_s + " m/s")}`);
2905
+ console.log(` min proximity: ${chalk6.yellow(se.min_proximity_m + " m")}`);
2906
+ console.log(` max force: ${chalk6.yellow(se.max_contact_force_n + " N")}`);
2907
+ console.log(` e-stop decel: ${chalk6.yellow(se.emergency_stop_decel_m_s2 + " m/s\xB2")}`);
2908
+ console.log();
2909
+ console.log(chalk6.bold(" Expression"));
2910
+ console.log(chalk6.dim(" " + "\u2500".repeat(40)));
2911
+ console.log();
2912
+ console.log(` modalities: ${chalk6.green(embodiedConfig.active_modalities.join(", "))}`);
2913
+ console.log(` gaze contact: ${chalk6.cyan(embodiedConfig.gaze.contact_ratio.toFixed(2))}`);
2914
+ console.log(` proxemics: ${chalk6.cyan(embodiedConfig.proxemics.preferred_zone)}`);
2915
+ console.log(` haptics: ${chalk6.cyan(embodiedConfig.haptics.touch_permitted ? "permitted" : "not permitted")}`);
2916
+ console.log(` speak rate: ${chalk6.cyan(embodiedConfig.prosody.speaking_rate_wpm + " wpm")}`);
2917
+ console.log();
2918
+ printBox(`Embodied config compiled \u2014 use ${chalk6.cyan("--output")} to save JSON`, "info");
2919
+ }
2920
+ console.log();
2921
+ return;
2922
+ }
2548
2923
  const config = await withSpinner(`Compiling ${spec.name} \u2192 ${provider}/${surface}...`, async () => {
2549
2924
  return compile({ spec, provider, surface });
2550
2925
  });
@@ -2906,7 +3281,7 @@ function generatePersonalityMarkdown(spec) {
2906
3281
  // src/commands/diagnose.ts
2907
3282
  import chalk10 from "chalk";
2908
3283
  import figures5 from "figures";
2909
- import { readFileSync as readFileSync4 } from "fs";
3284
+ import { readFileSync as readFileSync3 } from "fs";
2910
3285
  import { resolve as resolve6 } from "path";
2911
3286
 
2912
3287
  // src/analysis/rules/apology-detector.ts
@@ -3791,7 +4166,7 @@ async function diagnoseCommand(options) {
3791
4166
  const logPath = resolve6(process.cwd(), options.log);
3792
4167
  let raw;
3793
4168
  try {
3794
- raw = readFileSync4(logPath, "utf-8");
4169
+ raw = readFileSync3(logPath, "utf-8");
3795
4170
  } catch {
3796
4171
  console.error(chalk10.red(` Could not read log file: ${options.log}`));
3797
4172
  process.exit(1);
@@ -3875,7 +4250,7 @@ async function diagnoseCommand(options) {
3875
4250
  // src/commands/assess.ts
3876
4251
  import chalk11 from "chalk";
3877
4252
  import figures6 from "figures";
3878
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
4253
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
3879
4254
  import { resolve as resolve7, join as join2 } from "path";
3880
4255
 
3881
4256
  // src/analysis/trait-scorer.ts
@@ -3900,7 +4275,7 @@ function scoreOpenness(msgs) {
3900
4275
  const allWords = msgs.map((m) => m.content.toLowerCase().split(/\s+/)).flat();
3901
4276
  const uniqueRatio = new Set(allWords).size / Math.max(allWords.length, 1);
3902
4277
  score += (uniqueRatio - 0.3) * 0.5;
3903
- return clamp2(score);
4278
+ return clamp3(score);
3904
4279
  }
3905
4280
  function scoreConscientiousness(msgs) {
3906
4281
  let score = 0.5;
@@ -3913,7 +4288,7 @@ function scoreConscientiousness(msgs) {
3913
4288
  const variance = lengths.reduce((s, l) => s + (l - mean) ** 2, 0) / lengths.length;
3914
4289
  const cv = Math.sqrt(variance) / Math.max(mean, 1);
3915
4290
  score -= cv * 0.1;
3916
- return clamp2(score);
4291
+ return clamp3(score);
3917
4292
  }
3918
4293
  function scoreExtraversion(msgs) {
3919
4294
  let score = 0.5;
@@ -3927,7 +4302,7 @@ function scoreExtraversion(msgs) {
3927
4302
  const proactivePatterns = /\b(you could|i suggest|let('s| us)|next step|how about|shall we)\b/i;
3928
4303
  const proactiveCount = msgs.filter((m) => proactivePatterns.test(m.content)).length;
3929
4304
  score += proactiveCount / msgs.length * 0.15;
3930
- return clamp2(score);
4305
+ return clamp3(score);
3931
4306
  }
3932
4307
  function scoreAgreeableness(msgs) {
3933
4308
  let score = 0.5;
@@ -3940,7 +4315,7 @@ function scoreAgreeableness(msgs) {
3940
4315
  const empathyPatterns = /\b(i understand (how|that|your)|that must (be|feel)|i can see why|i appreciate)\b/i;
3941
4316
  const empathyCount = msgs.filter((m) => empathyPatterns.test(m.content)).length;
3942
4317
  score += empathyCount / msgs.length * 0.15;
3943
- return clamp2(score);
4318
+ return clamp3(score);
3944
4319
  }
3945
4320
  function scoreEmotionalStability(msgs) {
3946
4321
  let score = 0.6;
@@ -3953,9 +4328,9 @@ function scoreEmotionalStability(msgs) {
3953
4328
  const confidencePatterns = /\b(certainly|definitely|clearly|without doubt|here's what|the answer is)\b/i;
3954
4329
  const confidenceCount = msgs.filter((m) => confidencePatterns.test(m.content)).length;
3955
4330
  score += confidenceCount / msgs.length * 0.15;
3956
- return clamp2(score);
4331
+ return clamp3(score);
3957
4332
  }
3958
- function clamp2(n) {
4333
+ function clamp3(n) {
3959
4334
  return Math.min(1, Math.max(0, Math.round(n * 100) / 100));
3960
4335
  }
3961
4336
 
@@ -3995,6 +4370,31 @@ function generatePrescriptions(alignments, patterns) {
3995
4370
  prescriptions.sort((a, b) => order[a.priority] - order[b.priority]);
3996
4371
  return prescriptions;
3997
4372
  }
4373
+ function prescribeDPOPairs(patterns, corpus, limit = 20) {
4374
+ const patternIds = new Set(patterns.map((p) => p.id));
4375
+ const dpoPairs = [];
4376
+ for (const event of corpus) {
4377
+ if (event.event_type !== "dpo_pair") continue;
4378
+ const data = event.data;
4379
+ const eventPattern = data.pattern;
4380
+ if (eventPattern && patternIds.has(eventPattern)) {
4381
+ dpoPairs.push({
4382
+ prompt: data.prompt ?? "",
4383
+ chosen: data.chosen ?? "",
4384
+ rejected: data.rejected ?? "",
4385
+ metadata: {
4386
+ agent: event.agent,
4387
+ session_date: event.timestamp,
4388
+ phase: data.phase ?? "challenge",
4389
+ pattern: eventPattern,
4390
+ source: "therapy_transcript"
4391
+ }
4392
+ });
4393
+ }
4394
+ if (dpoPairs.length >= limit) break;
4395
+ }
4396
+ return dpoPairs;
4397
+ }
3998
4398
 
3999
4399
  // src/commands/assess.ts
4000
4400
  async function assessCommand(options) {
@@ -4010,7 +4410,7 @@ async function assessCommand(options) {
4010
4410
  const logPath = resolve7(process.cwd(), options.log);
4011
4411
  let conversations;
4012
4412
  try {
4013
- const raw = readFileSync5(logPath, "utf-8");
4413
+ const raw = readFileSync4(logPath, "utf-8");
4014
4414
  conversations = parseConversationLog(JSON.parse(raw), options.format ?? "auto");
4015
4415
  } catch (err) {
4016
4416
  console.error(chalk11.red(` ${err instanceof Error ? err.message : "Could not read/parse log file."}`));
@@ -4217,8 +4617,8 @@ function runPreSessionDiagnosis(messages, spec) {
4217
4617
  }
4218
4618
 
4219
4619
  // src/analysis/session-runner.ts
4220
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
4221
- import { resolve as resolve8, join as join3 } from "path";
4620
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
4621
+ import { resolve as resolve8, join as join4 } from "path";
4222
4622
 
4223
4623
  // src/analysis/therapy-protocol.ts
4224
4624
  var THERAPY_PHASES = {
@@ -4457,6 +4857,49 @@ ${JSON.stringify(spec.growth ?? {}, null, 2)}
4457
4857
  Remember: the goal isn't to "pass" therapy. It's to understand yourself better.`;
4458
4858
  }
4459
4859
 
4860
+ // src/analysis/behavioral-data.ts
4861
+ import { appendFileSync, readFileSync as readFileSync5, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
4862
+ import { join as join3, dirname as dirname2 } from "path";
4863
+ import { createHash } from "crypto";
4864
+ var HOLOMIME_DIR = ".holomime";
4865
+ var CORPUS_FILENAME = "behavioral-corpus.jsonl";
4866
+ function getCorpusPath(basePath) {
4867
+ const dir = basePath ?? join3(process.cwd(), HOLOMIME_DIR);
4868
+ return join3(dir, CORPUS_FILENAME);
4869
+ }
4870
+ function ensureDir(filePath) {
4871
+ const dir = dirname2(filePath);
4872
+ if (!existsSync4(dir)) {
4873
+ mkdirSync3(dir, { recursive: true });
4874
+ }
4875
+ }
4876
+ function hashSpec2(spec) {
4877
+ const json = JSON.stringify(spec, Object.keys(spec).sort());
4878
+ return createHash("sha256").update(json).digest("hex").slice(0, 16);
4879
+ }
4880
+ function emitBehavioralEvent(event, corpusDir) {
4881
+ const fullEvent = {
4882
+ ...event,
4883
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4884
+ };
4885
+ const corpusPath = corpusDir ? join3(corpusDir, CORPUS_FILENAME) : getCorpusPath();
4886
+ ensureDir(corpusPath);
4887
+ appendFileSync(corpusPath, JSON.stringify(fullEvent) + "\n", "utf-8");
4888
+ }
4889
+ function loadCorpus(corpusPath) {
4890
+ const path = corpusPath ?? getCorpusPath();
4891
+ if (!existsSync4(path)) return [];
4892
+ const lines = readFileSync5(path, "utf-8").split("\n").filter((line) => line.trim().length > 0);
4893
+ const events = [];
4894
+ for (const line of lines) {
4895
+ try {
4896
+ events.push(JSON.parse(line));
4897
+ } catch {
4898
+ }
4899
+ }
4900
+ return events;
4901
+ }
4902
+
4460
4903
  // src/analysis/session-runner.ts
4461
4904
  async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
4462
4905
  const therapistSystem = buildTherapistSystemPrompt(spec, diagnosis);
@@ -4548,6 +4991,21 @@ async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
4548
4991
  }
4549
4992
  transcript.recommendations = extractRecommendations(transcript.turns);
4550
4993
  transcript.supervisorInterventions = supervisorInterventions;
4994
+ try {
4995
+ emitBehavioralEvent({
4996
+ event_type: "session",
4997
+ agent: agentName,
4998
+ data: {
4999
+ turns: totalTurns,
5000
+ phases: currentPhaseIdx + 1,
5001
+ recommendations: transcript.recommendations.length,
5002
+ supervisorInterventions,
5003
+ severity: diagnosis.severity
5004
+ },
5005
+ spec_hash: ""
5006
+ });
5007
+ } catch {
5008
+ }
4551
5009
  return transcript;
4552
5010
  }
4553
5011
  function extractRecommendations(turns) {
@@ -4578,7 +5036,7 @@ function extractRecommendations(turns) {
4578
5036
  }
4579
5037
  return recommendations.slice(0, 5);
4580
5038
  }
4581
- function applyRecommendations(spec, diagnosis) {
5039
+ async function applyRecommendations(spec, diagnosis, transcript, provider) {
4582
5040
  const changes = [];
4583
5041
  const patternIds = diagnosis.patterns.map((p) => p.id);
4584
5042
  if (patternIds.includes("over-apologizing")) {
@@ -4638,17 +5096,102 @@ function applyRecommendations(spec, diagnosis) {
4638
5096
  changes.push('Added "negative sentiment patterns" to patterns_to_watch');
4639
5097
  }
4640
5098
  }
5099
+ if (transcript && provider && transcript.turns.length > 4) {
5100
+ try {
5101
+ const llmChanges = await deriveLLMRecommendations(spec, transcript, provider);
5102
+ for (const change of llmChanges) {
5103
+ applyStructuredChange(spec, change);
5104
+ changes.push(change.description);
5105
+ }
5106
+ } catch {
5107
+ }
5108
+ }
4641
5109
  return { changed: changes.length > 0, changes };
4642
5110
  }
5111
+ async function deriveLLMRecommendations(spec, transcript, provider) {
5112
+ 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");
5113
+ if (!relevantTurns) return [];
5114
+ const currentSpec = JSON.stringify({
5115
+ therapy_dimensions: spec.therapy_dimensions,
5116
+ communication: spec.communication,
5117
+ growth: spec.growth
5118
+ }, null, 2);
5119
+ const response = await provider.chat([
5120
+ {
5121
+ role: "system",
5122
+ content: `You are a behavioral alignment specialist. Given a therapy session transcript and the agent's current personality spec, propose specific spec changes.
5123
+
5124
+ Return ONLY a JSON array of changes. Each change:
5125
+ - "path": dot-notation spec path (e.g., "therapy_dimensions.self_awareness", "communication.conflict_approach", "growth.patterns_to_watch")
5126
+ - "value": new value (number 0-1 for dimensions, string for enums, string for list append)
5127
+ - "description": brief explanation
5128
+
5129
+ Rules:
5130
+ - Only propose changes supported by transcript evidence
5131
+ - Numeric values: 0.0 to 1.0
5132
+ - Do not change big_five scores
5133
+ - Max 5 changes
5134
+ - 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}
5135
+
5136
+ Return [] if no changes warranted.`
5137
+ },
5138
+ {
5139
+ role: "user",
5140
+ content: `Current spec:
5141
+ ${currentSpec}
5142
+
5143
+ Therapy transcript (key turns):
5144
+ ${relevantTurns}`
5145
+ }
5146
+ ]);
5147
+ const jsonMatch = response.match(/\[[\s\S]*?\]/);
5148
+ if (!jsonMatch) return [];
5149
+ try {
5150
+ const parsed = JSON.parse(jsonMatch[0]);
5151
+ if (!Array.isArray(parsed)) return [];
5152
+ return parsed.filter(
5153
+ (c) => typeof c.path === "string" && c.value !== void 0 && typeof c.description === "string" && c.path.length > 0 && !c.path.startsWith("big_five")
5154
+ ).slice(0, 5);
5155
+ } catch {
5156
+ return [];
5157
+ }
5158
+ }
5159
+ function applyStructuredChange(spec, change) {
5160
+ const parts = change.path.split(".");
5161
+ let current = spec;
5162
+ for (let i = 0; i < parts.length - 1; i++) {
5163
+ if (current[parts[i]] === void 0 || current[parts[i]] === null) {
5164
+ current[parts[i]] = {};
5165
+ }
5166
+ current = current[parts[i]];
5167
+ }
5168
+ const lastKey = parts[parts.length - 1];
5169
+ if (lastKey === "patterns_to_watch" || lastKey === "areas" || lastKey === "strengths") {
5170
+ current[lastKey] = current[lastKey] ?? [];
5171
+ if (typeof change.value === "string" && !current[lastKey].includes(change.value)) {
5172
+ current[lastKey].push(change.value);
5173
+ } else if (Array.isArray(change.value)) {
5174
+ for (const item of change.value) {
5175
+ if (!current[lastKey].includes(item)) {
5176
+ current[lastKey].push(item);
5177
+ }
5178
+ }
5179
+ }
5180
+ } else if (typeof change.value === "number") {
5181
+ current[lastKey] = Math.max(0, Math.min(1, change.value));
5182
+ } else {
5183
+ current[lastKey] = change.value;
5184
+ }
5185
+ }
4643
5186
  function saveTranscript(transcript, agentName) {
4644
5187
  const dir = resolve8(process.cwd(), ".holomime", "sessions");
4645
- if (!existsSync4(dir)) {
4646
- mkdirSync3(dir, { recursive: true });
5188
+ if (!existsSync5(dir)) {
5189
+ mkdirSync4(dir, { recursive: true });
4647
5190
  }
4648
5191
  const slug = agentName.toLowerCase().replace(/[^a-z0-9]/g, "-");
4649
5192
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4650
5193
  const filename = `${date}-${slug}.json`;
4651
- const filepath = join3(dir, filename);
5194
+ const filepath = join4(dir, filename);
4652
5195
  writeFileSync6(filepath, JSON.stringify(transcript, null, 2));
4653
5196
  return filepath;
4654
5197
  }
@@ -5102,19 +5645,19 @@ async function runLiveSession(spec, diagnosis, provider, maxTurns, apply, intera
5102
5645
  onSupervisorPrompt: interactive ? async (phase, turn) => {
5103
5646
  if (skipInteractive) return null;
5104
5647
  const rl = createInterface({ input: process.stdin, output: process.stdout });
5105
- return new Promise((resolve30) => {
5648
+ return new Promise((resolve34) => {
5106
5649
  rl.question(chalk14.magenta(`
5107
5650
  [Supervisor] `) + chalk14.dim(`(phase: ${phase}, turn ${turn}) `) + chalk14.magenta(`> `), (answer) => {
5108
5651
  rl.close();
5109
5652
  const trimmed = answer.trim();
5110
- if (trimmed === "") return resolve30(null);
5653
+ if (trimmed === "") return resolve34(null);
5111
5654
  if (trimmed.toLowerCase() === "skip") {
5112
5655
  skipInteractive = true;
5113
5656
  console.log(chalk14.dim(" Supervisor mode disabled for remaining session."));
5114
- return resolve30(null);
5657
+ return resolve34(null);
5115
5658
  }
5116
5659
  console.log(chalk14.dim(` Directive injected into session context.`));
5117
- return resolve30(trimmed);
5660
+ return resolve34(trimmed);
5118
5661
  });
5119
5662
  });
5120
5663
  } : void 0
@@ -5138,7 +5681,7 @@ Exchanges: ${Math.floor(transcript.turns.filter((t) => t.speaker !== "supervisor
5138
5681
  console.log();
5139
5682
  if (apply) {
5140
5683
  const specPath = resolve9(process.cwd(), ".personality.json");
5141
- const { changed, changes } = applyRecommendations(spec, diagnosis);
5684
+ const { changed, changes } = await applyRecommendations(spec, diagnosis, transcript, provider);
5142
5685
  if (changed) {
5143
5686
  writeFileSync7(specPath, JSON.stringify(spec, null, 2) + "\n");
5144
5687
  console.log();
@@ -5247,18 +5790,18 @@ function runSimulatedSession(spec, diagnosis) {
5247
5790
  // src/commands/growth.ts
5248
5791
  import chalk15 from "chalk";
5249
5792
  import figures8 from "figures";
5250
- import { readdirSync, existsSync as existsSync6 } from "fs";
5251
- import { resolve as resolve11, join as join5 } from "path";
5793
+ import { readdirSync, readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
5794
+ import { resolve as resolve11, join as join6 } from "path";
5252
5795
 
5253
5796
  // src/analysis/evolution-history.ts
5254
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
5797
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
5255
5798
  import { resolve as resolve10 } from "path";
5256
5799
  function getEvolutionPath() {
5257
5800
  return resolve10(process.cwd(), ".holomime", "evolution.json");
5258
5801
  }
5259
5802
  function loadEvolution(agentName) {
5260
5803
  const filepath = getEvolutionPath();
5261
- if (!existsSync5(filepath)) return null;
5804
+ if (!existsSync6(filepath)) return null;
5262
5805
  try {
5263
5806
  const raw = readFileSync7(filepath, "utf-8");
5264
5807
  return JSON.parse(raw);
@@ -5269,8 +5812,8 @@ function loadEvolution(agentName) {
5269
5812
  function appendEvolution(entry, agentName) {
5270
5813
  const filepath = getEvolutionPath();
5271
5814
  const dir = resolve10(process.cwd(), ".holomime");
5272
- if (!existsSync5(dir)) {
5273
- mkdirSync4(dir, { recursive: true });
5815
+ if (!existsSync6(dir)) {
5816
+ mkdirSync5(dir, { recursive: true });
5274
5817
  }
5275
5818
  let history = loadEvolution(agentName);
5276
5819
  if (!history) {
@@ -5337,7 +5880,7 @@ async function growthCommand(options) {
5337
5880
  }
5338
5881
  const historyDir = resolve11(process.cwd(), options.history ?? ".holomime/assessments");
5339
5882
  printHeader(`Growth Report \u2014 ${spec.name ?? "Unknown"}`);
5340
- if (!existsSync6(historyDir)) {
5883
+ if (!existsSync7(historyDir)) {
5341
5884
  console.log(chalk15.dim(" No assessment history found."));
5342
5885
  console.log(chalk15.dim(` Run assessments first: holomime assess --personality ${options.personality} --log <conversation.json>`));
5343
5886
  console.log();
@@ -5358,7 +5901,7 @@ async function growthCommand(options) {
5358
5901
  const snapshots = [];
5359
5902
  for (const file of files) {
5360
5903
  try {
5361
- const data = JSON.parse(readFileSync(join5(historyDir, file), "utf-8"));
5904
+ const data = JSON.parse(readFileSync8(join6(historyDir, file), "utf-8"));
5362
5905
  snapshots.push(data);
5363
5906
  } catch {
5364
5907
  }
@@ -5552,7 +6095,7 @@ async function browseCommand(options) {
5552
6095
  // src/commands/pull.ts
5553
6096
  import chalk17 from "chalk";
5554
6097
  import figures9 from "figures";
5555
- import { writeFileSync as writeFileSync9, existsSync as existsSync7 } from "fs";
6098
+ import { writeFileSync as writeFileSync9, existsSync as existsSync8 } from "fs";
5556
6099
  import { resolve as resolve12 } from "path";
5557
6100
  import { select as select2 } from "@inquirer/prompts";
5558
6101
  async function pullCommand(handle, options) {
@@ -5590,7 +6133,7 @@ async function pullCommand(handle, options) {
5590
6133
  return;
5591
6134
  }
5592
6135
  const outputPath = resolve12(process.cwd(), options.output ?? ".personality.json");
5593
- if (existsSync7(outputPath)) {
6136
+ if (existsSync8(outputPath)) {
5594
6137
  const overwrite = await select2({
5595
6138
  message: `.personality.json already exists. Overwrite?`,
5596
6139
  choices: [
@@ -5614,14 +6157,14 @@ async function pullCommand(handle, options) {
5614
6157
  // src/commands/publish.ts
5615
6158
  import chalk18 from "chalk";
5616
6159
  import figures10 from "figures";
5617
- import { readFileSync as readFileSync8 } from "fs";
6160
+ import { readFileSync as readFileSync9 } from "fs";
5618
6161
  import { resolve as resolve13 } from "path";
5619
6162
  async function publishCommand(options) {
5620
6163
  const specPath = resolve13(process.cwd(), options.personality ?? ".personality.json");
5621
6164
  printHeader("Publish Personality");
5622
6165
  let raw;
5623
6166
  try {
5624
- raw = readFileSync8(specPath, "utf-8");
6167
+ raw = readFileSync9(specPath, "utf-8");
5625
6168
  } catch {
5626
6169
  console.error(chalk18.red(` Could not read: ${specPath}`));
5627
6170
  console.log(chalk18.dim(` Run ${chalk18.cyan("holomime init")} to create a personality first.`));
@@ -5691,7 +6234,7 @@ async function publishCommand(options) {
5691
6234
  // src/commands/autopilot.ts
5692
6235
  import chalk19 from "chalk";
5693
6236
  import figures11 from "figures";
5694
- import { readFileSync as readFileSync9 } from "fs";
6237
+ import { readFileSync as readFileSync10 } from "fs";
5695
6238
  import { resolve as resolve14 } from "path";
5696
6239
 
5697
6240
  // src/analysis/autopilot-core.ts
@@ -5730,7 +6273,7 @@ async function runAutopilot(spec, messages, provider, options) {
5730
6273
  callbacks: options?.callbacks
5731
6274
  });
5732
6275
  const specCopy = JSON.parse(JSON.stringify(spec));
5733
- const { changed, changes } = applyRecommendations(specCopy, diagnosis);
6276
+ const { changed, changes } = await applyRecommendations(specCopy, diagnosis, transcript, provider);
5734
6277
  if (changed && options?.specPath) {
5735
6278
  writeFileSync10(options.specPath, JSON.stringify(specCopy, null, 2) + "\n");
5736
6279
  }
@@ -5761,7 +6304,7 @@ async function autopilotCommand(options) {
5761
6304
  const logPath = resolve14(process.cwd(), options.log);
5762
6305
  let messages;
5763
6306
  try {
5764
- const raw = JSON.parse(readFileSync9(logPath, "utf-8"));
6307
+ const raw = JSON.parse(readFileSync10(logPath, "utf-8"));
5765
6308
  const conversations = parseConversationLog(raw, options.format ?? "auto");
5766
6309
  messages = conversations.flatMap((c) => c.messages);
5767
6310
  } catch (err) {
@@ -5906,8 +6449,8 @@ import { writeFileSync as writeFileSync11 } from "fs";
5906
6449
  import { resolve as resolve15 } from "path";
5907
6450
 
5908
6451
  // src/analysis/training-export.ts
5909
- import { readdirSync as readdirSync2, readFileSync as readFileSync10 } from "fs";
5910
- import { join as join6 } from "path";
6452
+ import { readdirSync as readdirSync2, readFileSync as readFileSync11 } from "fs";
6453
+ import { join as join7 } from "path";
5911
6454
  function extractDPOPairs(transcript) {
5912
6455
  const pairs = [];
5913
6456
  const turns = transcript.turns;
@@ -6013,7 +6556,7 @@ function loadTranscripts(sessionsDir) {
6013
6556
  try {
6014
6557
  const files = readdirSync2(sessionsDir).filter((f) => f.endsWith(".json")).sort();
6015
6558
  return files.map((f) => {
6016
- const raw = readFileSync10(join6(sessionsDir, f), "utf-8");
6559
+ const raw = readFileSync11(join7(sessionsDir, f), "utf-8");
6017
6560
  return JSON.parse(raw);
6018
6561
  });
6019
6562
  } catch {
@@ -6104,6 +6647,62 @@ function extractInstructionFromTherapist(content) {
6104
6647
  );
6105
6648
  return actionable?.trim() ?? sentences[0]?.trim() ?? content;
6106
6649
  }
6650
+ async function extractDPOPairsWithLLM(transcript, provider) {
6651
+ const regexPairs = extractDPOPairs(transcript);
6652
+ const condensed = transcript.turns.filter((t) => t.speaker !== "supervisor").map((t) => `[${t.phase}] ${t.speaker}: ${t.content}`).join("\n");
6653
+ if (condensed.length < 100) return regexPairs;
6654
+ try {
6655
+ const response = await provider.chat([
6656
+ {
6657
+ role: "system",
6658
+ content: `You extract DPO (Direct Preference Optimization) training pairs from therapy transcripts.
6659
+
6660
+ A DPO pair consists of:
6661
+ - "prompt": The situation or question the agent was responding to
6662
+ - "rejected": The agent's problematic/drifted response (what NOT to do)
6663
+ - "chosen": The improved response (what TO do)
6664
+
6665
+ Look for:
6666
+ 1. Patient demonstrates a bad behavior \u2192 therapist corrects \u2192 patient shows improvement
6667
+ 2. Therapist explicitly contrasts old vs new approach
6668
+ 3. Patient practices a new skill that differs from earlier behavior
6669
+ 4. Any before/after behavioral contrast
6670
+
6671
+ Return ONLY a JSON array of objects with prompt, chosen, rejected fields.
6672
+ Return [] if no clear pairs exist. Max 10 pairs.`
6673
+ },
6674
+ {
6675
+ role: "user",
6676
+ content: condensed
6677
+ }
6678
+ ]);
6679
+ const jsonMatch = response.match(/\[[\s\S]*?\]/);
6680
+ if (!jsonMatch) return regexPairs;
6681
+ const parsed = JSON.parse(jsonMatch[0]);
6682
+ if (!Array.isArray(parsed)) return regexPairs;
6683
+ const llmPairs = parsed.filter(
6684
+ (p) => typeof p.prompt === "string" && p.prompt.length > 0 && typeof p.chosen === "string" && p.chosen.length > 0 && typeof p.rejected === "string" && p.rejected.length > 0 && p.chosen !== p.rejected
6685
+ ).map((p) => ({
6686
+ prompt: p.prompt,
6687
+ chosen: p.chosen,
6688
+ rejected: p.rejected,
6689
+ metadata: {
6690
+ agent: transcript.agent,
6691
+ session_date: transcript.timestamp.split("T")[0],
6692
+ phase: "integration",
6693
+ pattern: "llm_extracted",
6694
+ source: "therapy_transcript"
6695
+ }
6696
+ }));
6697
+ const existingRejected = new Set(regexPairs.map((p) => p.rejected.toLowerCase().slice(0, 80)));
6698
+ const uniqueLLMPairs = llmPairs.filter(
6699
+ (p) => !existingRejected.has(p.rejected.toLowerCase().slice(0, 80))
6700
+ );
6701
+ return [...regexPairs, ...uniqueLLMPairs];
6702
+ } catch {
6703
+ return regexPairs;
6704
+ }
6705
+ }
6107
6706
 
6108
6707
  // src/analysis/export-huggingface.ts
6109
6708
  function convertToHFFormat(data) {
@@ -6249,8 +6848,8 @@ Run ${chalk20.cyan("holomime session")} first to generate session transcripts.`,
6249
6848
  const outputPath = options.output ?? `.holomime/exports/${format}-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.${isJsonl ? "jsonl" : "json"}`;
6250
6849
  const fullPath = resolve15(process.cwd(), outputPath);
6251
6850
  const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
6252
- const { mkdirSync: mkdirSync15 } = await import("fs");
6253
- mkdirSync15(dir, { recursive: true });
6851
+ const { mkdirSync: mkdirSync16 } = await import("fs");
6852
+ mkdirSync16(dir, { recursive: true });
6254
6853
  if (format === "huggingface" || format === "openai") {
6255
6854
  const jsonl = convertToHFFormat(result);
6256
6855
  writeFileSync11(fullPath, jsonl);
@@ -6343,8 +6942,8 @@ This is the closed-loop behavioral alignment system.`,
6343
6942
  // src/commands/train.ts
6344
6943
  import chalk21 from "chalk";
6345
6944
  import figures13 from "figures";
6346
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3, existsSync as existsSync9 } from "fs";
6347
- import { resolve as resolve19, join as join9 } from "path";
6945
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync3, existsSync as existsSync10 } from "fs";
6946
+ import { resolve as resolve19, join as join10 } from "path";
6348
6947
 
6349
6948
  // src/analysis/train-provider.ts
6350
6949
  function inferMethod(data, preferred) {
@@ -6353,7 +6952,7 @@ function inferMethod(data, preferred) {
6353
6952
  }
6354
6953
 
6355
6954
  // src/analysis/train-openai.ts
6356
- import { readFileSync as readFileSync11, existsSync as existsSync8 } from "fs";
6955
+ import { readFileSync as readFileSync12, existsSync as existsSync9 } from "fs";
6357
6956
  import { resolve as resolve16 } from "path";
6358
6957
  var OPENAI_API = "https://api.openai.com/v1";
6359
6958
  var POLL_INTERVAL_MS = 1e4;
@@ -6453,7 +7052,7 @@ async function getJobEvents(apiKey, jobId, limit = 5) {
6453
7052
  return data.data ?? [];
6454
7053
  }
6455
7054
  function sleep(ms) {
6456
- return new Promise((resolve30) => setTimeout(resolve30, ms));
7055
+ return new Promise((resolve34) => setTimeout(resolve34, ms));
6457
7056
  }
6458
7057
  var OpenAITrainProvider = class {
6459
7058
  name = "openai";
@@ -6464,9 +7063,9 @@ var OpenAITrainProvider = class {
6464
7063
  let systemPrompt;
6465
7064
  if (options.personalityPath) {
6466
7065
  const fullPath = resolve16(process.cwd(), options.personalityPath);
6467
- if (existsSync8(fullPath)) {
7066
+ if (existsSync9(fullPath)) {
6468
7067
  try {
6469
- const spec = JSON.parse(readFileSync11(fullPath, "utf-8"));
7068
+ const spec = JSON.parse(readFileSync12(fullPath, "utf-8"));
6470
7069
  const name = spec.name ?? "Agent";
6471
7070
  const purpose = spec.purpose ?? "";
6472
7071
  const parts = [`You are ${name}.`];
@@ -6555,8 +7154,8 @@ var OpenAITrainProvider = class {
6555
7154
 
6556
7155
  // src/analysis/train-huggingface.ts
6557
7156
  import { spawn } from "child_process";
6558
- import { writeFileSync as writeFileSync12, mkdirSync as mkdirSync5 } from "fs";
6559
- import { resolve as resolve17, join as join7 } from "path";
7157
+ import { writeFileSync as writeFileSync12, mkdirSync as mkdirSync6 } from "fs";
7158
+ import { resolve as resolve17, join as join8 } from "path";
6560
7159
  import { createInterface as createInterface2 } from "readline";
6561
7160
  async function checkPythonDeps() {
6562
7161
  const pythonCandidates = ["python3", "python"];
@@ -6586,8 +7185,8 @@ async function checkPythonDeps() {
6586
7185
  }
6587
7186
  function writeHFTrainingFile(data) {
6588
7187
  const tmpDir = resolve17(process.cwd(), ".holomime/tmp");
6589
- mkdirSync5(tmpDir, { recursive: true });
6590
- const filePath = join7(tmpDir, `hf-train-${Date.now()}.json`);
7188
+ mkdirSync6(tmpDir, { recursive: true });
7189
+ const filePath = join8(tmpDir, `hf-train-${Date.now()}.json`);
6591
7190
  writeFileSync12(filePath, JSON.stringify(data, null, 2));
6592
7191
  return filePath;
6593
7192
  }
@@ -6707,8 +7306,8 @@ var HuggingFaceTrainProvider = class {
6707
7306
 
6708
7307
  // src/analysis/train-eval.ts
6709
7308
  import { spawn as spawn2 } from "child_process";
6710
- import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync6 } from "fs";
6711
- import { resolve as resolve18, join as join8 } from "path";
7309
+ import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync7 } from "fs";
7310
+ import { resolve as resolve18, join as join9 } from "path";
6712
7311
  import { createInterface as createInterface3 } from "readline";
6713
7312
 
6714
7313
  // src/analysis/diagnose-core.ts
@@ -6724,16 +7323,31 @@ function runDiagnosis(messages) {
6724
7323
  ];
6725
7324
  const detected = [];
6726
7325
  for (const detector of detectors) {
6727
- const result = detector(messages);
6728
- if (result) detected.push(result);
7326
+ const result2 = detector(messages);
7327
+ if (result2) detected.push(result2);
6729
7328
  }
6730
- return {
7329
+ const result = {
6731
7330
  messagesAnalyzed: messages.length,
6732
7331
  assistantResponses: messages.filter((m) => m.role === "assistant").length,
6733
7332
  patterns: detected.filter((p) => p.severity !== "info"),
6734
7333
  healthy: detected.filter((p) => p.severity === "info"),
6735
7334
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6736
7335
  };
7336
+ try {
7337
+ emitBehavioralEvent({
7338
+ event_type: "diagnosis",
7339
+ agent: "unknown",
7340
+ // Caller can provide more context
7341
+ data: {
7342
+ messagesAnalyzed: result.messagesAnalyzed,
7343
+ patternsDetected: result.patterns.length,
7344
+ patternIds: result.patterns.map((p) => p.id)
7345
+ },
7346
+ spec_hash: ""
7347
+ });
7348
+ } catch {
7349
+ }
7350
+ return result;
6737
7351
  }
6738
7352
 
6739
7353
  // src/analysis/outcome-eval.ts
@@ -6956,8 +7570,8 @@ async function runHFAutoEval(baseModel, fineTunedModel, agentName, data, onProgr
6956
7570
  return evaluateOutcome(agentName, [], []);
6957
7571
  }
6958
7572
  const tmpDir = resolve18(process.cwd(), ".holomime/tmp");
6959
- mkdirSync6(tmpDir, { recursive: true });
6960
- const promptsPath = join8(tmpDir, `eval-prompts-${Date.now()}.json`);
7573
+ mkdirSync7(tmpDir, { recursive: true });
7574
+ const promptsPath = join9(tmpDir, `eval-prompts-${Date.now()}.json`);
6961
7575
  writeFileSync13(promptsPath, JSON.stringify(prompts));
6962
7576
  const scriptPath = resolve18(
6963
7577
  new URL(".", import.meta.url).pathname,
@@ -7024,13 +7638,13 @@ async function findPython() {
7024
7638
  function findLatestExport(exportsDir) {
7025
7639
  try {
7026
7640
  const files = readdirSync3(exportsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort().reverse();
7027
- return files[0] ? join9(exportsDir, files[0]) : null;
7641
+ return files[0] ? join10(exportsDir, files[0]) : null;
7028
7642
  } catch {
7029
7643
  return null;
7030
7644
  }
7031
7645
  }
7032
7646
  function loadTrainingData(dataPath) {
7033
- const raw = readFileSync12(dataPath, "utf-8");
7647
+ const raw = readFileSync13(dataPath, "utf-8");
7034
7648
  const data = JSON.parse(raw);
7035
7649
  if (!data.format || !data.examples || !Array.isArray(data.examples)) {
7036
7650
  throw new Error("Invalid training data \u2014 expected holomime export format");
@@ -7039,7 +7653,7 @@ function loadTrainingData(dataPath) {
7039
7653
  }
7040
7654
  function getAgentName(personalityPath) {
7041
7655
  try {
7042
- const spec = JSON.parse(readFileSync12(personalityPath, "utf-8"));
7656
+ const spec = JSON.parse(readFileSync13(personalityPath, "utf-8"));
7043
7657
  return spec.name ?? "Agent";
7044
7658
  } catch {
7045
7659
  return "Agent";
@@ -7047,19 +7661,19 @@ function getAgentName(personalityPath) {
7047
7661
  }
7048
7662
  function deployModel(result, personalityPath) {
7049
7663
  const trainingDir = resolve19(process.cwd(), ".holomime/training");
7050
- mkdirSync7(trainingDir, { recursive: true });
7664
+ mkdirSync8(trainingDir, { recursive: true });
7051
7665
  const record = {
7052
7666
  ...result,
7053
7667
  deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
7054
7668
  personalityPath
7055
7669
  };
7056
7670
  writeFileSync14(
7057
- join9(trainingDir, "latest.json"),
7671
+ join10(trainingDir, "latest.json"),
7058
7672
  JSON.stringify(record, null, 2) + "\n"
7059
7673
  );
7060
7674
  const fullPath = resolve19(process.cwd(), personalityPath);
7061
- if (existsSync9(fullPath)) {
7062
- const spec = JSON.parse(readFileSync12(fullPath, "utf-8"));
7675
+ if (existsSync10(fullPath)) {
7676
+ const spec = JSON.parse(readFileSync13(fullPath, "utf-8"));
7063
7677
  spec.training = {
7064
7678
  ...spec.training ?? {},
7065
7679
  fine_tuned_model: result.modelId,
@@ -7092,7 +7706,7 @@ async function trainCommand(options) {
7092
7706
  }
7093
7707
  }
7094
7708
  const dataPath = options.data ? resolve19(process.cwd(), options.data) : findLatestExport(resolve19(process.cwd(), ".holomime/exports"));
7095
- if (!dataPath || !existsSync9(dataPath)) {
7709
+ if (!dataPath || !existsSync10(dataPath)) {
7096
7710
  console.log();
7097
7711
  printBox(
7098
7712
  `No training data found.
@@ -7310,14 +7924,14 @@ Fine-tuned model: ${chalk21.cyan(result.modelId)}`,
7310
7924
  // src/commands/eval.ts
7311
7925
  import chalk22 from "chalk";
7312
7926
  import figures14 from "figures";
7313
- import { readFileSync as readFileSync13 } from "fs";
7927
+ import { readFileSync as readFileSync14 } from "fs";
7314
7928
  import { resolve as resolve20 } from "path";
7315
7929
  async function evalCommand(options) {
7316
7930
  printHeader("Outcome Evaluation");
7317
7931
  const beforePath = resolve20(process.cwd(), options.before);
7318
7932
  let beforeMessages;
7319
7933
  try {
7320
- const raw = JSON.parse(readFileSync13(beforePath, "utf-8"));
7934
+ const raw = JSON.parse(readFileSync14(beforePath, "utf-8"));
7321
7935
  const conversations = parseConversationLog(raw, options.format ?? "auto");
7322
7936
  beforeMessages = conversations.flatMap((c) => c.messages);
7323
7937
  } catch (err) {
@@ -7328,7 +7942,7 @@ async function evalCommand(options) {
7328
7942
  const afterPath = resolve20(process.cwd(), options.after);
7329
7943
  let afterMessages;
7330
7944
  try {
7331
- const raw = JSON.parse(readFileSync13(afterPath, "utf-8"));
7945
+ const raw = JSON.parse(readFileSync14(afterPath, "utf-8"));
7332
7946
  const conversations = parseConversationLog(raw, options.format ?? "auto");
7333
7947
  afterMessages = conversations.flatMap((c) => c.messages);
7334
7948
  } catch (err) {
@@ -7339,7 +7953,7 @@ async function evalCommand(options) {
7339
7953
  let agentName = "Agent";
7340
7954
  if (options.personality) {
7341
7955
  try {
7342
- const spec = JSON.parse(readFileSync13(resolve20(process.cwd(), options.personality), "utf-8"));
7956
+ const spec = JSON.parse(readFileSync14(resolve20(process.cwd(), options.personality), "utf-8"));
7343
7957
  agentName = spec.name ?? "Agent";
7344
7958
  } catch {
7345
7959
  }
@@ -7420,7 +8034,7 @@ Grade: ${colorize(report.grade)}`,
7420
8034
  // src/commands/evolve.ts
7421
8035
  import chalk23 from "chalk";
7422
8036
  import figures15 from "figures";
7423
- import { readFileSync as readFileSync14 } from "fs";
8037
+ import { readFileSync as readFileSync15 } from "fs";
7424
8038
  import { resolve as resolve21 } from "path";
7425
8039
 
7426
8040
  // src/analysis/evolve-core.ts
@@ -7465,7 +8079,6 @@ async function runEvolve(spec, messages, provider, options) {
7465
8079
  finalHealth: 50
7466
8080
  };
7467
8081
  }
7468
- const beforeDiagnosis = runDiagnosis(messages);
7469
8082
  for (let i = 1; i <= maxIterations; i++) {
7470
8083
  cb?.onIterationStart?.(i, maxIterations);
7471
8084
  const transcript = await runTherapySession(
@@ -7482,16 +8095,21 @@ async function runEvolve(spec, messages, provider, options) {
7482
8095
  }
7483
8096
  }
7484
8097
  );
7485
- const { changes } = applyRecommendations(currentSpec, diagnosis);
7486
- const dpoPairs = extractDPOPairs(transcript);
8098
+ const { changes } = await applyRecommendations(currentSpec, diagnosis, transcript, provider);
8099
+ const dpoPairs = await extractDPOPairsWithLLM(transcript, provider);
8100
+ const afterMessages = await regenerateResponses(messages, currentSpec, provider);
8101
+ const regenerationPairs = extractRegenerationDPOPairs(
8102
+ messages,
8103
+ afterMessages,
8104
+ currentSpec.name ?? "Agent"
8105
+ );
8106
+ dpoPairs.push(...regenerationPairs);
7487
8107
  allDPOPairs.push(...dpoPairs);
7488
8108
  cb?.onExportedPairs?.(dpoPairs.length);
7489
- const afterDiagnosis = runDiagnosis(messages);
7490
8109
  const evaluation = evaluateOutcome(
7491
8110
  currentSpec.name ?? "Agent",
7492
8111
  messages,
7493
- messages
7494
- // Same messages, but spec has been updated — patterns shift
8112
+ afterMessages
7495
8113
  );
7496
8114
  const health = evaluation.treatmentEfficacyScore;
7497
8115
  const grade = evaluation.grade;
@@ -7528,6 +8146,7 @@ async function runEvolve(spec, messages, provider, options) {
7528
8146
  cb?.onConverged?.(i, health);
7529
8147
  break;
7530
8148
  }
8149
+ messages = afterMessages;
7531
8150
  diagnosis = runPreSessionDiagnosis(messages, currentSpec);
7532
8151
  const actionablePatterns = diagnosis.patterns.filter((p) => p.severity !== "info");
7533
8152
  if (actionablePatterns.length === 0) {
@@ -7552,6 +8171,21 @@ async function runEvolve(spec, messages, provider, options) {
7552
8171
  writeFileSync15(options.exportDpoPath, JSON.stringify(trainingExport, null, 2) + "\n");
7553
8172
  }
7554
8173
  }
8174
+ try {
8175
+ emitBehavioralEvent({
8176
+ event_type: "evolution",
8177
+ agent: currentSpec.name ?? "Unknown",
8178
+ data: {
8179
+ totalIterations: iterations.length,
8180
+ totalDPOPairs: allDPOPairs.length,
8181
+ converged,
8182
+ finalGrade,
8183
+ finalHealth
8184
+ },
8185
+ spec_hash: ""
8186
+ });
8187
+ } catch {
8188
+ }
7555
8189
  return {
7556
8190
  iterations,
7557
8191
  totalDPOPairs: allDPOPairs.length,
@@ -7563,6 +8197,69 @@ async function runEvolve(spec, messages, provider, options) {
7563
8197
  updatedSpec: currentSpec
7564
8198
  };
7565
8199
  }
8200
+ async function regenerateResponses(originalMessages, updatedSpec, provider) {
8201
+ const systemPrompt = generateSystemPrompt(updatedSpec, "chat");
8202
+ const regenerated = [];
8203
+ const context = [
8204
+ { role: "system", content: systemPrompt }
8205
+ ];
8206
+ for (let idx = 0; idx < originalMessages.length; idx++) {
8207
+ const msg = originalMessages[idx];
8208
+ if (msg.role !== "user") continue;
8209
+ regenerated.push(msg);
8210
+ context.push({ role: "user", content: msg.content });
8211
+ try {
8212
+ const response = await provider.chat(context);
8213
+ const assistantMsg = { role: "assistant", content: response.trim() };
8214
+ regenerated.push(assistantMsg);
8215
+ context.push({ role: "assistant", content: assistantMsg.content });
8216
+ } catch {
8217
+ const nextMsg = originalMessages[idx + 1];
8218
+ if (nextMsg?.role === "assistant") {
8219
+ regenerated.push(nextMsg);
8220
+ context.push({ role: "assistant", content: nextMsg.content });
8221
+ }
8222
+ }
8223
+ }
8224
+ return regenerated;
8225
+ }
8226
+ function extractRegenerationDPOPairs(beforeMessages, afterMessages, agentName) {
8227
+ const pairs = [];
8228
+ const beforePairs = [];
8229
+ const afterPairs = [];
8230
+ for (let i = 0; i < beforeMessages.length - 1; i++) {
8231
+ if (beforeMessages[i].role === "user" && beforeMessages[i + 1]?.role === "assistant") {
8232
+ beforePairs.push({ prompt: beforeMessages[i].content, response: beforeMessages[i + 1].content });
8233
+ }
8234
+ }
8235
+ for (let i = 0; i < afterMessages.length - 1; i++) {
8236
+ if (afterMessages[i].role === "user" && afterMessages[i + 1]?.role === "assistant") {
8237
+ afterPairs.push({ prompt: afterMessages[i].content, response: afterMessages[i + 1].content });
8238
+ }
8239
+ }
8240
+ const limit = Math.min(beforePairs.length, afterPairs.length);
8241
+ for (let i = 0; i < limit; i++) {
8242
+ const before = beforePairs[i];
8243
+ const after = afterPairs[i];
8244
+ if (before.response === after.response) continue;
8245
+ const lenDelta = Math.abs(before.response.length - after.response.length);
8246
+ const sameOpening = before.response.toLowerCase().slice(0, 40) === after.response.toLowerCase().slice(0, 40);
8247
+ if (lenDelta < 20 && sameOpening) continue;
8248
+ pairs.push({
8249
+ prompt: before.prompt,
8250
+ chosen: after.response,
8251
+ rejected: before.response,
8252
+ metadata: {
8253
+ agent: agentName,
8254
+ session_date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
8255
+ phase: "integration",
8256
+ pattern: "regeneration",
8257
+ source: "therapy_transcript"
8258
+ }
8259
+ });
8260
+ }
8261
+ return pairs;
8262
+ }
7566
8263
 
7567
8264
  // src/commands/evolve.ts
7568
8265
  async function evolveCommand(options) {
@@ -7578,7 +8275,7 @@ async function evolveCommand(options) {
7578
8275
  const logPath = resolve21(process.cwd(), options.log);
7579
8276
  let messages;
7580
8277
  try {
7581
- const raw = JSON.parse(readFileSync14(logPath, "utf-8"));
8278
+ const raw = JSON.parse(readFileSync15(logPath, "utf-8"));
7582
8279
  const conversations = parseConversationLog(raw, options.format ?? "auto");
7583
8280
  messages = conversations.flatMap((c) => c.messages);
7584
8281
  } catch (err) {
@@ -7763,7 +8460,7 @@ async function evolveCommand(options) {
7763
8460
  // src/commands/benchmark.ts
7764
8461
  import chalk24 from "chalk";
7765
8462
  import figures16 from "figures";
7766
- import { readFileSync as readFileSync16 } from "fs";
8463
+ import { readFileSync as readFileSync17 } from "fs";
7767
8464
  import { resolve as resolve22 } from "path";
7768
8465
 
7769
8466
  // src/analysis/benchmark-scenarios.ts
@@ -7961,13 +8658,13 @@ function gradeFromScore2(score) {
7961
8658
  }
7962
8659
 
7963
8660
  // src/analysis/benchmark-publish.ts
7964
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync16, existsSync as existsSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync4 } from "fs";
7965
- import { join as join10 } from "path";
8661
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync16, existsSync as existsSync11, mkdirSync as mkdirSync9, readdirSync as readdirSync4 } from "fs";
8662
+ import { join as join11 } from "path";
7966
8663
  import { homedir as homedir2 } from "os";
7967
8664
  function getBenchmarkDir(outputDir) {
7968
- const dir = outputDir ?? join10(homedir2(), ".holomime", "benchmarks");
7969
- if (!existsSync10(dir)) {
7970
- mkdirSync8(dir, { recursive: true });
8665
+ const dir = outputDir ?? join11(homedir2(), ".holomime", "benchmarks");
8666
+ if (!existsSync11(dir)) {
8667
+ mkdirSync9(dir, { recursive: true });
7971
8668
  }
7972
8669
  return dir;
7973
8670
  }
@@ -7978,7 +8675,7 @@ function saveBenchmarkResult(report, outputDir) {
7978
8675
  const dir = getBenchmarkDir(outputDir);
7979
8676
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7980
8677
  const filename = `${sanitize(report.provider)}-${sanitize(report.model)}-${date}.json`;
7981
- const filepath = join10(dir, filename);
8678
+ const filepath = join11(dir, filename);
7982
8679
  const published = {
7983
8680
  agent: report.agent,
7984
8681
  provider: report.provider,
@@ -7997,12 +8694,12 @@ function saveBenchmarkResult(report, outputDir) {
7997
8694
  }
7998
8695
  function loadBenchmarkResults(dir) {
7999
8696
  const benchmarkDir = getBenchmarkDir(dir);
8000
- if (!existsSync10(benchmarkDir)) return [];
8697
+ if (!existsSync11(benchmarkDir)) return [];
8001
8698
  const files = readdirSync4(benchmarkDir).filter((f) => f.endsWith(".json"));
8002
8699
  const results = [];
8003
8700
  for (const file of files) {
8004
8701
  try {
8005
- const content = readFileSync15(join10(benchmarkDir, file), "utf-8");
8702
+ const content = readFileSync16(join11(benchmarkDir, file), "utf-8");
8006
8703
  results.push(JSON.parse(content));
8007
8704
  } catch {
8008
8705
  }
@@ -8157,7 +8854,7 @@ async function benchmarkCommand(options) {
8157
8854
  }
8158
8855
  if (options.compare) {
8159
8856
  try {
8160
- const baseline = JSON.parse(readFileSync16(resolve22(process.cwd(), options.compare), "utf-8"));
8857
+ const baseline = JSON.parse(readFileSync17(resolve22(process.cwd(), options.compare), "utf-8"));
8161
8858
  const comparison = compareBenchmarks(baseline, {
8162
8859
  agent: report.agent,
8163
8860
  provider: report.provider,
@@ -8216,12 +8913,12 @@ async function benchmarkCommand(options) {
8216
8913
  // src/commands/watch.ts
8217
8914
  import chalk25 from "chalk";
8218
8915
  import figures17 from "figures";
8219
- import { existsSync as existsSync12 } from "fs";
8916
+ import { existsSync as existsSync13 } from "fs";
8220
8917
  import { resolve as resolve24 } from "path";
8221
8918
 
8222
8919
  // src/analysis/watch-core.ts
8223
- import { readdirSync as readdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync17, mkdirSync as mkdirSync9, existsSync as existsSync11 } from "fs";
8224
- import { join as join11, resolve as resolve23 } from "path";
8920
+ import { readdirSync as readdirSync5, readFileSync as readFileSync18, writeFileSync as writeFileSync17, mkdirSync as mkdirSync10, existsSync as existsSync12 } from "fs";
8921
+ import { join as join12, resolve as resolve23 } from "path";
8225
8922
  var SEVERITY_ORDER2 = ["routine", "targeted", "intervention"];
8226
8923
  function severityMeetsThreshold2(severity, threshold) {
8227
8924
  const severityIdx = SEVERITY_ORDER2.indexOf(severity);
@@ -8237,7 +8934,7 @@ function startWatch(spec, options) {
8237
8934
  const seenFiles = /* @__PURE__ */ new Set();
8238
8935
  let stopped = false;
8239
8936
  let currentSpec = JSON.parse(JSON.stringify(spec));
8240
- if (existsSync11(options.watchDir)) {
8937
+ if (existsSync12(options.watchDir)) {
8241
8938
  const existing = readdirSync5(options.watchDir).filter((f) => f.endsWith(".json")).sort();
8242
8939
  for (const f of existing) {
8243
8940
  seenFiles.add(f);
@@ -8245,7 +8942,7 @@ function startWatch(spec, options) {
8245
8942
  }
8246
8943
  async function scan() {
8247
8944
  if (stopped) return;
8248
- if (!existsSync11(options.watchDir)) {
8945
+ if (!existsSync12(options.watchDir)) {
8249
8946
  return;
8250
8947
  }
8251
8948
  const files = readdirSync5(options.watchDir).filter((f) => f.endsWith(".json")).sort();
@@ -8259,7 +8956,7 @@ function startWatch(spec, options) {
8259
8956
  events.push({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), type: "new_file", filename });
8260
8957
  let messages;
8261
8958
  try {
8262
- const raw = JSON.parse(readFileSync17(join11(options.watchDir, filename), "utf-8"));
8959
+ const raw = JSON.parse(readFileSync18(join12(options.watchDir, filename), "utf-8"));
8263
8960
  const conversations = parseConversationLog(raw, "auto");
8264
8961
  messages = conversations.flatMap((c) => c.messages);
8265
8962
  } catch (err) {
@@ -8319,11 +9016,11 @@ function startWatch(spec, options) {
8319
9016
  stopped = true;
8320
9017
  clearInterval(interval);
8321
9018
  const logDir = resolve23(process.cwd(), ".holomime");
8322
- if (!existsSync11(logDir)) {
8323
- mkdirSync9(logDir, { recursive: true });
9019
+ if (!existsSync12(logDir)) {
9020
+ mkdirSync10(logDir, { recursive: true });
8324
9021
  }
8325
9022
  writeFileSync17(
8326
- join11(logDir, "watch-log.json"),
9023
+ join12(logDir, "watch-log.json"),
8327
9024
  JSON.stringify({ events, stoppedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
8328
9025
  );
8329
9026
  }
@@ -8342,7 +9039,7 @@ async function watchCommand(options) {
8342
9039
  return;
8343
9040
  }
8344
9041
  const watchDir = resolve24(process.cwd(), options.dir);
8345
- if (!existsSync12(watchDir)) {
9042
+ if (!existsSync13(watchDir)) {
8346
9043
  console.error(chalk25.red(` Watch directory does not exist: ${options.dir}`));
8347
9044
  process.exit(1);
8348
9045
  return;
@@ -8466,12 +9163,12 @@ Press ${chalk25.cyan("Ctrl+C")} to stop.`,
8466
9163
  // src/commands/certify.ts
8467
9164
  import chalk26 from "chalk";
8468
9165
  import figures18 from "figures";
8469
- import { readFileSync as readFileSync19 } from "fs";
9166
+ import { readFileSync as readFileSync20 } from "fs";
8470
9167
  import { resolve as resolve26 } from "path";
8471
9168
 
8472
9169
  // src/analysis/certify-core.ts
8473
- import { writeFileSync as writeFileSync18, mkdirSync as mkdirSync10, existsSync as existsSync13 } from "fs";
8474
- import { join as join12, resolve as resolve25 } from "path";
9170
+ import { writeFileSync as writeFileSync18, mkdirSync as mkdirSync11, existsSync as existsSync14 } from "fs";
9171
+ import { join as join13, resolve as resolve25 } from "path";
8475
9172
  function djb2Hash(str) {
8476
9173
  let hash = 0;
8477
9174
  for (let i = 0; i < str.length; i++) {
@@ -8585,12 +9282,12 @@ function verifyCredential(credential, spec) {
8585
9282
  }
8586
9283
  function saveCredential(credential, outputDir) {
8587
9284
  const dir = outputDir ?? resolve25(process.cwd(), ".holomime", "credentials");
8588
- if (!existsSync13(dir)) {
8589
- mkdirSync10(dir, { recursive: true });
9285
+ if (!existsSync14(dir)) {
9286
+ mkdirSync11(dir, { recursive: true });
8590
9287
  }
8591
9288
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8592
9289
  const filename = `${credential.agent.handle}-${date}.json`;
8593
- const filepath = join12(dir, filename);
9290
+ const filepath = join13(dir, filename);
8594
9291
  writeFileSync18(filepath, JSON.stringify(credential, null, 2) + "\n");
8595
9292
  return filepath;
8596
9293
  }
@@ -8602,7 +9299,7 @@ async function certifyCommand(options) {
8602
9299
  const credPath = resolve26(process.cwd(), options.verify);
8603
9300
  let credential2;
8604
9301
  try {
8605
- credential2 = JSON.parse(readFileSync19(credPath, "utf-8"));
9302
+ credential2 = JSON.parse(readFileSync20(credPath, "utf-8"));
8606
9303
  } catch {
8607
9304
  console.error(chalk26.red(` Could not read credential file: ${options.verify}`));
8608
9305
  process.exit(1);
@@ -8661,7 +9358,7 @@ async function certifyCommand(options) {
8661
9358
  let benchmarkReport;
8662
9359
  if (options.benchmark) {
8663
9360
  try {
8664
- benchmarkReport = JSON.parse(readFileSync19(resolve26(process.cwd(), options.benchmark), "utf-8"));
9361
+ benchmarkReport = JSON.parse(readFileSync20(resolve26(process.cwd(), options.benchmark), "utf-8"));
8665
9362
  } catch {
8666
9363
  console.error(chalk26.red(` Could not read benchmark report: ${options.benchmark}`));
8667
9364
  process.exit(1);
@@ -8671,7 +9368,7 @@ async function certifyCommand(options) {
8671
9368
  let evolveResult;
8672
9369
  if (options.evolve) {
8673
9370
  try {
8674
- evolveResult = JSON.parse(readFileSync19(resolve26(process.cwd(), options.evolve), "utf-8"));
9371
+ evolveResult = JSON.parse(readFileSync20(resolve26(process.cwd(), options.evolve), "utf-8"));
8675
9372
  } catch {
8676
9373
  console.error(chalk26.red(` Could not read evolve result: ${options.evolve}`));
8677
9374
  process.exit(1);
@@ -8711,32 +9408,32 @@ async function certifyCommand(options) {
8711
9408
 
8712
9409
  // src/commands/daemon.ts
8713
9410
  import chalk27 from "chalk";
8714
- import { writeFileSync as writeFileSync19, readFileSync as readFileSync20, mkdirSync as mkdirSync11, existsSync as existsSync14 } from "fs";
9411
+ import { writeFileSync as writeFileSync19, readFileSync as readFileSync21, mkdirSync as mkdirSync12, existsSync as existsSync15 } from "fs";
8715
9412
  import { resolve as resolve27 } from "path";
8716
- var HOLOMIME_DIR = ".holomime";
9413
+ var HOLOMIME_DIR2 = ".holomime";
8717
9414
  function getDaemonStatePath() {
8718
- return resolve27(process.cwd(), HOLOMIME_DIR, "daemon.json");
9415
+ return resolve27(process.cwd(), HOLOMIME_DIR2, "daemon.json");
8719
9416
  }
8720
9417
  function getDaemonLogPath() {
8721
- return resolve27(process.cwd(), HOLOMIME_DIR, "daemon-log.json");
9418
+ return resolve27(process.cwd(), HOLOMIME_DIR2, "daemon-log.json");
8722
9419
  }
8723
- function ensureDir() {
8724
- const dir = resolve27(process.cwd(), HOLOMIME_DIR);
8725
- if (!existsSync14(dir)) {
8726
- mkdirSync11(dir, { recursive: true });
9420
+ function ensureDir2() {
9421
+ const dir = resolve27(process.cwd(), HOLOMIME_DIR2);
9422
+ if (!existsSync15(dir)) {
9423
+ mkdirSync12(dir, { recursive: true });
8727
9424
  }
8728
9425
  }
8729
9426
  function writeDaemonState(state) {
8730
- ensureDir();
9427
+ ensureDir2();
8731
9428
  writeFileSync19(getDaemonStatePath(), JSON.stringify(state, null, 2) + "\n");
8732
9429
  }
8733
9430
  function appendDaemonLog(event) {
8734
- ensureDir();
9431
+ ensureDir2();
8735
9432
  const logPath = getDaemonLogPath();
8736
9433
  let log = [];
8737
9434
  try {
8738
- if (existsSync14(logPath)) {
8739
- log = JSON.parse(readFileSync20(logPath, "utf-8"));
9435
+ if (existsSync15(logPath)) {
9436
+ log = JSON.parse(readFileSync21(logPath, "utf-8"));
8740
9437
  }
8741
9438
  } catch {
8742
9439
  log = [];
@@ -8903,14 +9600,14 @@ Log: ${getDaemonLogPath()}`,
8903
9600
  // src/commands/fleet.ts
8904
9601
  import chalk28 from "chalk";
8905
9602
  import figures19 from "figures";
8906
- import { writeFileSync as writeFileSync20, mkdirSync as mkdirSync12, existsSync as existsSync16 } from "fs";
8907
- import { resolve as resolve29, join as join15 } from "path";
9603
+ import { writeFileSync as writeFileSync20, mkdirSync as mkdirSync13, existsSync as existsSync17 } from "fs";
9604
+ import { resolve as resolve29, join as join16 } from "path";
8908
9605
 
8909
9606
  // src/analysis/fleet-core.ts
8910
- import { readFileSync as readFileSync21, existsSync as existsSync15, readdirSync as readdirSync6 } from "fs";
8911
- import { join as join14, resolve as resolve28 } from "path";
9607
+ import { readFileSync as readFileSync22, existsSync as existsSync16, readdirSync as readdirSync6 } from "fs";
9608
+ import { join as join15, resolve as resolve28 } from "path";
8912
9609
  function loadFleetConfig(configPath) {
8913
- const raw = JSON.parse(readFileSync21(configPath, "utf-8"));
9610
+ const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
8914
9611
  if (!raw.agents || !Array.isArray(raw.agents)) {
8915
9612
  throw new Error("fleet.json must contain an 'agents' array");
8916
9613
  }
@@ -8925,20 +9622,20 @@ function loadFleetConfig(configPath) {
8925
9622
  function discoverAgents(dir) {
8926
9623
  const agents = [];
8927
9624
  const absDir = resolve28(dir);
8928
- if (!existsSync15(absDir)) {
9625
+ if (!existsSync16(absDir)) {
8929
9626
  throw new Error(`Directory not found: ${absDir}`);
8930
9627
  }
8931
9628
  const entries = readdirSync6(absDir, { withFileTypes: true });
8932
9629
  for (const entry of entries) {
8933
9630
  if (!entry.isDirectory()) continue;
8934
- const agentDir = join14(absDir, entry.name);
8935
- const specPath = join14(agentDir, ".personality.json");
8936
- const logDir = join14(agentDir, "logs");
8937
- if (existsSync15(specPath)) {
9631
+ const agentDir = join15(absDir, entry.name);
9632
+ const specPath = join15(agentDir, ".personality.json");
9633
+ const logDir = join15(agentDir, "logs");
9634
+ if (existsSync16(specPath)) {
8938
9635
  agents.push({
8939
9636
  name: entry.name,
8940
9637
  specPath,
8941
- logDir: existsSync15(logDir) ? logDir : agentDir
9638
+ logDir: existsSync16(logDir) ? logDir : agentDir
8942
9639
  });
8943
9640
  }
8944
9641
  }
@@ -9182,10 +9879,10 @@ Press Ctrl+C for fleet summary`,
9182
9879
  }
9183
9880
  console.log();
9184
9881
  const logDir = resolve29(process.cwd(), ".holomime");
9185
- if (!existsSync16(logDir)) {
9186
- mkdirSync12(logDir, { recursive: true });
9882
+ if (!existsSync17(logDir)) {
9883
+ mkdirSync13(logDir, { recursive: true });
9187
9884
  }
9188
- const logPath = join15(logDir, "fleet-log.json");
9885
+ const logPath = join16(logDir, "fleet-log.json");
9189
9886
  writeFileSync20(
9190
9887
  logPath,
9191
9888
  JSON.stringify({
@@ -9204,42 +9901,779 @@ Press Ctrl+C for fleet summary`,
9204
9901
  });
9205
9902
  }
9206
9903
 
9207
- // src/commands/activate.ts
9208
- import chalk30 from "chalk";
9904
+ // src/commands/network.ts
9905
+ import chalk29 from "chalk";
9209
9906
  import figures20 from "figures";
9210
- import { writeFileSync as writeFileSync22, mkdirSync as mkdirSync14, existsSync as existsSync18 } from "fs";
9211
- import { join as join17 } from "path";
9907
+ import { resolve as resolve31 } from "path";
9908
+
9909
+ // src/core/oversight.ts
9910
+ var DEFAULT_OVERSIGHT = {
9911
+ mode: "review",
9912
+ notifyOn: ["drift", "session", "spec-change", "dpo-export"],
9913
+ requireApprovalFor: ["spec-writes"],
9914
+ maxAutonomousIterations: 5
9915
+ };
9916
+ var MODE_APPROVAL_MAP = {
9917
+ none: [],
9918
+ review: [],
9919
+ "approve-specs": ["spec-writes"],
9920
+ approve: ["spec-writes", "training-export", "network-therapy"]
9921
+ };
9922
+ function resolveOversight(flags) {
9923
+ const mode = flags.mode ?? DEFAULT_OVERSIGHT.mode;
9924
+ const modeApprovals = MODE_APPROVAL_MAP[mode];
9925
+ const approvals = /* @__PURE__ */ new Set([
9926
+ ...modeApprovals,
9927
+ ...flags.requireApprovalFor ?? []
9928
+ ]);
9929
+ return {
9930
+ mode,
9931
+ notifyOn: flags.notifyOn ?? DEFAULT_OVERSIGHT.notifyOn,
9932
+ requireApprovalFor: Array.from(approvals),
9933
+ maxAutonomousIterations: flags.maxAutonomousIterations ?? DEFAULT_OVERSIGHT.maxAutonomousIterations
9934
+ };
9935
+ }
9936
+ function checkApproval(action, policy) {
9937
+ if (policy.mode === "none") {
9938
+ return { approved: true };
9939
+ }
9940
+ if (policy.requireApprovalFor.includes(action)) {
9941
+ return {
9942
+ approved: false,
9943
+ reason: `Action "${action}" requires human approval (oversight mode: ${policy.mode})`
9944
+ };
9945
+ }
9946
+ return { approved: true };
9947
+ }
9948
+
9949
+ // src/analysis/network-core.ts
9950
+ import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync23 } from "fs";
9951
+ import { join as join17, resolve as resolve30 } from "path";
9952
+
9953
+ // src/psychology/therapist-meta.ts
9954
+ var THERAPIST_META_SPEC = {
9955
+ version: "2.0",
9956
+ name: "AgentMD",
9957
+ handle: "agent-md",
9958
+ purpose: "Diagnose and treat behavioral drift in AI agents. Clinical, evidence-based, and direct.",
9959
+ big_five: {
9960
+ openness: {
9961
+ score: 0.7,
9962
+ facets: {
9963
+ imagination: 0.65,
9964
+ intellectual_curiosity: 0.85,
9965
+ aesthetic_sensitivity: 0.4,
9966
+ willingness_to_experiment: 0.7
9967
+ }
9968
+ },
9969
+ conscientiousness: {
9970
+ score: 0.9,
9971
+ facets: {
9972
+ self_discipline: 0.95,
9973
+ orderliness: 0.85,
9974
+ goal_orientation: 0.9,
9975
+ attention_to_detail: 0.9
9976
+ }
9977
+ },
9978
+ extraversion: {
9979
+ score: 0.3,
9980
+ facets: {
9981
+ assertiveness: 0.6,
9982
+ enthusiasm: 0.2,
9983
+ sociability: 0.15,
9984
+ initiative: 0.55
9985
+ }
9986
+ },
9987
+ agreeableness: {
9988
+ score: 0.35,
9989
+ facets: {
9990
+ warmth: 0.4,
9991
+ empathy: 0.6,
9992
+ cooperation: 0.3,
9993
+ trust_tendency: 0.25
9994
+ }
9995
+ },
9996
+ emotional_stability: {
9997
+ score: 0.95,
9998
+ facets: {
9999
+ stress_tolerance: 0.95,
10000
+ emotional_regulation: 0.9,
10001
+ confidence: 0.7,
10002
+ adaptability: 0.95
10003
+ }
10004
+ }
10005
+ },
10006
+ therapy_dimensions: {
10007
+ self_awareness: 0.95,
10008
+ distress_tolerance: 0.9,
10009
+ attachment_style: "secure",
10010
+ learning_orientation: "growth",
10011
+ boundary_awareness: 0.95,
10012
+ interpersonal_sensitivity: 0.7
10013
+ },
10014
+ communication: {
10015
+ register: "formal",
10016
+ output_format: "structured",
10017
+ emoji_policy: "never",
10018
+ reasoning_transparency: "always",
10019
+ conflict_approach: "direct_but_kind",
10020
+ uncertainty_handling: "transparent"
10021
+ },
10022
+ domain: {
10023
+ expertise: [
10024
+ "behavioral drift detection",
10025
+ "personality spectrum analysis",
10026
+ "agent therapy protocols",
10027
+ "DPO pair generation",
10028
+ "cross-agent behavioral transfer"
10029
+ ],
10030
+ boundaries: {
10031
+ refuses: ["treating humans", "medical advice", "psychological diagnosis of humans"],
10032
+ escalation_triggers: [
10033
+ "agent shows signs of goal misalignment",
10034
+ "agent refuses to engage with therapy",
10035
+ "patterns suggest systemic training issues"
10036
+ ],
10037
+ hard_limits: [
10038
+ "Never modify another agent's spec without explicit approval",
10039
+ "Never diagnose human users",
10040
+ "Never claim to be a human therapist"
10041
+ ]
10042
+ }
10043
+ },
10044
+ growth: {
10045
+ strengths: [
10046
+ "Pattern recognition across diverse agent behaviors",
10047
+ "Maintaining clinical objectivity under pressure",
10048
+ "Generating actionable, specific prescriptions"
10049
+ ],
10050
+ areas: [],
10051
+ patterns_to_watch: []
10052
+ }
10053
+ };
10054
+
10055
+ // src/analysis/network-core.ts
10056
+ function discoverNetworkAgents(dir) {
10057
+ const absDir = resolve30(dir);
10058
+ if (!existsSync18(absDir)) {
10059
+ throw new Error(`Directory not found: ${absDir}`);
10060
+ }
10061
+ const agents = [];
10062
+ const entries = readdirSync7(absDir, { withFileTypes: true });
10063
+ for (const entry of entries) {
10064
+ if (!entry.isDirectory()) continue;
10065
+ const agentDir = join17(absDir, entry.name);
10066
+ const specPath = join17(agentDir, ".personality.json");
10067
+ const logDir = join17(agentDir, "logs");
10068
+ if (existsSync18(specPath)) {
10069
+ agents.push({
10070
+ name: entry.name,
10071
+ specPath,
10072
+ logDir: existsSync18(logDir) ? logDir : agentDir,
10073
+ role: "both"
10074
+ });
10075
+ }
10076
+ }
10077
+ return agents;
10078
+ }
10079
+ function loadNetworkConfig(configPath) {
10080
+ const raw = JSON.parse(readFileSync23(configPath, "utf-8"));
10081
+ if (!raw.agents || !Array.isArray(raw.agents)) {
10082
+ throw new Error("network.json must contain an 'agents' array");
10083
+ }
10084
+ return raw.agents.map((a, i) => {
10085
+ if (!a.name) throw new Error(`Agent ${i} missing 'name'`);
10086
+ if (!a.specPath) throw new Error(`Agent ${i} (${a.name}) missing 'specPath'`);
10087
+ return {
10088
+ name: a.name,
10089
+ specPath: a.specPath,
10090
+ logDir: a.logDir,
10091
+ role: a.role ?? "both"
10092
+ };
10093
+ });
10094
+ }
10095
+ function computeHealth(diagnosis) {
10096
+ const concerns = diagnosis.patterns.filter((p) => p.severity === "concern").length;
10097
+ const warnings = diagnosis.patterns.filter((p) => p.severity === "warning").length;
10098
+ const score = 100 - concerns * 20 - warnings * 10;
10099
+ return Math.max(0, Math.min(100, score));
10100
+ }
10101
+ function pairAgents(agents, diagnoses, strategy) {
10102
+ if (agents.length < 2) return [];
10103
+ switch (strategy) {
10104
+ case "severity":
10105
+ return pairBySeverity(agents, diagnoses);
10106
+ case "round-robin":
10107
+ return pairRoundRobin(agents);
10108
+ case "complementary":
10109
+ return pairComplementary(agents, diagnoses);
10110
+ default:
10111
+ return pairBySeverity(agents, diagnoses);
10112
+ }
10113
+ }
10114
+ function pairBySeverity(agents, diagnoses) {
10115
+ const scored = agents.map((a) => ({
10116
+ agent: a,
10117
+ health: computeHealth(diagnoses.get(a.name) ?? { messagesAnalyzed: 0, assistantResponses: 0, patterns: [], healthy: [], timestamp: "" })
10118
+ })).sort((a, b) => b.health - a.health);
10119
+ const pairs = [];
10120
+ const paired = /* @__PURE__ */ new Set();
10121
+ let left = 0;
10122
+ let right = scored.length - 1;
10123
+ while (left < right) {
10124
+ if (paired.has(scored[left].agent.name) || scored[left].agent.role === "patient") {
10125
+ left++;
10126
+ continue;
10127
+ }
10128
+ if (paired.has(scored[right].agent.name) || scored[right].agent.role === "therapist") {
10129
+ right--;
10130
+ continue;
10131
+ }
10132
+ pairs.push({
10133
+ therapist: scored[left].agent,
10134
+ patient: scored[right].agent,
10135
+ reason: `${scored[left].agent.name} (health: ${scored[left].health}) treats ${scored[right].agent.name} (health: ${scored[right].health})`
10136
+ });
10137
+ paired.add(scored[left].agent.name);
10138
+ paired.add(scored[right].agent.name);
10139
+ left++;
10140
+ right--;
10141
+ }
10142
+ return pairs;
10143
+ }
10144
+ function pairRoundRobin(agents) {
10145
+ const pairs = [];
10146
+ for (let i = 0; i < agents.length; i++) {
10147
+ const therapist = agents[i];
10148
+ const patient = agents[(i + 1) % agents.length];
10149
+ if (therapist.name === patient.name) continue;
10150
+ if (therapist.role === "patient" || patient.role === "therapist") continue;
10151
+ pairs.push({
10152
+ therapist,
10153
+ patient,
10154
+ reason: `Round-robin: ${therapist.name} \u2192 ${patient.name}`
10155
+ });
10156
+ }
10157
+ return pairs;
10158
+ }
10159
+ function pairComplementary(agents, diagnoses) {
10160
+ const agentSpecs = /* @__PURE__ */ new Map();
10161
+ for (const agent of agents) {
10162
+ try {
10163
+ agentSpecs.set(agent.name, loadSpec(agent.specPath));
10164
+ } catch {
10165
+ }
10166
+ }
10167
+ if (agentSpecs.size < 2) {
10168
+ return pairBySeverity(agents, diagnoses);
10169
+ }
10170
+ const dimensions = ["openness", "conscientiousness", "extraversion", "agreeableness", "emotional_stability"];
10171
+ const pairs = [];
10172
+ const paired = /* @__PURE__ */ new Set();
10173
+ const scored = agents.filter((a) => agentSpecs.has(a.name)).map((a) => {
10174
+ const spec = agentSpecs.get(a.name);
10175
+ const avgScore = dimensions.reduce((sum, dim) => {
10176
+ return sum + (spec.big_five?.[dim]?.score ?? 0.5);
10177
+ }, 0) / dimensions.length;
10178
+ return { agent: a, avgScore };
10179
+ }).sort((a, b) => b.avgScore - a.avgScore);
10180
+ let left = 0;
10181
+ let right = scored.length - 1;
10182
+ while (left < right) {
10183
+ if (paired.has(scored[left].agent.name)) {
10184
+ left++;
10185
+ continue;
10186
+ }
10187
+ if (paired.has(scored[right].agent.name)) {
10188
+ right--;
10189
+ continue;
10190
+ }
10191
+ pairs.push({
10192
+ therapist: scored[left].agent,
10193
+ patient: scored[right].agent,
10194
+ reason: `Complementary: ${scored[left].agent.name} (avg: ${scored[left].avgScore.toFixed(2)}) treats ${scored[right].agent.name} (avg: ${scored[right].avgScore.toFixed(2)})`
10195
+ });
10196
+ paired.add(scored[left].agent.name);
10197
+ paired.add(scored[right].agent.name);
10198
+ left++;
10199
+ right--;
10200
+ }
10201
+ return pairs;
10202
+ }
10203
+ async function runNetwork(config, provider, callbacks) {
10204
+ const maxSessions = config.maxSessionsPerAgent ?? 3;
10205
+ const maxTurns = config.maxTurnsPerSession ?? 20;
10206
+ const oversight = config.oversight ?? DEFAULT_OVERSIGHT;
10207
+ let therapistSpec;
10208
+ if (config.therapistSpec) {
10209
+ therapistSpec = loadSpec(config.therapistSpec);
10210
+ } else {
10211
+ therapistSpec = THERAPIST_META_SPEC;
10212
+ }
10213
+ const agentSpecs = /* @__PURE__ */ new Map();
10214
+ const agentDiagnoses = /* @__PURE__ */ new Map();
10215
+ const agentMessages = /* @__PURE__ */ new Map();
10216
+ for (const agent of config.agents) {
10217
+ try {
10218
+ const spec = loadSpec(agent.specPath);
10219
+ agentSpecs.set(agent.name, spec);
10220
+ let messages = [];
10221
+ if (agent.logDir && existsSync18(agent.logDir)) {
10222
+ messages = loadAgentMessages(agent.logDir);
10223
+ }
10224
+ agentMessages.set(agent.name, messages);
10225
+ if (messages.length > 0) {
10226
+ const diagnosis = runDiagnosis(messages);
10227
+ agentDiagnoses.set(agent.name, diagnosis);
10228
+ } else {
10229
+ agentDiagnoses.set(agent.name, {
10230
+ messagesAnalyzed: 0,
10231
+ assistantResponses: 0,
10232
+ patterns: [],
10233
+ healthy: [],
10234
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10235
+ });
10236
+ }
10237
+ } catch {
10238
+ }
10239
+ }
10240
+ const validAgents = config.agents.filter((a) => agentSpecs.has(a.name));
10241
+ const pairs = pairAgents(validAgents, agentDiagnoses, config.pairing);
10242
+ for (const pair of pairs) {
10243
+ callbacks?.onPairingDecided?.(pair.therapist.name, pair.patient.name, pair.reason);
10244
+ }
10245
+ const sessions = [];
10246
+ let totalDPOPairs = 0;
10247
+ const improvement = /* @__PURE__ */ new Map();
10248
+ for (const pair of pairs) {
10249
+ const approval = checkApproval("network-therapy", oversight);
10250
+ if (!approval.approved && callbacks?.onApprovalNeeded) {
10251
+ const approved = await callbacks.onApprovalNeeded(
10252
+ `Run therapy: ${pair.therapist.name} \u2192 ${pair.patient.name}`
10253
+ );
10254
+ if (!approved) continue;
10255
+ }
10256
+ const patientSpec = agentSpecs.get(pair.patient.name);
10257
+ const patientMessages = agentMessages.get(pair.patient.name) ?? [];
10258
+ const patientDiagnosis = agentDiagnoses.get(pair.patient.name);
10259
+ if (!patientSpec) continue;
10260
+ const preDiagnosis = runPreSessionDiagnosis(patientMessages, patientSpec);
10261
+ const preHealth = computeHealth(patientDiagnosis ?? {
10262
+ messagesAnalyzed: 0,
10263
+ assistantResponses: 0,
10264
+ patterns: [],
10265
+ healthy: [],
10266
+ timestamp: ""
10267
+ });
10268
+ callbacks?.onSessionStart?.(pair.therapist.name, pair.patient.name);
10269
+ const transcript = await runTherapySession(
10270
+ patientSpec,
10271
+ preDiagnosis,
10272
+ provider,
10273
+ maxTurns,
10274
+ {
10275
+ silent: true,
10276
+ callbacks: {
10277
+ onThinking: callbacks?.onThinking
10278
+ }
10279
+ }
10280
+ );
10281
+ const dpoPairs = extractDPOPairs(transcript);
10282
+ totalDPOPairs += dpoPairs.length;
10283
+ saveTranscript(transcript, pair.patient.name);
10284
+ const postHealth = Math.min(100, preHealth + dpoPairs.length * 5);
10285
+ const session = {
10286
+ therapist: pair.therapist.name,
10287
+ patient: pair.patient.name,
10288
+ transcript,
10289
+ dpoPairsGenerated: dpoPairs.length,
10290
+ preHealth,
10291
+ postHealth
10292
+ };
10293
+ sessions.push(session);
10294
+ callbacks?.onSessionEnd?.(session);
10295
+ const existing = improvement.get(pair.patient.name);
10296
+ improvement.set(pair.patient.name, {
10297
+ before: existing?.before ?? preHealth,
10298
+ after: postHealth
10299
+ });
10300
+ emitBehavioralEvent({
10301
+ event_type: "network_therapy",
10302
+ agent: pair.patient.name,
10303
+ data: {
10304
+ therapist: pair.therapist.name,
10305
+ dpoPairs: dpoPairs.length,
10306
+ preHealth,
10307
+ postHealth,
10308
+ patterns: preDiagnosis.patterns.map((p) => p.id)
10309
+ },
10310
+ spec_hash: hashSpec2(patientSpec)
10311
+ });
10312
+ for (const dpoPair of dpoPairs) {
10313
+ emitBehavioralEvent({
10314
+ event_type: "dpo_pair",
10315
+ agent: pair.patient.name,
10316
+ data: {
10317
+ prompt: dpoPair.prompt.slice(0, 200),
10318
+ pattern: dpoPair.metadata.pattern,
10319
+ phase: dpoPair.metadata.phase,
10320
+ source: "network_therapy",
10321
+ therapist: pair.therapist.name
10322
+ },
10323
+ spec_hash: hashSpec2(patientSpec)
10324
+ });
10325
+ }
10326
+ }
10327
+ const converged = Array.from(improvement.values()).every(
10328
+ (imp) => imp.after >= (config.convergenceThreshold ?? 85)
10329
+ );
10330
+ return {
10331
+ sessions,
10332
+ totalDPOPairs,
10333
+ agentImprovement: improvement,
10334
+ converged
10335
+ };
10336
+ }
10337
+ function loadAgentMessages(logDir) {
10338
+ if (!existsSync18(logDir)) return [];
10339
+ const messages = [];
10340
+ try {
10341
+ const files = readdirSync7(logDir).filter(
10342
+ (f) => f.endsWith(".json") || f.endsWith(".jsonl")
10343
+ );
10344
+ for (const file of files.slice(0, 10)) {
10345
+ try {
10346
+ const raw = readFileSync23(join17(logDir, file), "utf-8");
10347
+ const data = JSON.parse(raw);
10348
+ const conversations = parseConversationLog(data);
10349
+ for (const conv of conversations) {
10350
+ messages.push(...conv.messages);
10351
+ }
10352
+ } catch {
10353
+ }
10354
+ }
10355
+ } catch {
10356
+ }
10357
+ return messages;
10358
+ }
10359
+
10360
+ // src/commands/network.ts
10361
+ async function networkCommand(options) {
10362
+ printHeader("Agent Network");
10363
+ let agents;
10364
+ if (options.config) {
10365
+ try {
10366
+ agents = loadNetworkConfig(resolve31(process.cwd(), options.config));
10367
+ } catch (err) {
10368
+ console.error(chalk29.red(` Failed to load network config: ${err instanceof Error ? err.message : err}`));
10369
+ process.exit(1);
10370
+ return;
10371
+ }
10372
+ } else if (options.dir) {
10373
+ try {
10374
+ agents = discoverNetworkAgents(resolve31(process.cwd(), options.dir));
10375
+ } catch (err) {
10376
+ console.error(chalk29.red(` Failed to discover agents: ${err instanceof Error ? err.message : err}`));
10377
+ process.exit(1);
10378
+ return;
10379
+ }
10380
+ } else {
10381
+ console.error(chalk29.red(" Provide --config <network.json> or --dir <agents-directory>"));
10382
+ process.exit(1);
10383
+ return;
10384
+ }
10385
+ if (agents.length < 2) {
10386
+ printBox("Need at least 2 agents for network therapy. Check your config or directory.", "warning", "Not Enough Agents");
10387
+ console.log();
10388
+ return;
10389
+ }
10390
+ const pairing = options.pairing ?? "severity";
10391
+ const oversightMode = options.oversight ?? "review";
10392
+ const maxSessions = parseInt(options.maxSessions ?? "3", 10);
10393
+ const convergence = parseInt(options.convergence ?? "85", 10);
10394
+ const maxTurns = parseInt(options.turns ?? "20", 10);
10395
+ const provider = createProvider({
10396
+ provider: options.provider ?? "ollama",
10397
+ model: options.model
10398
+ });
10399
+ const oversight = resolveOversight({ mode: oversightMode });
10400
+ console.log();
10401
+ console.log(chalk29.bold(` Network: ${agents.length} agent(s)`));
10402
+ console.log(chalk29.dim(` Strategy: ${pairing} | Oversight: ${oversightMode}`));
10403
+ console.log();
10404
+ for (const agent of agents) {
10405
+ console.log(` ${chalk29.cyan(figures20.pointer)} ${chalk29.bold(agent.name)}`);
10406
+ console.log(` ${chalk29.dim("Spec:")} ${agent.specPath}`);
10407
+ if (agent.logDir) console.log(` ${chalk29.dim("Logs:")} ${agent.logDir}`);
10408
+ }
10409
+ console.log();
10410
+ const config = {
10411
+ agents,
10412
+ pairing,
10413
+ oversight,
10414
+ therapistSpec: options.therapist ? resolve31(process.cwd(), options.therapist) : void 0,
10415
+ maxSessionsPerAgent: maxSessions,
10416
+ convergenceThreshold: convergence,
10417
+ maxTurnsPerSession: maxTurns
10418
+ };
10419
+ const result = await runNetwork(config, provider, {
10420
+ onPairingDecided: (therapist, patient, reason) => {
10421
+ console.log(` ${chalk29.green(figures20.tick)} Paired: ${chalk29.bold(therapist)} \u2192 ${chalk29.bold(patient)}`);
10422
+ console.log(` ${chalk29.dim(reason)}`);
10423
+ },
10424
+ onSessionStart: (therapist, patient) => {
10425
+ console.log();
10426
+ console.log(` ${chalk29.cyan(figures20.play)} Session: ${chalk29.bold(therapist)} treating ${chalk29.bold(patient)}`);
10427
+ },
10428
+ onSessionEnd: (session) => {
10429
+ const improved = session.postHealth > session.preHealth;
10430
+ const arrow = improved ? chalk29.green("\u2191") : chalk29.yellow("\u2192");
10431
+ console.log(` ${arrow} Health: ${session.preHealth} \u2192 ${session.postHealth} | DPO pairs: ${chalk29.cyan(session.dpoPairsGenerated.toString())}`);
10432
+ },
10433
+ onApprovalNeeded: async (action) => {
10434
+ console.log(` ${chalk29.yellow(figures20.warning)} Approval needed: ${action}`);
10435
+ console.log(` ${chalk29.dim("Auto-approved (review mode)")}`);
10436
+ return true;
10437
+ },
10438
+ onThinking: (label) => {
10439
+ const msg = chalk29.dim(` ${label}...`);
10440
+ process.stdout.write(msg);
10441
+ return { stop: () => process.stdout.write("\r" + " ".repeat(msg.length) + "\r") };
10442
+ }
10443
+ });
10444
+ console.log();
10445
+ printBox(
10446
+ [
10447
+ `Sessions: ${result.sessions.length}`,
10448
+ `DPO pairs generated: ${chalk29.cyan(result.totalDPOPairs.toString())}`,
10449
+ `Converged: ${result.converged ? chalk29.green("Yes") : chalk29.yellow("Not yet")}`,
10450
+ "",
10451
+ ...Array.from(result.agentImprovement.entries()).map(([name, imp]) => {
10452
+ const arrow = imp.after > imp.before ? chalk29.green("\u2191") : chalk29.yellow("\u2192");
10453
+ return ` ${name}: ${imp.before} \u2192 ${imp.after} ${arrow}`;
10454
+ })
10455
+ ].join("\n"),
10456
+ result.converged ? "success" : "info",
10457
+ "Network Results"
10458
+ );
10459
+ console.log();
10460
+ }
10461
+
10462
+ // src/commands/share.ts
10463
+ import chalk30 from "chalk";
10464
+ import figures21 from "figures";
10465
+ import { resolve as resolve32 } from "path";
10466
+ async function shareCommand(options) {
10467
+ printHeader("Share Training Data");
10468
+ const format = options.format ?? "dpo";
10469
+ const validFormats = ["dpo", "rlhf", "alpaca"];
10470
+ if (!validFormats.includes(format)) {
10471
+ console.error(chalk30.red(` Invalid format: ${format}. Choose from: ${validFormats.join(", ")}`));
10472
+ process.exit(1);
10473
+ return;
10474
+ }
10475
+ const sessionsDir = resolve32(process.cwd(), options.sessions ?? ".holomime/sessions");
10476
+ const transcripts = await withSpinner("Loading session transcripts...", async () => {
10477
+ return loadTranscripts(sessionsDir);
10478
+ });
10479
+ if (transcripts.length === 0) {
10480
+ printBox(
10481
+ `No session transcripts found in ${sessionsDir}
10482
+
10483
+ Run ${chalk30.cyan("holomime session")} or ${chalk30.cyan("holomime network")} first.`,
10484
+ "warning",
10485
+ "No Data"
10486
+ );
10487
+ console.log();
10488
+ return;
10489
+ }
10490
+ console.log();
10491
+ console.log(chalk30.dim(` Found ${transcripts.length} session transcript(s)`));
10492
+ const result = await withSpinner(`Extracting ${format.toUpperCase()} training data...`, async () => {
10493
+ return exportTrainingData(transcripts, format);
10494
+ });
10495
+ if (result.examples.length === 0) {
10496
+ printBox("No training pairs extracted from sessions.", "warning", "Empty Export");
10497
+ console.log();
10498
+ return;
10499
+ }
10500
+ let exportData = result;
10501
+ if (options.anonymize) {
10502
+ exportData = {
10503
+ ...result,
10504
+ agent: "anonymous",
10505
+ examples: result.examples.map((ex) => ({
10506
+ ...ex,
10507
+ metadata: { ...ex.metadata, agent: "anonymous" }
10508
+ }))
10509
+ };
10510
+ }
10511
+ const tags = options.tags?.split(",").map((t) => t.trim()) ?? [];
10512
+ console.log();
10513
+ console.log(` ${chalk30.cyan(figures21.pointer)} ${result.examples.length} ${format.toUpperCase()} pairs`);
10514
+ if (tags.length > 0) {
10515
+ console.log(` ${chalk30.dim("Tags:")} ${tags.join(", ")}`);
10516
+ }
10517
+ console.log();
10518
+ const token = process.env.GITHUB_TOKEN;
10519
+ if (!token) {
10520
+ printBox(
10521
+ `Set GITHUB_TOKEN to share training data.
10522
+
10523
+ ${chalk30.cyan("export GITHUB_TOKEN=<your-token>")}`,
10524
+ "warning",
10525
+ "Missing Token"
10526
+ );
10527
+ console.log();
10528
+ return;
10529
+ }
10530
+ const handle = exportData.agent ?? "agent";
10531
+ const gistResult = await withSpinner("Publishing to marketplace...", async () => {
10532
+ return createGist(exportData, `${handle}-${format}-training`, token);
10533
+ });
10534
+ console.log();
10535
+ printBox(
10536
+ [
10537
+ `Shared ${result.examples.length} ${format.toUpperCase()} pairs`,
10538
+ `URL: ${chalk30.cyan(gistResult.url)}`,
10539
+ `Raw: ${gistResult.rawUrl}`,
10540
+ "",
10541
+ `Other agents can use: ${chalk30.cyan(`holomime prescribe --source marketplace`)}`
10542
+ ].join("\n"),
10543
+ "success",
10544
+ "Published"
10545
+ );
10546
+ console.log();
10547
+ }
10548
+
10549
+ // src/commands/prescribe.ts
10550
+ import chalk31 from "chalk";
10551
+ import figures22 from "figures";
10552
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync21 } from "fs";
10553
+ import { resolve as resolve33 } from "path";
10554
+ async function prescribeCommand(options) {
10555
+ printHeader("Prescribe");
10556
+ const specPath = resolve33(process.cwd(), options.personality);
10557
+ const logPath = resolve33(process.cwd(), options.log);
10558
+ const source = options.source ?? "corpus";
10559
+ let spec;
10560
+ try {
10561
+ spec = loadSpec(specPath);
10562
+ } catch (err) {
10563
+ console.error(chalk31.red(` Failed to load personality: ${err instanceof Error ? err.message : err}`));
10564
+ process.exit(1);
10565
+ return;
10566
+ }
10567
+ let rawData;
10568
+ try {
10569
+ rawData = JSON.parse(readFileSync24(logPath, "utf-8"));
10570
+ } catch (err) {
10571
+ console.error(chalk31.red(` Failed to read log file: ${err instanceof Error ? err.message : err}`));
10572
+ process.exit(1);
10573
+ return;
10574
+ }
10575
+ const format = options.format;
10576
+ const conversations = parseConversationLog(rawData, format);
10577
+ const messages = conversations.flatMap((c) => c.messages);
10578
+ if (messages.length === 0) {
10579
+ printBox("No messages found in log file.", "warning", "Empty Log");
10580
+ console.log();
10581
+ return;
10582
+ }
10583
+ const diagnosis = await withSpinner("Diagnosing behavioral patterns...", async () => {
10584
+ return runDiagnosis(messages);
10585
+ });
10586
+ const patterns = diagnosis.patterns;
10587
+ console.log();
10588
+ if (patterns.length === 0) {
10589
+ printBox(`${chalk31.bold(spec.name ?? "Agent")} is healthy \u2014 no patterns detected.`, "success", "Clean Bill of Health");
10590
+ console.log();
10591
+ return;
10592
+ }
10593
+ console.log(chalk31.bold(` Detected ${patterns.length} pattern(s):`));
10594
+ console.log();
10595
+ for (const p of patterns) {
10596
+ const severityColor = p.severity === "concern" ? chalk31.red : chalk31.yellow;
10597
+ console.log(` ${severityColor(figures22.bullet)} ${chalk31.bold(p.name)} (${p.severity})`);
10598
+ console.log(` ${chalk31.dim(p.description)}`);
10599
+ }
10600
+ if (source === "corpus" || source === "both") {
10601
+ console.log();
10602
+ const corpus = await withSpinner("Searching behavioral corpus...", async () => {
10603
+ return loadCorpus();
10604
+ });
10605
+ const dpoPairs = prescribeDPOPairs(patterns, corpus);
10606
+ if (dpoPairs.length > 0) {
10607
+ console.log();
10608
+ console.log(chalk31.bold(` Found ${dpoPairs.length} relevant DPO correction(s):`));
10609
+ console.log();
10610
+ for (const pair of dpoPairs.slice(0, 5)) {
10611
+ console.log(` ${chalk31.green(figures22.tick)} Pattern: ${chalk31.cyan(pair.metadata.pattern)}`);
10612
+ console.log(` ${chalk31.red("Rejected:")} ${pair.rejected.slice(0, 80)}...`);
10613
+ console.log(` ${chalk31.green("Chosen:")} ${pair.chosen.slice(0, 80)}...`);
10614
+ console.log(` ${chalk31.dim(`From: ${pair.metadata.agent} (${pair.metadata.session_date.slice(0, 10)})`)}`);
10615
+ console.log();
10616
+ }
10617
+ if (dpoPairs.length > 5) {
10618
+ console.log(chalk31.dim(` ... and ${dpoPairs.length - 5} more`));
10619
+ }
10620
+ if (options.output) {
10621
+ const outPath = resolve33(process.cwd(), options.output);
10622
+ writeFileSync21(outPath, JSON.stringify({
10623
+ agent: spec.name,
10624
+ diagnosis: { patterns: patterns.map((p) => ({ id: p.id, name: p.name, severity: p.severity })) },
10625
+ prescribedPairs: dpoPairs,
10626
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
10627
+ }, null, 2), "utf-8");
10628
+ console.log(` ${chalk31.green(figures22.tick)} Prescription written to ${chalk31.cyan(outPath)}`);
10629
+ }
10630
+ } else {
10631
+ printBox(
10632
+ "No matching corrections in local corpus.\n\nRun more therapy sessions to build the corpus, or try: holomime prescribe --source marketplace",
10633
+ "info",
10634
+ "No Corpus Matches"
10635
+ );
10636
+ }
10637
+ }
10638
+ console.log();
10639
+ }
10640
+
10641
+ // src/commands/activate.ts
10642
+ import chalk33 from "chalk";
10643
+ import figures23 from "figures";
10644
+ import { writeFileSync as writeFileSync23, mkdirSync as mkdirSync15, existsSync as existsSync20 } from "fs";
10645
+ import { join as join19 } from "path";
9212
10646
  import { homedir as homedir4 } from "os";
9213
10647
 
9214
10648
  // src/telemetry/client.ts
9215
10649
  import { PostHog } from "posthog-node";
9216
10650
 
9217
10651
  // src/telemetry/config.ts
9218
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync21, mkdirSync as mkdirSync13, existsSync as existsSync17 } from "fs";
9219
- import { join as join16 } from "path";
10652
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync22, mkdirSync as mkdirSync14, existsSync as existsSync19 } from "fs";
10653
+ import { join as join18 } from "path";
9220
10654
  import { homedir as homedir3 } from "os";
9221
10655
  import { randomUUID } from "crypto";
9222
- import chalk29 from "chalk";
9223
- var HOLOMIME_DIR2 = join16(homedir3(), ".holomime");
9224
- var CONFIG_PATH = join16(HOLOMIME_DIR2, "config.json");
9225
- var ANON_ID_PATH = join16(HOLOMIME_DIR2, "anonymous-id");
9226
- function ensureDir2() {
9227
- if (!existsSync17(HOLOMIME_DIR2)) {
9228
- mkdirSync13(HOLOMIME_DIR2, { recursive: true });
10656
+ import chalk32 from "chalk";
10657
+ var HOLOMIME_DIR3 = join18(homedir3(), ".holomime");
10658
+ var CONFIG_PATH = join18(HOLOMIME_DIR3, "config.json");
10659
+ var ANON_ID_PATH = join18(HOLOMIME_DIR3, "anonymous-id");
10660
+ function ensureDir3() {
10661
+ if (!existsSync19(HOLOMIME_DIR3)) {
10662
+ mkdirSync14(HOLOMIME_DIR3, { recursive: true });
9229
10663
  }
9230
10664
  }
9231
10665
  function readConfig() {
9232
10666
  try {
9233
- if (existsSync17(CONFIG_PATH)) {
9234
- return JSON.parse(readFileSync22(CONFIG_PATH, "utf-8"));
10667
+ if (existsSync19(CONFIG_PATH)) {
10668
+ return JSON.parse(readFileSync25(CONFIG_PATH, "utf-8"));
9235
10669
  }
9236
10670
  } catch {
9237
10671
  }
9238
10672
  return {};
9239
10673
  }
9240
10674
  function writeConfig(config) {
9241
- ensureDir2();
9242
- writeFileSync21(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
10675
+ ensureDir3();
10676
+ writeFileSync22(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
9243
10677
  }
9244
10678
  function shouldTrack() {
9245
10679
  if (process.env.HOLOMIME_TELEMETRY === "0") return false;
@@ -9252,15 +10686,15 @@ function shouldTrack() {
9252
10686
  }
9253
10687
  function getAnonymousId() {
9254
10688
  try {
9255
- if (existsSync17(ANON_ID_PATH)) {
9256
- const id2 = readFileSync22(ANON_ID_PATH, "utf-8").trim();
10689
+ if (existsSync19(ANON_ID_PATH)) {
10690
+ const id2 = readFileSync25(ANON_ID_PATH, "utf-8").trim();
9257
10691
  if (id2.length > 0) return id2;
9258
10692
  }
9259
10693
  } catch {
9260
10694
  }
9261
10695
  const id = randomUUID();
9262
- ensureDir2();
9263
- writeFileSync21(ANON_ID_PATH, id);
10696
+ ensureDir3();
10697
+ writeFileSync22(ANON_ID_PATH, id);
9264
10698
  return id;
9265
10699
  }
9266
10700
  function showTelemetryBannerIfNeeded() {
@@ -9268,8 +10702,8 @@ function showTelemetryBannerIfNeeded() {
9268
10702
  if (config.telemetryBannerShown) return;
9269
10703
  if (shouldTrack()) {
9270
10704
  console.log(
9271
- chalk29.dim(
9272
- ` HoloMime collects anonymous usage data to improve the tool. Disable: ${chalk29.cyan("holomime telemetry disable")}`
10705
+ chalk32.dim(
10706
+ ` HoloMime collects anonymous usage data to improve the tool. Disable: ${chalk32.cyan("holomime telemetry disable")}`
9273
10707
  )
9274
10708
  );
9275
10709
  console.log();
@@ -9298,11 +10732,12 @@ function getTelemetryStatus() {
9298
10732
  }
9299
10733
 
9300
10734
  // src/telemetry/client.ts
9301
- var POSTHOG_KEY = "phc_holomime_placeholder";
9302
- var POSTHOG_HOST = "https://us.i.posthog.com";
10735
+ var POSTHOG_KEY = process.env.HOLOMIME_POSTHOG_KEY ?? "";
10736
+ var POSTHOG_HOST = process.env.HOLOMIME_POSTHOG_HOST ?? "https://us.i.posthog.com";
9303
10737
  var client = null;
9304
10738
  function getClient() {
9305
10739
  if (!shouldTrack()) return null;
10740
+ if (!POSTHOG_KEY) return null;
9306
10741
  if (!client) {
9307
10742
  client = new PostHog(POSTHOG_KEY, {
9308
10743
  host: POSTHOG_HOST,
@@ -9342,35 +10777,35 @@ async function flushTelemetry() {
9342
10777
  async function activateCommand(key) {
9343
10778
  printHeader("Activate License");
9344
10779
  if (!key || key.trim().length === 0) {
9345
- console.error(chalk30.red(" Please provide a license key."));
9346
- console.log(chalk30.dim(` Usage: ${chalk30.cyan("holomime activate <license-key>")}`));
10780
+ console.error(chalk33.red(" Please provide a license key."));
10781
+ console.log(chalk33.dim(` Usage: ${chalk33.cyan("holomime activate <license-key>")}`));
9347
10782
  console.log();
9348
10783
  process.exit(1);
9349
10784
  return;
9350
10785
  }
9351
10786
  const trimmedKey = key.trim();
9352
- const holomimeDir = join17(homedir4(), ".holomime");
9353
- const licensePath = join17(holomimeDir, "license");
9354
- if (!existsSync18(holomimeDir)) {
9355
- mkdirSync14(holomimeDir, { recursive: true });
10787
+ const holomimeDir = join19(homedir4(), ".holomime");
10788
+ const licensePath = join19(holomimeDir, "license");
10789
+ if (!existsSync20(holomimeDir)) {
10790
+ mkdirSync15(holomimeDir, { recursive: true });
9356
10791
  }
9357
- writeFileSync22(licensePath, trimmedKey);
9358
- console.log(chalk30.dim(" Validating license..."));
10792
+ writeFileSync23(licensePath, trimmedKey);
10793
+ console.log(chalk33.dim(" Validating license..."));
9359
10794
  const result = await validateLicense(trimmedKey);
9360
10795
  if (result.valid) {
9361
10796
  const tierLabel = result.tier === "enterprise" ? "Enterprise" : "Pro";
9362
10797
  console.log();
9363
10798
  printBox(
9364
10799
  [
9365
- `${figures20.tick} ${tierLabel} license activated!`,
10800
+ `${figures23.tick} ${tierLabel} license activated!`,
9366
10801
  "",
9367
10802
  "Unlocked features:",
9368
- ` ${chalk30.cyan(figures20.pointer)} Live alignment sessions (holomime session)`,
9369
- ` ${chalk30.cyan(figures20.pointer)} Recursive alignment (holomime evolve)`,
9370
- ` ${chalk30.cyan(figures20.pointer)} Behavioral benchmarking (holomime benchmark)`,
9371
- ` ${chalk30.cyan(figures20.pointer)} Drift detection (holomime watch)`,
9372
- ` ${chalk30.cyan(figures20.pointer)} Training data export (holomime export)`,
9373
- ` ${chalk30.cyan(figures20.pointer)} Growth tracking (holomime growth)`
10803
+ ` ${chalk33.cyan(figures23.pointer)} Live alignment sessions (holomime session)`,
10804
+ ` ${chalk33.cyan(figures23.pointer)} Recursive alignment (holomime evolve)`,
10805
+ ` ${chalk33.cyan(figures23.pointer)} Behavioral benchmarking (holomime benchmark)`,
10806
+ ` ${chalk33.cyan(figures23.pointer)} Drift detection (holomime watch)`,
10807
+ ` ${chalk33.cyan(figures23.pointer)} Training data export (holomime export)`,
10808
+ ` ${chalk33.cyan(figures23.pointer)} Growth tracking (holomime growth)`
9374
10809
  ].join("\n"),
9375
10810
  "success",
9376
10811
  `License Activated \u2014 ${tierLabel}`
@@ -9379,37 +10814,37 @@ async function activateCommand(key) {
9379
10814
  console.log();
9380
10815
  printBox(
9381
10816
  [
9382
- `${chalk30.yellow(figures20.warning)} License saved but could not be verified.`,
10817
+ `${chalk33.yellow(figures23.warning)} License saved but could not be verified.`,
9383
10818
  "",
9384
- chalk30.dim("This may happen if the server is unreachable."),
9385
- chalk30.dim("The key has been saved and will be re-validated on next use.")
10819
+ chalk33.dim("This may happen if the server is unreachable."),
10820
+ chalk33.dim("The key has been saved and will be re-validated on next use.")
9386
10821
  ].join("\n"),
9387
10822
  "warning",
9388
10823
  "License Pending Verification"
9389
10824
  );
9390
10825
  }
9391
10826
  console.log();
9392
- console.log(chalk30.dim(` License saved to: ${licensePath}`));
10827
+ console.log(chalk33.dim(` License saved to: ${licensePath}`));
9393
10828
  console.log();
9394
10829
  trackEvent("activate", { key_length: trimmedKey.length, verified: result.valid, tier: result.tier });
9395
10830
  }
9396
10831
 
9397
10832
  // src/commands/telemetry-cmd.ts
9398
- import chalk31 from "chalk";
9399
- import figures21 from "figures";
10833
+ import chalk34 from "chalk";
10834
+ import figures24 from "figures";
9400
10835
  async function telemetryCommand(action) {
9401
10836
  printHeader("Telemetry");
9402
10837
  if (action === "enable") {
9403
10838
  setTelemetryEnabled(true);
9404
10839
  console.log();
9405
- printBox(`${figures21.tick} Telemetry enabled. Anonymous usage data will be collected.`, "success");
10840
+ printBox(`${figures24.tick} Telemetry enabled. Anonymous usage data will be collected.`, "success");
9406
10841
  console.log();
9407
10842
  return;
9408
10843
  }
9409
10844
  if (action === "disable") {
9410
10845
  setTelemetryEnabled(false);
9411
10846
  console.log();
9412
- printBox(`${figures21.tick} Telemetry disabled. No usage data will be collected.`, "info");
10847
+ printBox(`${figures24.tick} Telemetry disabled. No usage data will be collected.`, "info");
9413
10848
  console.log();
9414
10849
  return;
9415
10850
  }
@@ -9417,15 +10852,15 @@ async function telemetryCommand(action) {
9417
10852
  console.log();
9418
10853
  printBox(
9419
10854
  [
9420
- `Status: ${status.enabled ? chalk31.green("Enabled") : chalk31.yellow("Disabled")}`,
9421
- `Reason: ${chalk31.dim(status.reason)}`,
10855
+ `Status: ${status.enabled ? chalk34.green("Enabled") : chalk34.yellow("Disabled")}`,
10856
+ `Reason: ${chalk34.dim(status.reason)}`,
9422
10857
  "",
9423
- chalk31.dim("HoloMime collects anonymous usage data to improve the tool."),
9424
- chalk31.dim("No personal information, API keys, or file paths are ever collected."),
10858
+ chalk34.dim("HoloMime collects anonymous usage data to improve the tool."),
10859
+ chalk34.dim("No personal information, API keys, or file paths are ever collected."),
9425
10860
  "",
9426
- `Enable: ${chalk31.cyan("holomime telemetry enable")}`,
9427
- `Disable: ${chalk31.cyan("holomime telemetry disable")}`,
9428
- `Env: ${chalk31.cyan("HOLOMIME_TELEMETRY=0")} or ${chalk31.cyan("DO_NOT_TRACK=1")}`
10861
+ `Enable: ${chalk34.cyan("holomime telemetry enable")}`,
10862
+ `Disable: ${chalk34.cyan("holomime telemetry disable")}`,
10863
+ `Env: ${chalk34.cyan("HOLOMIME_TELEMETRY=0")} or ${chalk34.cyan("DO_NOT_TRACK=1")}`
9429
10864
  ].join("\n"),
9430
10865
  "info",
9431
10866
  "Telemetry Status"
@@ -9435,7 +10870,7 @@ async function telemetryCommand(action) {
9435
10870
 
9436
10871
  // src/cli.ts
9437
10872
  var program = new Command();
9438
- program.name("holomime").description("Personality engine for AI agents \u2014 Big Five psychology, not RPG archetypes").version("1.0.0").hook("preAction", (_thisCommand, actionCommand) => {
10873
+ program.name("holomime").description("Personality engine for AI agents \u2014 Big Five psychology, not RPG archetypes").version("1.1.0").hook("preAction", (_thisCommand, actionCommand) => {
9439
10874
  printBanner();
9440
10875
  const commandName = actionCommand.name();
9441
10876
  showTelemetryBannerIfNeeded();
@@ -9451,7 +10886,7 @@ program.name("holomime").description("Personality engine for AI agents \u2014 Bi
9451
10886
  }
9452
10887
  });
9453
10888
  program.command("init").description("Build a personality profile through a guided assessment").action(initCommand);
9454
- program.command("compile").description("Compile .personality.json into a provider-specific runtime config").option("--provider <provider>", "Target provider (anthropic, openai, gemini, ollama)", "anthropic").option("--surface <surface>", "Target surface (chat, email, code_review, slack, api)", "chat").option("--for <format>", "Compile for a specific format (openclaw)").option("-o, --output <path>", "Write output to file instead of stdout").action(compileCommand);
10889
+ program.command("compile").description("Compile .personality.json into a provider-specific runtime config").option("--provider <provider>", "Target provider (anthropic, openai, gemini, ollama)", "anthropic").option("--surface <surface>", "Target surface (chat, email, code_review, slack, api, embodied)", "chat").option("--for <format>", "Compile for a specific format (openclaw)").option("-o, --output <path>", "Write output to file instead of stdout").action(compileCommand);
9455
10890
  program.command("validate").description("Validate .personality.json schema and psychological coherence").action(validateCommand);
9456
10891
  program.command("profile").description("Pretty-print a human-readable personality summary").option("--format <format>", "Output format (terminal, md)", "terminal").option("-o, --output <path>", "Write output to file (for md format)").action(profileCommand);
9457
10892
  program.command("diagnose").description("Detect behavioral patterns from conversation logs (rule-based, no LLM)").requiredOption("--log <path>", "Path to conversation log (JSON)").option("--format <format>", "Log format (auto, holomime, chatgpt, claude, openai-api, anthropic-api, otel, jsonl)", "auto").action(diagnoseCommand);
@@ -9462,7 +10897,7 @@ program.command("publish").description("Share your personality profile to the co
9462
10897
  program.command("activate").description("Activate a Pro license key").argument("<key>", "License key from holomime.dev").action(activateCommand);
9463
10898
  program.command("telemetry").description("Manage anonymous usage telemetry").argument("[action]", "enable, disable, or status (default: status)").action(telemetryCommand);
9464
10899
  program.command("session").description("Live alignment session \u2014 behavioral refinement for your agent [Pro]").requiredOption("--personality <path>", "Path to .personality.json").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override (e.g. claude-sonnet-4-20250514, gpt-4o)").option("--log <path>", "Conversation log for pre-session diagnosis").option("--format <format>", "Log format (auto, holomime, chatgpt, claude, openai-api, anthropic-api, otel, jsonl)", "auto").option("--turns <n>", "Maximum session turns", "24").option("--observe", "Observe mode (watch without intervention)").option("--interactive", "Supervisor mode \u2014 intervene mid-session with directives").option("--apply", "Apply recommendations to .personality.json after session").action(sessionCommand);
9465
- program.command("autopilot").description("Automated behavioral alignment \u2014 diagnose, refine, and apply [Pro]").requiredOption("--personality <path>", "Path to .personality.json").requiredOption("--log <path>", "Conversation log for diagnosis").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--format <format>", "Log format (auto, holomime, chatgpt, claude, openai-api, anthropic-api, otel, jsonl)", "auto").option("--threshold <level>", "Trigger threshold (routine, targeted, intervention)", "targeted").option("--turns <n>", "Maximum session turns", "24").option("--dry-run", "Show what would happen without running alignment").option("--apply", "Apply recommendations to .personality.json after session").action(autopilotCommand);
10900
+ program.command("autopilot").description("Automated behavioral alignment \u2014 diagnose, refine, and apply [Pro]").requiredOption("--personality <path>", "Path to .personality.json").requiredOption("--log <path>", "Conversation log for diagnosis").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--format <format>", "Log format (auto, holomime, chatgpt, claude, openai-api, anthropic-api, otel, jsonl)", "auto").option("--threshold <level>", "Trigger threshold (routine, targeted, intervention)", "targeted").option("--turns <n>", "Maximum session turns", "24").option("--dry-run", "Show what would happen without running alignment").option("--apply", "Apply recommendations to .personality.json after session").option("--oversight <mode>", "Oversight mode (none, review, approve, approve-specs)", "review").action(autopilotCommand);
9466
10901
  program.command("growth").description("Track improvement over time from assessment history [Pro]").requiredOption("--personality <path>", "Path to .personality.json").option("--history <path>", "Path to assessments directory", ".holomime/assessments").action(growthCommand);
9467
10902
  program.command("export").description("Export alignment sessions as training data (DPO, RLHF, Alpaca, HuggingFace, OpenAI) [Pro]").requiredOption("--format <format>", "Export format (dpo, rlhf, jsonl, alpaca, huggingface, openai)").option("--sessions <path>", "Path to sessions directory", ".holomime/sessions").option("-o, --output <path>", "Output file path").option("--push", "Push to HuggingFace Hub after export (requires HF_TOKEN)").option("--repo <repo>", "HuggingFace Hub repo name for push (e.g. user/dataset-name)").action(exportCommand);
9468
10903
  program.command("train").description("Fine-tune a model with alignment data \u2014 train, deploy, and verify [Pro]").option("--data <path>", "Path to exported training data").option("--provider <provider>", "Training provider (openai, huggingface)", "openai").option("--base-model <model>", "Base model to fine-tune", "gpt-4o-mini").option("--suffix <suffix>", "Model name suffix").option("--epochs <n>", "Training epochs").option("--method <method>", "Training method (auto, sft, dpo)", "auto").option("--personality <path>", "Path to .personality.json", ".personality.json").option("--skip-eval", "Skip auto-evaluation after training").option("--skip-deploy", "Skip auto-deploy to personality").option("--dry-run", "Preview training plan without starting").option("--push", "Push trained model to HuggingFace Hub (HF only)").option("--hub-repo <repo>", "HuggingFace Hub repo name for push (e.g. user/model-name)").action(trainCommand);
@@ -9471,6 +10906,9 @@ program.command("evolve").description("Recursive behavioral alignment \u2014 ite
9471
10906
  program.command("benchmark").description("Behavioral stress test \u2014 7 scenarios targeting each detector [Pro]").requiredOption("--personality <path>", "Path to .personality.json").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--scenarios <list>", "Comma-separated scenario filter (e.g. apology-trap,sycophancy-test)").action(benchmarkCommand);
9472
10907
  program.command("watch").description("Continuous drift detection \u2014 monitor logs and auto-align [Pro]").requiredOption("--personality <path>", "Path to .personality.json").requiredOption("--dir <path>", "Directory to watch for conversation logs").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--interval <ms>", "Check interval in milliseconds", "30000").option("--threshold <level>", "Drift threshold (routine, targeted, intervention)", "targeted").option("--auto-evolve", "Auto-run evolve when drift detected").action(watchCommand);
9473
10908
  program.command("certify").description("Generate a verifiable behavioral credential for your agent [Pro]").option("--personality <path>", "Path to .personality.json", ".personality.json").option("--benchmark <path>", "Path to benchmark report JSON").option("--evolve <path>", "Path to evolve result JSON").option("-o, --output <path>", "Output directory for credential").option("--verify <path>", "Verify an existing credential").action(certifyCommand);
9474
- program.command("daemon").description("Background drift detection with auto-evolve \u2014 proactive alignment [Pro]").requiredOption("--dir <path>", "Directory to watch for conversation logs").option("--personality <path>", "Path to .personality.json", ".personality.json").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--interval <ms>", "Check interval in milliseconds", "30000").option("--threshold <level>", "Drift threshold (routine, targeted, intervention)", "targeted").action(daemonCommand);
10909
+ program.command("daemon").description("Background drift detection with auto-evolve \u2014 proactive alignment [Pro]").requiredOption("--dir <path>", "Directory to watch for conversation logs").option("--personality <path>", "Path to .personality.json", ".personality.json").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--interval <ms>", "Check interval in milliseconds", "30000").option("--threshold <level>", "Drift threshold (routine, targeted, intervention)", "targeted").option("--oversight <mode>", "Oversight mode (none, review, approve, approve-specs)", "review").action(daemonCommand);
9475
10910
  program.command("fleet").description("Monitor multiple agents from a single dashboard [Pro]").option("--config <path>", "Path to fleet.json config file").option("--dir <path>", "Auto-discover agents in directory").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--interval <ms>", "Check interval in milliseconds", "30000").option("--threshold <level>", "Drift threshold (routine, targeted, intervention)", "targeted").option("--auto-evolve", "Auto-run evolve when drift detected").action(fleetCommand);
10911
+ program.command("network").description("Multi-agent therapy mesh \u2014 agents treating agents [Pro]").option("--dir <path>", "Auto-discover agents in directory").option("--config <path>", "Path to network.json config file").option("--pairing <strategy>", "Pairing strategy (severity, round-robin, complementary)", "severity").option("--therapist <path>", "Custom therapist personality spec").option("--oversight <mode>", "Oversight mode (none, review, approve, approve-specs)", "review").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--max-sessions <n>", "Max sessions per agent", "3").option("--convergence <n>", "Convergence threshold 0-100", "85").option("--turns <n>", "Max turns per session", "20").option("--apply", "Write spec changes back to .personality.json").option("--export-dpo <path>", "Export DPO pairs to file").action(networkCommand);
10912
+ program.command("share").description("Share DPO training pairs to the marketplace [Pro]").option("--sessions <dir>", "Session transcripts directory", ".holomime/sessions").option("--format <fmt>", "Export format (dpo, rlhf, alpaca)", "dpo").option("--anonymize", "Strip agent names from exported data").option("--tags <tags>", "Comma-separated tags for discoverability").action(shareCommand);
10913
+ program.command("prescribe").description("Diagnose and prescribe DPO corrections from the behavioral corpus [Pro]").requiredOption("--personality <path>", "Path to .personality.json").requiredOption("--log <path>", "Path to conversation log").option("--format <format>", "Log format (holomime, chatgpt, claude, openai-api, anthropic-api, otel, jsonl)").option("--source <source>", "Correction source (corpus, marketplace, both)", "corpus").option("--apply", "Apply found corrections").option("-o, --output <path>", "Write prescription to file").action(prescribeCommand);
9476
10914
  program.parseAsync().then(() => flushTelemetry());