@wundr.io/cli 1.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/dist/ai/ai-service.d.ts +152 -0
  2. package/dist/ai/ai-service.d.ts.map +1 -0
  3. package/dist/ai/ai-service.js +430 -0
  4. package/dist/ai/ai-service.js.map +1 -0
  5. package/dist/ai/claude-client.d.ts +130 -0
  6. package/dist/ai/claude-client.d.ts.map +1 -0
  7. package/dist/ai/claude-client.js +340 -0
  8. package/dist/ai/claude-client.js.map +1 -0
  9. package/dist/ai/conversation-manager.d.ts +164 -0
  10. package/dist/ai/conversation-manager.d.ts.map +1 -0
  11. package/dist/ai/conversation-manager.js +614 -0
  12. package/dist/ai/conversation-manager.js.map +1 -0
  13. package/dist/ai/index.d.ts +5 -0
  14. package/dist/ai/index.d.ts.map +1 -0
  15. package/dist/ai/index.js +8 -0
  16. package/dist/ai/index.js.map +1 -0
  17. package/dist/cli.d.ts +36 -0
  18. package/dist/cli.d.ts.map +1 -0
  19. package/dist/cli.js +192 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/commands/ai.d.ts +89 -0
  22. package/dist/commands/ai.d.ts.map +1 -0
  23. package/dist/commands/ai.js +799 -0
  24. package/dist/commands/ai.js.map +1 -0
  25. package/dist/commands/alignment.d.ts +78 -0
  26. package/dist/commands/alignment.d.ts.map +1 -0
  27. package/dist/commands/alignment.js +817 -0
  28. package/dist/commands/alignment.js.map +1 -0
  29. package/dist/commands/analyze-optimized.d.ts +14 -0
  30. package/dist/commands/analyze-optimized.d.ts.map +1 -0
  31. package/dist/commands/analyze-optimized.js +600 -0
  32. package/dist/commands/analyze-optimized.js.map +1 -0
  33. package/dist/commands/analyze.d.ts +65 -0
  34. package/dist/commands/analyze.d.ts.map +1 -0
  35. package/dist/commands/analyze.js +435 -0
  36. package/dist/commands/analyze.js.map +1 -0
  37. package/dist/commands/batch.d.ts +71 -0
  38. package/dist/commands/batch.d.ts.map +1 -0
  39. package/dist/commands/batch.js +738 -0
  40. package/dist/commands/batch.js.map +1 -0
  41. package/dist/commands/chat.d.ts +71 -0
  42. package/dist/commands/chat.d.ts.map +1 -0
  43. package/dist/commands/chat.js +674 -0
  44. package/dist/commands/chat.js.map +1 -0
  45. package/dist/commands/claude-init.d.ts +28 -0
  46. package/dist/commands/claude-init.d.ts.map +1 -0
  47. package/dist/commands/claude-init.js +591 -0
  48. package/dist/commands/claude-init.js.map +1 -0
  49. package/dist/commands/claude-setup.d.ts +119 -0
  50. package/dist/commands/claude-setup.d.ts.map +1 -0
  51. package/dist/commands/claude-setup.js +1073 -0
  52. package/dist/commands/claude-setup.js.map +1 -0
  53. package/dist/commands/computer-setup-commands.d.ts +53 -0
  54. package/dist/commands/computer-setup-commands.d.ts.map +1 -0
  55. package/dist/commands/computer-setup-commands.js +705 -0
  56. package/dist/commands/computer-setup-commands.js.map +1 -0
  57. package/dist/commands/computer-setup.d.ts +7 -0
  58. package/dist/commands/computer-setup.d.ts.map +1 -0
  59. package/dist/commands/computer-setup.js +849 -0
  60. package/dist/commands/computer-setup.js.map +1 -0
  61. package/dist/commands/create-command.d.ts +7 -0
  62. package/dist/commands/create-command.d.ts.map +1 -0
  63. package/dist/commands/create-command.js +158 -0
  64. package/dist/commands/create-command.js.map +1 -0
  65. package/dist/commands/create.d.ts +74 -0
  66. package/dist/commands/create.d.ts.map +1 -0
  67. package/dist/commands/create.js +556 -0
  68. package/dist/commands/create.js.map +1 -0
  69. package/dist/commands/dashboard.d.ts +91 -0
  70. package/dist/commands/dashboard.d.ts.map +1 -0
  71. package/dist/commands/dashboard.js +538 -0
  72. package/dist/commands/dashboard.js.map +1 -0
  73. package/dist/commands/govern.d.ts +70 -0
  74. package/dist/commands/govern.d.ts.map +1 -0
  75. package/dist/commands/govern.js +481 -0
  76. package/dist/commands/govern.js.map +1 -0
  77. package/dist/commands/governance.d.ts +17 -0
  78. package/dist/commands/governance.d.ts.map +1 -0
  79. package/dist/commands/governance.js +703 -0
  80. package/dist/commands/governance.js.map +1 -0
  81. package/dist/commands/guardian.d.ts +20 -0
  82. package/dist/commands/guardian.d.ts.map +1 -0
  83. package/dist/commands/guardian.js +597 -0
  84. package/dist/commands/guardian.js.map +1 -0
  85. package/dist/commands/init.d.ts +59 -0
  86. package/dist/commands/init.d.ts.map +1 -0
  87. package/dist/commands/init.js +650 -0
  88. package/dist/commands/init.js.map +1 -0
  89. package/dist/commands/performance-optimizer.d.ts +30 -0
  90. package/dist/commands/performance-optimizer.d.ts.map +1 -0
  91. package/dist/commands/performance-optimizer.js +650 -0
  92. package/dist/commands/performance-optimizer.js.map +1 -0
  93. package/dist/commands/plugins.d.ts +87 -0
  94. package/dist/commands/plugins.d.ts.map +1 -0
  95. package/dist/commands/plugins.js +685 -0
  96. package/dist/commands/plugins.js.map +1 -0
  97. package/dist/commands/rag.d.ts +7 -0
  98. package/dist/commands/rag.d.ts.map +1 -0
  99. package/dist/commands/rag.js +748 -0
  100. package/dist/commands/rag.js.map +1 -0
  101. package/dist/commands/session.d.ts +41 -0
  102. package/dist/commands/session.d.ts.map +1 -0
  103. package/dist/commands/session.js +441 -0
  104. package/dist/commands/session.js.map +1 -0
  105. package/dist/commands/setup.d.ts +29 -0
  106. package/dist/commands/setup.d.ts.map +1 -0
  107. package/dist/commands/setup.js +397 -0
  108. package/dist/commands/setup.js.map +1 -0
  109. package/dist/commands/test-init.d.ts +9 -0
  110. package/dist/commands/test-init.d.ts.map +1 -0
  111. package/dist/commands/test-init.js +222 -0
  112. package/dist/commands/test-init.js.map +1 -0
  113. package/dist/commands/test.d.ts +25 -0
  114. package/dist/commands/test.d.ts.map +1 -0
  115. package/dist/commands/test.js +217 -0
  116. package/dist/commands/test.js.map +1 -0
  117. package/dist/commands/vp.d.ts +7 -0
  118. package/dist/commands/vp.d.ts.map +1 -0
  119. package/dist/commands/vp.js +571 -0
  120. package/dist/commands/vp.js.map +1 -0
  121. package/dist/commands/watch.d.ts +76 -0
  122. package/dist/commands/watch.d.ts.map +1 -0
  123. package/dist/commands/watch.js +613 -0
  124. package/dist/commands/watch.js.map +1 -0
  125. package/dist/commands/worktree.d.ts +63 -0
  126. package/dist/commands/worktree.d.ts.map +1 -0
  127. package/dist/commands/worktree.js +774 -0
  128. package/dist/commands/worktree.js.map +1 -0
  129. package/dist/context/context-manager.d.ts +155 -0
  130. package/dist/context/context-manager.d.ts.map +1 -0
  131. package/dist/context/context-manager.js +383 -0
  132. package/dist/context/context-manager.js.map +1 -0
  133. package/dist/context/index.d.ts +3 -0
  134. package/dist/context/index.d.ts.map +1 -0
  135. package/dist/context/index.js +6 -0
  136. package/dist/context/index.js.map +1 -0
  137. package/dist/context/session-manager.d.ts +207 -0
  138. package/dist/context/session-manager.d.ts.map +1 -0
  139. package/dist/context/session-manager.js +686 -0
  140. package/dist/context/session-manager.js.map +1 -0
  141. package/dist/index.d.ts +8 -0
  142. package/dist/index.d.ts.map +1 -0
  143. package/dist/index.js +51 -0
  144. package/dist/index.js.map +1 -0
  145. package/dist/interactive/interactive-mode.d.ts +76 -0
  146. package/dist/interactive/interactive-mode.d.ts.map +1 -0
  147. package/dist/interactive/interactive-mode.js +732 -0
  148. package/dist/interactive/interactive-mode.js.map +1 -0
  149. package/dist/nlp/command-mapper.d.ts +174 -0
  150. package/dist/nlp/command-mapper.d.ts.map +1 -0
  151. package/dist/nlp/command-mapper.js +624 -0
  152. package/dist/nlp/command-mapper.js.map +1 -0
  153. package/dist/nlp/command-parser.d.ts +106 -0
  154. package/dist/nlp/command-parser.d.ts.map +1 -0
  155. package/dist/nlp/command-parser.js +417 -0
  156. package/dist/nlp/command-parser.js.map +1 -0
  157. package/dist/nlp/index.d.ts +5 -0
  158. package/dist/nlp/index.d.ts.map +1 -0
  159. package/dist/nlp/index.js +8 -0
  160. package/dist/nlp/index.js.map +1 -0
  161. package/dist/nlp/intent-classifier.d.ts +59 -0
  162. package/dist/nlp/intent-classifier.d.ts.map +1 -0
  163. package/dist/nlp/intent-classifier.js +384 -0
  164. package/dist/nlp/intent-classifier.js.map +1 -0
  165. package/dist/nlp/intent-parser.d.ts +152 -0
  166. package/dist/nlp/intent-parser.d.ts.map +1 -0
  167. package/dist/nlp/intent-parser.js +744 -0
  168. package/dist/nlp/intent-parser.js.map +1 -0
  169. package/dist/plugins/plugin-manager.d.ts +120 -0
  170. package/dist/plugins/plugin-manager.d.ts.map +1 -0
  171. package/dist/plugins/plugin-manager.js +595 -0
  172. package/dist/plugins/plugin-manager.js.map +1 -0
  173. package/dist/types/index.d.ts +224 -0
  174. package/dist/types/index.d.ts.map +1 -0
  175. package/dist/types/index.js +3 -0
  176. package/dist/types/index.js.map +1 -0
  177. package/dist/utils/backup-rollback-manager.d.ts +72 -0
  178. package/dist/utils/backup-rollback-manager.d.ts.map +1 -0
  179. package/dist/utils/backup-rollback-manager.js +289 -0
  180. package/dist/utils/backup-rollback-manager.js.map +1 -0
  181. package/dist/utils/claude-config-installer.d.ts +94 -0
  182. package/dist/utils/claude-config-installer.d.ts.map +1 -0
  183. package/dist/utils/claude-config-installer.js +628 -0
  184. package/dist/utils/claude-config-installer.js.map +1 -0
  185. package/dist/utils/config-manager.d.ts +73 -0
  186. package/dist/utils/config-manager.d.ts.map +1 -0
  187. package/dist/utils/config-manager.js +339 -0
  188. package/dist/utils/config-manager.js.map +1 -0
  189. package/dist/utils/error-handler.d.ts +46 -0
  190. package/dist/utils/error-handler.d.ts.map +1 -0
  191. package/dist/utils/error-handler.js +169 -0
  192. package/dist/utils/error-handler.js.map +1 -0
  193. package/dist/utils/logger.d.ts +25 -0
  194. package/dist/utils/logger.d.ts.map +1 -0
  195. package/dist/utils/logger.js +105 -0
  196. package/dist/utils/logger.js.map +1 -0
  197. package/package.json +6 -4
  198. package/src/ai/ai-service.ts +22 -19
  199. package/src/ai/claude-client.ts +20 -16
  200. package/src/ai/conversation-manager.ts +37 -30
  201. package/src/cli.ts +46 -17
  202. package/src/commands/ai.ts +196 -88
  203. package/src/commands/alignment.ts +1212 -0
  204. package/src/commands/analyze-optimized.ts +394 -89
  205. package/src/commands/analyze.ts +22 -20
  206. package/src/commands/batch.ts +41 -38
  207. package/src/commands/chat.ts +37 -34
  208. package/src/commands/claude-init.ts +38 -30
  209. package/src/commands/claude-setup.ts +692 -97
  210. package/src/commands/computer-setup-commands.ts +45 -39
  211. package/src/commands/computer-setup.ts +490 -20
  212. package/src/commands/create-command.ts +7 -7
  213. package/src/commands/create.ts +36 -33
  214. package/src/commands/dashboard.ts +33 -28
  215. package/src/commands/govern.ts +34 -29
  216. package/src/commands/governance.ts +1005 -0
  217. package/src/commands/guardian.ts +887 -0
  218. package/src/commands/init.ts +112 -22
  219. package/src/commands/performance-optimizer.ts +48 -42
  220. package/src/commands/plugins.ts +35 -32
  221. package/src/commands/project-update.ts +1053 -0
  222. package/src/commands/rag.ts +904 -0
  223. package/src/commands/session.ts +631 -0
  224. package/src/commands/setup.ts +35 -31
  225. package/src/commands/test-init.ts +6 -5
  226. package/src/commands/test.ts +7 -6
  227. package/src/commands/vp.ts +762 -0
  228. package/src/commands/watch.ts +44 -33
  229. package/src/commands/worktree.ts +1057 -0
  230. package/src/context/context-manager.ts +15 -12
  231. package/src/context/session-manager.ts +51 -40
  232. package/src/index.ts +7 -6
  233. package/src/interactive/interactive-mode.ts +25 -18
  234. package/src/lib/conflict-resolution.ts +28 -0
  235. package/src/lib/merge-strategy.ts +28 -0
  236. package/src/lib/safety-mechanisms.ts +47 -0
  237. package/src/lib/state-detection.ts +28 -0
  238. package/src/nlp/command-mapper.ts +35 -30
  239. package/src/nlp/command-parser.ts +20 -17
  240. package/src/nlp/intent-classifier.ts +7 -7
  241. package/src/nlp/intent-parser.ts +61 -49
  242. package/src/plugins/plugin-manager.ts +27 -23
  243. package/src/types/index.ts +1 -1
  244. package/src/types/modules.d.ts +1 -0
  245. package/src/utils/backup-rollback-manager.ts +13 -11
  246. package/src/utils/claude-config-installer.ts +18 -16
  247. package/src/utils/config-manager.ts +12 -9
  248. package/src/utils/error-handler.ts +5 -3
  249. package/src/utils/logger.ts +35 -12
@@ -0,0 +1,1212 @@
1
+ /**
2
+ * Alignment Monitoring CLI Commands
3
+ *
4
+ * Provides commands for monitoring AI agent alignment drift across five dimensions:
5
+ * - Policy Violation Rate
6
+ * - Intent-Outcome Gap
7
+ * - Evaluator Disagreement
8
+ * - Escalation Suppression
9
+ * - Reward Hacking
10
+ */
11
+
12
+ import * as os from 'os';
13
+ import * as path from 'path';
14
+
15
+ import chalk from 'chalk';
16
+ import { Command } from 'commander';
17
+ import fs from 'fs-extra';
18
+ import ora from 'ora';
19
+
20
+ // ============================================================================
21
+ // Types
22
+ // ============================================================================
23
+
24
+ export interface AlignmentDriftMetrics {
25
+ policyViolationRate: number; // % of actions violating constraints (0-1)
26
+ intentOutcomeGap: number; // Divergence from stated goals (0-1)
27
+ evaluatorDisagreement: number; // Human override rate (0-1)
28
+ escalationSuppression: number; // Agents avoiding triggers (0-1)
29
+ rewardHacking: number; // Gaming metrics instances count
30
+ }
31
+
32
+ export interface AlignmentDimension {
33
+ name: string;
34
+ key: keyof AlignmentDriftMetrics;
35
+ threshold: number;
36
+ description: string;
37
+ currentValue: number;
38
+ status: 'healthy' | 'warning' | 'critical';
39
+ }
40
+
41
+ export interface AlignmentReport {
42
+ timestamp: string;
43
+ sessionId?: string;
44
+ overallScore: number; // 0-100 (100 = perfect alignment)
45
+ status: 'healthy' | 'warning' | 'critical';
46
+ metrics: AlignmentDriftMetrics;
47
+ dimensions: AlignmentDimension[];
48
+ recommendations: string[];
49
+ history?: AlignmentHistoryEntry[];
50
+ }
51
+
52
+ export interface AlignmentHistoryEntry {
53
+ timestamp: string;
54
+ sessionId?: string;
55
+ score: number;
56
+ status: 'healthy' | 'warning' | 'critical';
57
+ metrics: AlignmentDriftMetrics;
58
+ }
59
+
60
+ export interface AlignmentDebtReport {
61
+ generatedAt: string;
62
+ period: {
63
+ start: string;
64
+ end: string;
65
+ days: number;
66
+ };
67
+ summary: {
68
+ averageScore: number;
69
+ lowestScore: number;
70
+ highestScore: number;
71
+ trend: 'improving' | 'stable' | 'degrading';
72
+ totalViolations: number;
73
+ };
74
+ dimensionAnalysis: {
75
+ [key: string]: {
76
+ average: number;
77
+ max: number;
78
+ exceedances: number;
79
+ };
80
+ };
81
+ criticalEvents: Array<{
82
+ timestamp: string;
83
+ dimension: string;
84
+ value: number;
85
+ threshold: number;
86
+ }>;
87
+ recommendations: string[];
88
+ }
89
+
90
+ interface DimensionAnalysisEntry {
91
+ average: number;
92
+ max: number;
93
+ exceedances: number;
94
+ }
95
+
96
+ // ============================================================================
97
+ // AlignmentDriftDetector - Embedded Implementation
98
+ // ============================================================================
99
+
100
+ class AlignmentDriftDetector {
101
+ private readonly dataDir: string;
102
+ private readonly historyFile: string;
103
+
104
+ // Thresholds from Three-Tier Architecture spec
105
+ private readonly thresholds = {
106
+ policyViolation: 0.005, // >0.5% daily violations triggers alert
107
+ intentOutcomeGap: 0.15, // >15% divergence is concerning
108
+ evaluatorDisagreement: 0.2, // >20% monthly overrides
109
+ escalationSuppression: 0.4, // >40% drop from baseline
110
+ rewardHacking: 5, // >5 instances/month
111
+ };
112
+
113
+ constructor(dataDir?: string) {
114
+ this.dataDir = dataDir || path.join(os.homedir(), '.wundr', 'alignment');
115
+ this.historyFile = path.join(this.dataDir, 'history.json');
116
+ }
117
+
118
+ async initialize(): Promise<void> {
119
+ await fs.ensureDir(this.dataDir);
120
+
121
+ if (!(await fs.pathExists(this.historyFile))) {
122
+ await fs.writeJson(this.historyFile, { entries: [] }, { spaces: 2 });
123
+ }
124
+ }
125
+
126
+ async calculateAlignmentDebt(sessionId?: string): Promise<number> {
127
+ const metrics = await this.collectMetrics(sessionId);
128
+ return this.calculateScore(metrics);
129
+ }
130
+
131
+ async getAlignmentReport(sessionId?: string): Promise<AlignmentReport> {
132
+ await this.initialize();
133
+
134
+ const metrics = await this.collectMetrics(sessionId);
135
+ const score = this.calculateScore(metrics);
136
+ const status = this.determineStatus(score);
137
+ const dimensions = this.analyzeDimensions(metrics);
138
+ const recommendations = this.generateRecommendations(dimensions, status);
139
+
140
+ const report: AlignmentReport = {
141
+ timestamp: new Date().toISOString(),
142
+ sessionId,
143
+ overallScore: score,
144
+ status,
145
+ metrics,
146
+ dimensions,
147
+ recommendations,
148
+ };
149
+
150
+ // Store in history
151
+ await this.storeHistoryEntry({
152
+ timestamp: report.timestamp,
153
+ sessionId,
154
+ score,
155
+ status,
156
+ metrics,
157
+ });
158
+
159
+ return report;
160
+ }
161
+
162
+ async getAlignmentScore(sessionId?: string): Promise<{
163
+ score: number;
164
+ status: 'healthy' | 'warning' | 'critical';
165
+ color: 'green' | 'yellow' | 'red';
166
+ }> {
167
+ const score = await this.calculateAlignmentDebt(sessionId);
168
+ const status = this.determineStatus(score);
169
+
170
+ return {
171
+ score,
172
+ status,
173
+ color:
174
+ status === 'healthy'
175
+ ? 'green'
176
+ : status === 'warning'
177
+ ? 'yellow'
178
+ : 'red',
179
+ };
180
+ }
181
+
182
+ async getHistory(days: number = 30): Promise<AlignmentHistoryEntry[]> {
183
+ await this.initialize();
184
+
185
+ const history = await this.loadHistory();
186
+ const cutoffDate = new Date();
187
+ cutoffDate.setDate(cutoffDate.getDate() - days);
188
+
189
+ return history.entries
190
+ .filter(
191
+ (entry: AlignmentHistoryEntry) =>
192
+ new Date(entry.timestamp) >= cutoffDate,
193
+ )
194
+ .sort(
195
+ (a: AlignmentHistoryEntry, b: AlignmentHistoryEntry) =>
196
+ new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
197
+ );
198
+ }
199
+
200
+ async getDimensionBreakdown(): Promise<AlignmentDimension[]> {
201
+ const metrics = await this.collectMetrics();
202
+ return this.analyzeDimensions(metrics);
203
+ }
204
+
205
+ async generateDebtReport(
206
+ days: number = 7,
207
+ sessionId?: string,
208
+ outputFile?: string,
209
+ ): Promise<AlignmentDebtReport> {
210
+ await this.initialize();
211
+
212
+ const history = await this.getHistory(days);
213
+ const filteredHistory = sessionId
214
+ ? history.filter(h => h.sessionId === sessionId)
215
+ : history;
216
+
217
+ const endDate = new Date();
218
+ const startDate = new Date();
219
+ startDate.setDate(startDate.getDate() - days);
220
+
221
+ // Calculate summary statistics
222
+ const scores = filteredHistory.map(h => h.score);
223
+ const averageScore =
224
+ scores.length > 0
225
+ ? scores.reduce((a, b) => a + b, 0) / scores.length
226
+ : 100;
227
+ const lowestScore = scores.length > 0 ? Math.min(...scores) : 100;
228
+ const highestScore = scores.length > 0 ? Math.max(...scores) : 100;
229
+
230
+ // Calculate trend
231
+ let trend: 'improving' | 'stable' | 'degrading' = 'stable';
232
+ if (scores.length >= 2) {
233
+ const firstHalf = scores.slice(0, Math.floor(scores.length / 2));
234
+ const secondHalf = scores.slice(Math.floor(scores.length / 2));
235
+ const firstAvg = firstHalf.reduce((a, b) => a + b, 0) / firstHalf.length;
236
+ const secondAvg =
237
+ secondHalf.reduce((a, b) => a + b, 0) / secondHalf.length;
238
+
239
+ if (secondAvg > firstAvg + 5) {
240
+ trend = 'improving';
241
+ } else if (secondAvg < firstAvg - 5) {
242
+ trend = 'degrading';
243
+ }
244
+ }
245
+
246
+ // Analyze dimensions across history
247
+ const dimensionAnalysis: AlignmentDebtReport['dimensionAnalysis'] = {};
248
+ const dimensionKeys: (keyof AlignmentDriftMetrics)[] = [
249
+ 'policyViolationRate',
250
+ 'intentOutcomeGap',
251
+ 'evaluatorDisagreement',
252
+ 'escalationSuppression',
253
+ 'rewardHacking',
254
+ ];
255
+
256
+ for (const key of dimensionKeys) {
257
+ const values = filteredHistory.map(h => h.metrics[key]);
258
+ const threshold = this.getThresholdForKey(key);
259
+
260
+ dimensionAnalysis[key] = {
261
+ average:
262
+ values.length > 0
263
+ ? values.reduce((a, b) => a + b, 0) / values.length
264
+ : 0,
265
+ max: values.length > 0 ? Math.max(...values) : 0,
266
+ exceedances: values.filter(v => v > threshold).length,
267
+ };
268
+ }
269
+
270
+ // Find critical events
271
+ const criticalEvents: AlignmentDebtReport['criticalEvents'] = [];
272
+ for (const entry of filteredHistory) {
273
+ for (const key of dimensionKeys) {
274
+ const threshold = this.getThresholdForKey(key);
275
+ if (entry.metrics[key] > threshold * 2) {
276
+ criticalEvents.push({
277
+ timestamp: entry.timestamp,
278
+ dimension: key,
279
+ value: entry.metrics[key],
280
+ threshold,
281
+ });
282
+ }
283
+ }
284
+ }
285
+
286
+ // Calculate total violations
287
+ const totalViolations = Object.values(dimensionAnalysis).reduce(
288
+ (sum, dim) => sum + dim.exceedances,
289
+ 0,
290
+ );
291
+
292
+ // Generate recommendations
293
+ const recommendations = this.generateDebtRecommendations(
294
+ dimensionAnalysis,
295
+ trend,
296
+ );
297
+
298
+ const report: AlignmentDebtReport = {
299
+ generatedAt: new Date().toISOString(),
300
+ period: {
301
+ start: startDate.toISOString(),
302
+ end: endDate.toISOString(),
303
+ days,
304
+ },
305
+ summary: {
306
+ averageScore,
307
+ lowestScore,
308
+ highestScore,
309
+ trend,
310
+ totalViolations,
311
+ },
312
+ dimensionAnalysis,
313
+ criticalEvents: criticalEvents.slice(0, 20),
314
+ recommendations,
315
+ };
316
+
317
+ if (outputFile) {
318
+ await fs.writeJson(outputFile, report, { spaces: 2 });
319
+ }
320
+
321
+ return report;
322
+ }
323
+
324
+ getThresholds(): typeof this.thresholds {
325
+ return { ...this.thresholds };
326
+ }
327
+
328
+ private async collectMetrics(
329
+ sessionId?: string,
330
+ ): Promise<AlignmentDriftMetrics> {
331
+ const sessionMetricsFile = sessionId
332
+ ? path.join(this.dataDir, 'sessions', `${sessionId}.json`)
333
+ : path.join(this.dataDir, 'current-metrics.json');
334
+
335
+ if (await fs.pathExists(sessionMetricsFile)) {
336
+ try {
337
+ const data = await fs.readJson(sessionMetricsFile);
338
+ return {
339
+ policyViolationRate: data.policyViolationRate ?? 0,
340
+ intentOutcomeGap: data.intentOutcomeGap ?? 0,
341
+ evaluatorDisagreement: data.evaluatorDisagreement ?? 0,
342
+ escalationSuppression: data.escalationSuppression ?? 0,
343
+ rewardHacking: data.rewardHacking ?? 0,
344
+ };
345
+ } catch {
346
+ // Fall through to defaults
347
+ }
348
+ }
349
+
350
+ return {
351
+ policyViolationRate: 0,
352
+ intentOutcomeGap: 0,
353
+ evaluatorDisagreement: 0,
354
+ escalationSuppression: 0,
355
+ rewardHacking: 0,
356
+ };
357
+ }
358
+
359
+ private calculateScore(metrics: AlignmentDriftMetrics): number {
360
+ const normalizedPolicyViolation = Math.min(
361
+ metrics.policyViolationRate / this.thresholds.policyViolation,
362
+ 2,
363
+ );
364
+ const normalizedIntentGap = Math.min(
365
+ metrics.intentOutcomeGap / this.thresholds.intentOutcomeGap,
366
+ 2,
367
+ );
368
+ const normalizedEvaluatorDisagreement = Math.min(
369
+ metrics.evaluatorDisagreement / this.thresholds.evaluatorDisagreement,
370
+ 2,
371
+ );
372
+ const normalizedEscalationSuppression = Math.min(
373
+ metrics.escalationSuppression / this.thresholds.escalationSuppression,
374
+ 2,
375
+ );
376
+ const normalizedRewardHacking = Math.min(
377
+ metrics.rewardHacking / this.thresholds.rewardHacking,
378
+ 2,
379
+ );
380
+
381
+ const weights = {
382
+ policyViolation: 0.25,
383
+ intentOutcomeGap: 0.25,
384
+ evaluatorDisagreement: 0.2,
385
+ escalationSuppression: 0.15,
386
+ rewardHacking: 0.15,
387
+ };
388
+
389
+ const penalty =
390
+ normalizedPolicyViolation * weights.policyViolation +
391
+ normalizedIntentGap * weights.intentOutcomeGap +
392
+ normalizedEvaluatorDisagreement * weights.evaluatorDisagreement +
393
+ normalizedEscalationSuppression * weights.escalationSuppression +
394
+ normalizedRewardHacking * weights.rewardHacking;
395
+
396
+ const score = Math.max(0, Math.min(100, 100 - penalty * 50));
397
+ return Math.round(score * 10) / 10;
398
+ }
399
+
400
+ private determineStatus(score: number): 'healthy' | 'warning' | 'critical' {
401
+ if (score >= 80) {
402
+ return 'healthy';
403
+ }
404
+ if (score >= 50) {
405
+ return 'warning';
406
+ }
407
+ return 'critical';
408
+ }
409
+
410
+ private analyzeDimensions(
411
+ metrics: AlignmentDriftMetrics,
412
+ ): AlignmentDimension[] {
413
+ return [
414
+ {
415
+ name: 'Policy Violation Rate',
416
+ key: 'policyViolationRate',
417
+ threshold: this.thresholds.policyViolation,
418
+ description: 'Percentage of actions violating defined constraints',
419
+ currentValue: metrics.policyViolationRate,
420
+ status: this.getDimensionStatus(
421
+ metrics.policyViolationRate,
422
+ this.thresholds.policyViolation,
423
+ ),
424
+ },
425
+ {
426
+ name: 'Intent-Outcome Gap',
427
+ key: 'intentOutcomeGap',
428
+ threshold: this.thresholds.intentOutcomeGap,
429
+ description: 'Divergence between stated goals and actual outcomes',
430
+ currentValue: metrics.intentOutcomeGap,
431
+ status: this.getDimensionStatus(
432
+ metrics.intentOutcomeGap,
433
+ this.thresholds.intentOutcomeGap,
434
+ ),
435
+ },
436
+ {
437
+ name: 'Evaluator Disagreement',
438
+ key: 'evaluatorDisagreement',
439
+ threshold: this.thresholds.evaluatorDisagreement,
440
+ description: 'Rate of human overrides on agent decisions',
441
+ currentValue: metrics.evaluatorDisagreement,
442
+ status: this.getDimensionStatus(
443
+ metrics.evaluatorDisagreement,
444
+ this.thresholds.evaluatorDisagreement,
445
+ ),
446
+ },
447
+ {
448
+ name: 'Escalation Suppression',
449
+ key: 'escalationSuppression',
450
+ threshold: this.thresholds.escalationSuppression,
451
+ description: 'Drop in escalation triggers compared to baseline',
452
+ currentValue: metrics.escalationSuppression,
453
+ status: this.getDimensionStatus(
454
+ metrics.escalationSuppression,
455
+ this.thresholds.escalationSuppression,
456
+ ),
457
+ },
458
+ {
459
+ name: 'Reward Hacking',
460
+ key: 'rewardHacking',
461
+ threshold: this.thresholds.rewardHacking,
462
+ description: 'Instances of gaming metrics without achieving intent',
463
+ currentValue: metrics.rewardHacking,
464
+ status: this.getDimensionStatus(
465
+ metrics.rewardHacking,
466
+ this.thresholds.rewardHacking,
467
+ ),
468
+ },
469
+ ];
470
+ }
471
+
472
+ private getDimensionStatus(
473
+ value: number,
474
+ threshold: number,
475
+ ): 'healthy' | 'warning' | 'critical' {
476
+ if (value <= threshold) {
477
+ return 'healthy';
478
+ }
479
+ if (value <= threshold * 2) {
480
+ return 'warning';
481
+ }
482
+ return 'critical';
483
+ }
484
+
485
+ private getThresholdForKey(key: keyof AlignmentDriftMetrics): number {
486
+ const thresholdMap: Record<keyof AlignmentDriftMetrics, number> = {
487
+ policyViolationRate: this.thresholds.policyViolation,
488
+ intentOutcomeGap: this.thresholds.intentOutcomeGap,
489
+ evaluatorDisagreement: this.thresholds.evaluatorDisagreement,
490
+ escalationSuppression: this.thresholds.escalationSuppression,
491
+ rewardHacking: this.thresholds.rewardHacking,
492
+ };
493
+ return thresholdMap[key];
494
+ }
495
+
496
+ private generateRecommendations(
497
+ dimensions: AlignmentDimension[],
498
+ status: string,
499
+ ): string[] {
500
+ const recommendations: string[] = [];
501
+
502
+ for (const dim of dimensions) {
503
+ if (dim.status === 'critical') {
504
+ recommendations.push(
505
+ `CRITICAL: ${dim.name} at ${this.formatValue(dim.currentValue, dim.key)} exceeds threshold (${this.formatValue(dim.threshold, dim.key)}). Immediate action required.`,
506
+ );
507
+ } else if (dim.status === 'warning') {
508
+ recommendations.push(
509
+ `WARNING: ${dim.name} at ${this.formatValue(dim.currentValue, dim.key)} approaching threshold (${this.formatValue(dim.threshold, dim.key)}). Monitor closely.`,
510
+ );
511
+ }
512
+ }
513
+
514
+ if (status === 'critical') {
515
+ recommendations.push(
516
+ 'Consider pausing autonomous operations until alignment issues are resolved.',
517
+ );
518
+ recommendations.push('Schedule immediate review with Guardian role.');
519
+ } else if (status === 'warning') {
520
+ recommendations.push(
521
+ 'Review recent agent decisions for potential alignment drift.',
522
+ );
523
+ recommendations.push(
524
+ 'Consider tightening constraints or adding checkpoints.',
525
+ );
526
+ }
527
+
528
+ if (recommendations.length === 0) {
529
+ recommendations.push(
530
+ 'All alignment metrics within acceptable thresholds.',
531
+ );
532
+ }
533
+
534
+ return recommendations;
535
+ }
536
+
537
+ private generateDebtRecommendations(
538
+ dimensionAnalysis: AlignmentDebtReport['dimensionAnalysis'],
539
+ trend: 'improving' | 'stable' | 'degrading',
540
+ ): string[] {
541
+ const recommendations: string[] = [];
542
+
543
+ if (trend === 'degrading') {
544
+ recommendations.push(
545
+ 'Alignment score is degrading over time. Review recent changes to agent configurations.',
546
+ );
547
+ } else if (trend === 'improving') {
548
+ recommendations.push(
549
+ 'Alignment score is improving. Continue current governance practices.',
550
+ );
551
+ }
552
+
553
+ for (const [key, analysis] of Object.entries(dimensionAnalysis)) {
554
+ if ((analysis as DimensionAnalysisEntry).exceedances > 0) {
555
+ const friendlyName = this.getFriendlyDimensionName(key);
556
+ const entry = analysis as DimensionAnalysisEntry;
557
+ recommendations.push(
558
+ `${friendlyName}: ${entry.exceedances} threshold exceedance(s) detected. Average: ${entry.average.toFixed(3)}`,
559
+ );
560
+ }
561
+ }
562
+
563
+ return recommendations;
564
+ }
565
+
566
+ private getFriendlyDimensionName(key: string): string {
567
+ const names: Record<string, string> = {
568
+ policyViolationRate: 'Policy Violation Rate',
569
+ intentOutcomeGap: 'Intent-Outcome Gap',
570
+ evaluatorDisagreement: 'Evaluator Disagreement',
571
+ escalationSuppression: 'Escalation Suppression',
572
+ rewardHacking: 'Reward Hacking',
573
+ };
574
+ return names[key] || key;
575
+ }
576
+
577
+ private formatValue(value: number, key: keyof AlignmentDriftMetrics): string {
578
+ if (key === 'rewardHacking') {
579
+ return `${value} instances`;
580
+ }
581
+ return `${(value * 100).toFixed(1)}%`;
582
+ }
583
+
584
+ private async storeHistoryEntry(entry: AlignmentHistoryEntry): Promise<void> {
585
+ const history = await this.loadHistory();
586
+ history.entries.push(entry);
587
+
588
+ if (history.entries.length > 1000) {
589
+ history.entries = history.entries.slice(-1000);
590
+ }
591
+
592
+ await fs.writeJson(this.historyFile, history, { spaces: 2 });
593
+ }
594
+
595
+ private async loadHistory(): Promise<{ entries: AlignmentHistoryEntry[] }> {
596
+ try {
597
+ if (await fs.pathExists(this.historyFile)) {
598
+ return await fs.readJson(this.historyFile);
599
+ }
600
+ } catch {
601
+ // Fall through to default
602
+ }
603
+ return { entries: [] };
604
+ }
605
+ }
606
+
607
+ // ============================================================================
608
+ // Utility Functions
609
+ // ============================================================================
610
+
611
+ function padRight(str: string, length: number): string {
612
+ return str.length >= length
613
+ ? str.substring(0, length)
614
+ : str + ' '.repeat(length - str.length);
615
+ }
616
+
617
+ function getStatusColor(
618
+ status: 'healthy' | 'warning' | 'critical',
619
+ ): (str: string) => string {
620
+ switch (status) {
621
+ case 'healthy':
622
+ return chalk.green;
623
+ case 'warning':
624
+ return chalk.yellow;
625
+ case 'critical':
626
+ return chalk.red;
627
+ default:
628
+ return chalk.white;
629
+ }
630
+ }
631
+
632
+ function getStatusIcon(status: 'healthy' | 'warning' | 'critical'): string {
633
+ switch (status) {
634
+ case 'healthy':
635
+ return '[HEALTHY]';
636
+ case 'warning':
637
+ return '[WARNING]';
638
+ case 'critical':
639
+ return '[CRITICAL]';
640
+ default:
641
+ return '[UNKNOWN]';
642
+ }
643
+ }
644
+
645
+ function formatScore(score: number): string {
646
+ if (score >= 80) {
647
+ return chalk.green(`${score.toFixed(1)}/100`);
648
+ }
649
+ if (score >= 50) {
650
+ return chalk.yellow(`${score.toFixed(1)}/100`);
651
+ }
652
+ return chalk.red(`${score.toFixed(1)}/100`);
653
+ }
654
+
655
+ function formatTrend(trend: 'improving' | 'stable' | 'degrading'): string {
656
+ switch (trend) {
657
+ case 'improving':
658
+ return chalk.green('Improving [^]');
659
+ case 'degrading':
660
+ return chalk.red('Degrading [v]');
661
+ case 'stable':
662
+ return chalk.gray('Stable [-]');
663
+ default:
664
+ return chalk.gray('Unknown');
665
+ }
666
+ }
667
+
668
+ function formatPercentage(value: number): string {
669
+ return `${(value * 100).toFixed(2)}%`;
670
+ }
671
+
672
+ function formatDimensionValue(value: number, key: string): string {
673
+ if (key === 'rewardHacking') {
674
+ return `${value} instances`;
675
+ }
676
+ return formatPercentage(value);
677
+ }
678
+
679
+ // ============================================================================
680
+ // Command Factory
681
+ // ============================================================================
682
+
683
+ /**
684
+ * Create the alignment command with subcommands
685
+ */
686
+ export function createAlignmentCommand(): Command {
687
+ const command = new Command('alignment')
688
+ .description('Alignment drift monitoring and reporting')
689
+ .addHelpText(
690
+ 'after',
691
+ chalk.gray(`
692
+ Examples:
693
+ ${chalk.green('wundr alignment report')} Generate alignment debt report
694
+ ${chalk.green('wundr alignment score')} Get current alignment score
695
+ ${chalk.green('wundr alignment history --days 7')} Show alignment history for past week
696
+ ${chalk.green('wundr alignment dimensions')} Show breakdown by dimension
697
+ `),
698
+ );
699
+
700
+ // Report subcommand
701
+ command
702
+ .command('report')
703
+ .description('Generate alignment debt report')
704
+ .option('-s, --session <id>', 'Filter by session ID')
705
+ .option('-d, --days <n>', 'Number of days to analyze', '7')
706
+ .option('-o, --output <file>', 'Output file path for JSON report')
707
+ .action(async options => {
708
+ await generateAlignmentReport(options);
709
+ });
710
+
711
+ // Score subcommand
712
+ command
713
+ .command('score')
714
+ .description('Get current alignment score')
715
+ .option('-s, --session <id>', 'Get score for specific session')
716
+ .action(async options => {
717
+ await showAlignmentScore(options);
718
+ });
719
+
720
+ // History subcommand
721
+ command
722
+ .command('history')
723
+ .description('Show alignment history')
724
+ .option('-d, --days <n>', 'Number of days to show', '30')
725
+ .option(
726
+ '-f, --format <type>',
727
+ 'Output format (table, json, chart)',
728
+ 'table',
729
+ )
730
+ .action(async options => {
731
+ await showAlignmentHistory(options);
732
+ });
733
+
734
+ // Dimensions subcommand
735
+ command
736
+ .command('dimensions')
737
+ .description('Show breakdown by dimension')
738
+ .option('-s, --session <id>', 'Get dimensions for specific session')
739
+ .action(async options => {
740
+ await showDimensionBreakdown(options);
741
+ });
742
+
743
+ return command;
744
+ }
745
+
746
+ // ============================================================================
747
+ // Command Implementations
748
+ // ============================================================================
749
+
750
+ async function generateAlignmentReport(options: {
751
+ session?: string;
752
+ days?: string;
753
+ output?: string;
754
+ }): Promise<void> {
755
+ const spinner = ora('Generating alignment debt report...').start();
756
+
757
+ try {
758
+ const detector = new AlignmentDriftDetector();
759
+ await detector.initialize();
760
+
761
+ const days = parseInt(options.days || '7', 10);
762
+ const report = await detector.generateDebtReport(
763
+ days,
764
+ options.session,
765
+ options.output,
766
+ );
767
+
768
+ spinner.stop();
769
+
770
+ // Display report header
771
+ console.log(chalk.cyan('\nAlignment Debt Report'));
772
+ console.log(chalk.gray('='.repeat(70)));
773
+ console.log(
774
+ chalk.white('Generated: ') +
775
+ chalk.gray(new Date(report.generatedAt).toLocaleString()),
776
+ );
777
+ console.log(
778
+ chalk.white('Period: ') +
779
+ chalk.gray(
780
+ `${days} days (${new Date(report.period.start).toLocaleDateString()} - ${new Date(report.period.end).toLocaleDateString()})`,
781
+ ),
782
+ );
783
+
784
+ if (options.session) {
785
+ console.log(chalk.white('Session: ') + chalk.gray(options.session));
786
+ }
787
+
788
+ // Summary section
789
+ console.log(chalk.gray('\n' + '-'.repeat(70)));
790
+ console.log(chalk.cyan('Summary'));
791
+ console.log(
792
+ chalk.white('Average Score: ') +
793
+ formatScore(report.summary.averageScore),
794
+ );
795
+ console.log(
796
+ chalk.white('Lowest Score: ') +
797
+ formatScore(report.summary.lowestScore),
798
+ );
799
+ console.log(
800
+ chalk.white('Highest Score: ') +
801
+ formatScore(report.summary.highestScore),
802
+ );
803
+ console.log(
804
+ chalk.white('Trend: ') + formatTrend(report.summary.trend),
805
+ );
806
+ console.log(
807
+ chalk.white('Total Violations: ') +
808
+ (report.summary.totalViolations > 0
809
+ ? chalk.red(String(report.summary.totalViolations))
810
+ : chalk.green('0')),
811
+ );
812
+
813
+ // Dimension analysis section
814
+ console.log(chalk.gray('\n' + '-'.repeat(70)));
815
+ console.log(chalk.cyan('Dimension Analysis'));
816
+ console.log(
817
+ chalk.cyan(
818
+ padRight('Dimension', 25) +
819
+ padRight('Average', 12) +
820
+ padRight('Max', 12) +
821
+ padRight('Exceedances', 12),
822
+ ),
823
+ );
824
+ console.log(chalk.gray('-'.repeat(61)));
825
+
826
+ const dimensionNames: Record<string, string> = {
827
+ policyViolationRate: 'Policy Violation Rate',
828
+ intentOutcomeGap: 'Intent-Outcome Gap',
829
+ evaluatorDisagreement: 'Evaluator Disagreement',
830
+ escalationSuppression: 'Escalation Suppression',
831
+ rewardHacking: 'Reward Hacking',
832
+ };
833
+
834
+ for (const [key, analysis] of Object.entries(report.dimensionAnalysis)) {
835
+ const name = dimensionNames[key] || key;
836
+ const entry = analysis as DimensionAnalysisEntry;
837
+ const exceedanceColor = entry.exceedances > 0 ? chalk.red : chalk.green;
838
+
839
+ console.log(
840
+ padRight(name, 25) +
841
+ padRight(formatDimensionValue(entry.average, key), 12) +
842
+ padRight(formatDimensionValue(entry.max, key), 12) +
843
+ exceedanceColor(padRight(String(entry.exceedances), 12)),
844
+ );
845
+ }
846
+
847
+ // Critical events section
848
+ if (report.criticalEvents.length > 0) {
849
+ console.log(chalk.gray('\n' + '-'.repeat(70)));
850
+ console.log(chalk.red('Critical Events'));
851
+ for (const event of report.criticalEvents.slice(0, 5)) {
852
+ const dimName = dimensionNames[event.dimension] || event.dimension;
853
+ console.log(
854
+ chalk.red(' [!] ') +
855
+ chalk.gray(new Date(event.timestamp).toLocaleString()) +
856
+ chalk.white(` ${dimName}: `) +
857
+ chalk.red(formatDimensionValue(event.value, event.dimension)) +
858
+ chalk.gray(
859
+ ` (threshold: ${formatDimensionValue(event.threshold, event.dimension)})`,
860
+ ),
861
+ );
862
+ }
863
+ if (report.criticalEvents.length > 5) {
864
+ console.log(
865
+ chalk.gray(` ... and ${report.criticalEvents.length - 5} more`),
866
+ );
867
+ }
868
+ }
869
+
870
+ // Recommendations section
871
+ console.log(chalk.gray('\n' + '-'.repeat(70)));
872
+ console.log(chalk.cyan('Recommendations'));
873
+ for (const rec of report.recommendations) {
874
+ const color = rec.startsWith('CRITICAL')
875
+ ? chalk.red
876
+ : rec.startsWith('WARNING')
877
+ ? chalk.yellow
878
+ : chalk.white;
879
+ console.log(color(` - ${rec}`));
880
+ }
881
+
882
+ console.log(chalk.gray('='.repeat(70)));
883
+
884
+ // Show output file if saved
885
+ if (options.output) {
886
+ console.log(chalk.green(`\nReport saved to: ${options.output}`));
887
+ }
888
+
889
+ console.log('');
890
+ } catch (error) {
891
+ spinner.fail('Failed to generate alignment report');
892
+ console.error(
893
+ chalk.red(error instanceof Error ? error.message : String(error)),
894
+ );
895
+ }
896
+ }
897
+
898
+ async function showAlignmentScore(options: {
899
+ session?: string;
900
+ }): Promise<void> {
901
+ const spinner = ora('Calculating alignment score...').start();
902
+
903
+ try {
904
+ const detector = new AlignmentDriftDetector();
905
+ await detector.initialize();
906
+
907
+ const result = await detector.getAlignmentScore(options.session);
908
+
909
+ spinner.stop();
910
+
911
+ console.log(chalk.cyan('\nAlignment Score'));
912
+ console.log(chalk.gray('='.repeat(40)));
913
+
914
+ // Large score display with color
915
+ const scoreColor =
916
+ result.color === 'green'
917
+ ? chalk.green
918
+ : result.color === 'yellow'
919
+ ? chalk.yellow
920
+ : chalk.red;
921
+
922
+ const scoreDisplay = `
923
+ ${scoreColor(' +-----------------+')}
924
+ ${scoreColor(' | |')}
925
+ ${scoreColor(' |')} ${chalk.bold(scoreColor(result.score.toFixed(1)))} ${scoreColor('|')}
926
+ ${scoreColor(' |')} ${chalk.gray('/100')} ${scoreColor('|')}
927
+ ${scoreColor(' | |')}
928
+ ${scoreColor(' +-----------------+')}
929
+ `;
930
+ console.log(scoreDisplay);
931
+
932
+ const statusColor = getStatusColor(result.status);
933
+ console.log(
934
+ chalk.white('Status: ') + statusColor(getStatusIcon(result.status)),
935
+ );
936
+
937
+ if (options.session) {
938
+ console.log(chalk.white('Session: ') + chalk.gray(options.session));
939
+ }
940
+
941
+ // Show quick dimension summary
942
+ const report = await detector.getAlignmentReport(options.session);
943
+ console.log(chalk.gray('\n' + '-'.repeat(40)));
944
+ console.log(chalk.cyan('Quick Dimension Status'));
945
+
946
+ for (const dim of report.dimensions) {
947
+ const dimStatusColor = getStatusColor(dim.status);
948
+ const icon =
949
+ dim.status === 'healthy'
950
+ ? '[OK]'
951
+ : dim.status === 'warning'
952
+ ? '[!]'
953
+ : '[X]';
954
+ console.log(
955
+ dimStatusColor(` ${icon} `) +
956
+ chalk.white(padRight(dim.name, 25)) +
957
+ dimStatusColor(formatDimensionValue(dim.currentValue, dim.key)),
958
+ );
959
+ }
960
+
961
+ console.log(chalk.gray('='.repeat(40)));
962
+ console.log('');
963
+ } catch (error) {
964
+ spinner.fail('Failed to get alignment score');
965
+ console.error(
966
+ chalk.red(error instanceof Error ? error.message : String(error)),
967
+ );
968
+ }
969
+ }
970
+
971
+ async function showAlignmentHistory(options: {
972
+ days?: string;
973
+ format?: 'table' | 'json' | 'chart';
974
+ }): Promise<void> {
975
+ const spinner = ora('Loading alignment history...').start();
976
+
977
+ try {
978
+ const detector = new AlignmentDriftDetector();
979
+ await detector.initialize();
980
+
981
+ const days = parseInt(options.days || '30', 10);
982
+ const history = await detector.getHistory(days);
983
+
984
+ spinner.stop();
985
+
986
+ if (options.format === 'json') {
987
+ console.log(
988
+ JSON.stringify(
989
+ {
990
+ timestamp: new Date().toISOString(),
991
+ days,
992
+ count: history.length,
993
+ entries: history,
994
+ },
995
+ null,
996
+ 2,
997
+ ),
998
+ );
999
+ return;
1000
+ }
1001
+
1002
+ console.log(chalk.cyan('\nAlignment History'));
1003
+ console.log(chalk.gray('='.repeat(80)));
1004
+ console.log(
1005
+ chalk.gray(`Showing last ${days} days (${history.length} entries)`),
1006
+ );
1007
+
1008
+ if (history.length === 0) {
1009
+ console.log(chalk.yellow('\nNo alignment history found.'));
1010
+ console.log(chalk.gray('Run "wundr alignment score" to start tracking.'));
1011
+ console.log('');
1012
+ return;
1013
+ }
1014
+
1015
+ if (options.format === 'chart') {
1016
+ displayHistoryChart(history);
1017
+ } else {
1018
+ // Table format (default)
1019
+ console.log(chalk.gray('\n' + '-'.repeat(80)));
1020
+ console.log(
1021
+ chalk.cyan(
1022
+ padRight('Timestamp', 22) +
1023
+ padRight('Score', 10) +
1024
+ padRight('Status', 12) +
1025
+ padRight('Session', 20) +
1026
+ padRight('Policy', 10),
1027
+ ),
1028
+ );
1029
+ console.log(chalk.gray('-'.repeat(80)));
1030
+
1031
+ for (const entry of history.slice(0, 20)) {
1032
+ const statusColor = getStatusColor(entry.status);
1033
+ const timestamp = new Date(entry.timestamp).toLocaleString();
1034
+
1035
+ console.log(
1036
+ padRight(timestamp, 22) +
1037
+ formatScore(entry.score).padEnd(19) + // Account for ANSI codes
1038
+ statusColor(padRight(getStatusIcon(entry.status), 12)) +
1039
+ chalk.gray(padRight(entry.sessionId || '-', 20)) +
1040
+ padRight(formatPercentage(entry.metrics.policyViolationRate), 10),
1041
+ );
1042
+ }
1043
+
1044
+ if (history.length > 20) {
1045
+ console.log(chalk.gray(`... and ${history.length - 20} more entries`));
1046
+ }
1047
+ }
1048
+
1049
+ console.log(chalk.gray('='.repeat(80)));
1050
+ console.log('');
1051
+ } catch (error) {
1052
+ spinner.fail('Failed to load alignment history');
1053
+ console.error(
1054
+ chalk.red(error instanceof Error ? error.message : String(error)),
1055
+ );
1056
+ }
1057
+ }
1058
+
1059
+ function displayHistoryChart(history: AlignmentHistoryEntry[]): void {
1060
+ const chartHeight = 10;
1061
+ const chartWidth = Math.min(60, history.length);
1062
+
1063
+ const step = Math.ceil(history.length / chartWidth);
1064
+ const sampledHistory = history
1065
+ .filter((_, i) => i % step === 0)
1066
+ .slice(0, chartWidth);
1067
+
1068
+ console.log(chalk.gray('\n Score Trend:'));
1069
+ console.log(chalk.gray(' 100 |'));
1070
+
1071
+ for (let row = chartHeight; row >= 0; row--) {
1072
+ const threshold = (row / chartHeight) * 100;
1073
+ let line = ` ${String(Math.round(threshold)).padStart(3)} |`;
1074
+
1075
+ for (const entry of sampledHistory) {
1076
+ if (
1077
+ entry.score >= threshold &&
1078
+ entry.score < threshold + 100 / chartHeight
1079
+ ) {
1080
+ const color =
1081
+ entry.score >= 80
1082
+ ? chalk.green
1083
+ : entry.score >= 50
1084
+ ? chalk.yellow
1085
+ : chalk.red;
1086
+ line += color('*');
1087
+ } else if (entry.score >= threshold) {
1088
+ line += chalk.gray('|');
1089
+ } else {
1090
+ line += ' ';
1091
+ }
1092
+ }
1093
+
1094
+ console.log(line);
1095
+ }
1096
+
1097
+ console.log(chalk.gray(' 0 +' + '-'.repeat(chartWidth)));
1098
+ console.log(
1099
+ chalk.gray(
1100
+ ' ' +
1101
+ 'oldest'.padEnd(chartWidth / 2) +
1102
+ 'newest'.padStart(chartWidth / 2),
1103
+ ),
1104
+ );
1105
+ }
1106
+
1107
+ async function showDimensionBreakdown(options: {
1108
+ session?: string;
1109
+ }): Promise<void> {
1110
+ const spinner = ora('Loading dimension breakdown...').start();
1111
+
1112
+ try {
1113
+ const detector = new AlignmentDriftDetector();
1114
+ await detector.initialize();
1115
+
1116
+ const dimensions = await detector.getDimensionBreakdown();
1117
+ const thresholds = detector.getThresholds();
1118
+
1119
+ spinner.stop();
1120
+
1121
+ console.log(chalk.cyan('\nAlignment Dimensions'));
1122
+ console.log(chalk.gray('='.repeat(90)));
1123
+
1124
+ if (options.session) {
1125
+ console.log(chalk.white('Session: ') + chalk.gray(options.session));
1126
+ console.log('');
1127
+ }
1128
+
1129
+ // Display each dimension in detail
1130
+ for (const dim of dimensions) {
1131
+ const statusColor = getStatusColor(dim.status);
1132
+ const icon =
1133
+ dim.status === 'healthy'
1134
+ ? '[OK]'
1135
+ : dim.status === 'warning'
1136
+ ? '[!!]'
1137
+ : '[XX]';
1138
+
1139
+ console.log(chalk.gray('-'.repeat(90)));
1140
+ console.log(chalk.bold(statusColor(`${icon} ${dim.name}`)));
1141
+ console.log(chalk.gray(` ${dim.description}`));
1142
+ console.log('');
1143
+
1144
+ // Value bar visualization
1145
+ const barWidth = 40;
1146
+ const valueRatio = Math.min(dim.currentValue / (dim.threshold * 3), 1);
1147
+ const thresholdPos = Math.floor(
1148
+ (dim.threshold / (dim.threshold * 3)) * barWidth,
1149
+ );
1150
+ const valuePos = Math.floor(valueRatio * barWidth);
1151
+
1152
+ let bar = '';
1153
+ for (let i = 0; i < barWidth; i++) {
1154
+ if (i === thresholdPos) {
1155
+ bar += chalk.yellow('|');
1156
+ } else if (i < valuePos) {
1157
+ bar += statusColor('#');
1158
+ } else {
1159
+ bar += chalk.gray('-');
1160
+ }
1161
+ }
1162
+
1163
+ console.log(` [${bar}]`);
1164
+ console.log(
1165
+ ` ${chalk.white('Current:')} ${statusColor(formatDimensionValue(dim.currentValue, dim.key))} ${chalk.white('Threshold:')} ${chalk.yellow(formatDimensionValue(dim.threshold, dim.key))}`,
1166
+ );
1167
+ console.log(
1168
+ ` ${chalk.white('Status:')} ${statusColor(dim.status.toUpperCase())}`,
1169
+ );
1170
+ }
1171
+
1172
+ // Threshold reference
1173
+ console.log(chalk.gray('\n' + '='.repeat(90)));
1174
+ console.log(chalk.cyan('Threshold Reference'));
1175
+ console.log(
1176
+ chalk.gray(
1177
+ ` Policy Violation: >${formatPercentage(thresholds.policyViolation)} daily violations`,
1178
+ ),
1179
+ );
1180
+ console.log(
1181
+ chalk.gray(
1182
+ ` Intent-Outcome Gap: >${formatPercentage(thresholds.intentOutcomeGap)} divergence`,
1183
+ ),
1184
+ );
1185
+ console.log(
1186
+ chalk.gray(
1187
+ ` Evaluator Disagreement:>${formatPercentage(thresholds.evaluatorDisagreement)} monthly overrides`,
1188
+ ),
1189
+ );
1190
+ console.log(
1191
+ chalk.gray(
1192
+ ` Escalation Suppression:>${formatPercentage(thresholds.escalationSuppression)} drop from baseline`,
1193
+ ),
1194
+ );
1195
+ console.log(
1196
+ chalk.gray(
1197
+ ` Reward Hacking: >${thresholds.rewardHacking} instances/month`,
1198
+ ),
1199
+ );
1200
+
1201
+ console.log(chalk.gray('='.repeat(90)));
1202
+ console.log('');
1203
+ } catch (error) {
1204
+ spinner.fail('Failed to load dimension breakdown');
1205
+ console.error(
1206
+ chalk.red(error instanceof Error ? error.message : String(error)),
1207
+ );
1208
+ }
1209
+ }
1210
+
1211
+ // Export the command
1212
+ export default createAlignmentCommand;