@ginkoai/cli 2.0.0-beta.4 → 2.0.0-beta.5

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.
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @fileType: utility
3
+ * @status: current
4
+ * @updated: 2026-01-03
5
+ * @tags: [staleness, detection, team, sync, EPIC-008]
6
+ * @related: [../commands/sync/team-sync.ts, ../commands/start/start-reflection.ts]
7
+ * @priority: high
8
+ * @complexity: medium
9
+ * @dependencies: [chalk]
10
+ */
11
+ export interface StalenessConfig {
12
+ /** Days before showing warning (default: 1) */
13
+ warningThresholdDays: number;
14
+ /** Days before showing critical warning (default: 7) */
15
+ criticalThresholdDays: number;
16
+ }
17
+ export interface ChangedSinceSync {
18
+ adrs: number;
19
+ patterns: number;
20
+ sprints: number;
21
+ total: number;
22
+ }
23
+ export interface StalenessResult {
24
+ /** Whether context is stale (exceeds warning threshold) */
25
+ isStale: boolean;
26
+ /** Severity level based on days since sync */
27
+ severity: 'none' | 'warning' | 'critical';
28
+ /** Number of days since last sync (Infinity if never synced) */
29
+ daysSinceSync: number;
30
+ /** ISO timestamp of last sync, null if never synced */
31
+ lastSyncAt: string | null;
32
+ /** Count of changes by category since last sync */
33
+ changedSinceSync: ChangedSinceSync;
34
+ /** Human-readable message describing staleness state */
35
+ message: string;
36
+ }
37
+ /**
38
+ * Check staleness of team context for current user
39
+ *
40
+ * @param graphId - The graph ID to check
41
+ * @param token - Bearer token for authentication
42
+ * @param config - Optional staleness configuration
43
+ * @returns StalenessResult with severity, days since sync, and changes
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const result = await checkStaleness(graphId, token, {
48
+ * warningThresholdDays: 1,
49
+ * criticalThresholdDays: 7,
50
+ * });
51
+ *
52
+ * if (result.severity === 'critical') {
53
+ * console.log(chalk.red(result.message));
54
+ * }
55
+ * ```
56
+ */
57
+ export declare function checkStaleness(graphId: string, token: string, config?: Partial<StalenessConfig>): Promise<StalenessResult>;
58
+ /**
59
+ * Display staleness warning in CLI with appropriate styling
60
+ *
61
+ * @param result - The staleness check result
62
+ */
63
+ export declare function displayStalenessWarning(result: StalenessResult): void;
64
+ /**
65
+ * Calculate number of days since last sync
66
+ */
67
+ declare function calculateDaysSinceSync(lastSyncAt: string | null): number;
68
+ /**
69
+ * Determine severity based on days since sync and config thresholds
70
+ */
71
+ declare function determineSeverity(daysSinceSync: number, config: StalenessConfig): 'none' | 'warning' | 'critical';
72
+ /**
73
+ * Format human-readable message based on staleness state
74
+ */
75
+ declare function formatMessage(severity: 'none' | 'warning' | 'critical', daysSinceSync: number, lastSyncAt: string | null, changes: ChangedSinceSync): string;
76
+ /**
77
+ * Format relative time for display
78
+ */
79
+ declare function formatRelativeTime(dateStr: string): string;
80
+ export declare const _internal: {
81
+ calculateDaysSinceSync: typeof calculateDaysSinceSync;
82
+ determineSeverity: typeof determineSeverity;
83
+ formatMessage: typeof formatMessage;
84
+ formatRelativeTime: typeof formatRelativeTime;
85
+ DEFAULT_CONFIG: StalenessConfig;
86
+ };
87
+ export {};
88
+ //# sourceMappingURL=staleness-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staleness-detector.d.ts","sourceRoot":"","sources":["../../src/lib/staleness-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAoBH,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,oBAAoB,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;IAC1C,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mDAAmD;IACnD,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;CACjB;AAiBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAChC,OAAO,CAAC,eAAe,CAAC,CAwF1B;AAMD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAiCrE;AAMD;;GAEG;AACH,iBAAS,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAWjE;AAED;;GAEG;AACH,iBAAS,iBAAiB,CACxB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,eAAe,GACtB,MAAM,GAAG,SAAS,GAAG,UAAU,CAQjC;AA8CD;;GAEG;AACH,iBAAS,aAAa,CACpB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,EACzC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,OAAO,EAAE,gBAAgB,GACxB,MAAM,CAqBR;AAED;;GAEG;AACH,iBAAS,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAanD;AAaD,eAAO,MAAM,SAAS;;;;;;CAMrB,CAAC"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * @fileType: utility
3
+ * @status: current
4
+ * @updated: 2026-01-03
5
+ * @tags: [staleness, detection, team, sync, EPIC-008]
6
+ * @related: [../commands/sync/team-sync.ts, ../commands/start/start-reflection.ts]
7
+ * @priority: high
8
+ * @complexity: medium
9
+ * @dependencies: [chalk]
10
+ */
11
+ /**
12
+ * Staleness Detection System (EPIC-008 Sprint 2)
13
+ *
14
+ * Provides comprehensive staleness detection for team context synchronization.
15
+ * Used by both CLI (ginko start) and Dashboard (StalenessWarning component).
16
+ *
17
+ * Key features:
18
+ * - Configurable warning/critical thresholds
19
+ * - Tracks changes since last sync (ADRs, Patterns, Sprints)
20
+ * - Human-readable messages for different severity levels
21
+ */
22
+ import chalk from 'chalk';
23
+ // =============================================================================
24
+ // Constants
25
+ // =============================================================================
26
+ const API_BASE = process.env.GINKO_API_URL || 'https://app.ginkoai.com';
27
+ const DEFAULT_CONFIG = {
28
+ warningThresholdDays: 1,
29
+ criticalThresholdDays: 7,
30
+ };
31
+ // =============================================================================
32
+ // Main API
33
+ // =============================================================================
34
+ /**
35
+ * Check staleness of team context for current user
36
+ *
37
+ * @param graphId - The graph ID to check
38
+ * @param token - Bearer token for authentication
39
+ * @param config - Optional staleness configuration
40
+ * @returns StalenessResult with severity, days since sync, and changes
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const result = await checkStaleness(graphId, token, {
45
+ * warningThresholdDays: 1,
46
+ * criticalThresholdDays: 7,
47
+ * });
48
+ *
49
+ * if (result.severity === 'critical') {
50
+ * console.log(chalk.red(result.message));
51
+ * }
52
+ * ```
53
+ */
54
+ export async function checkStaleness(graphId, token, config) {
55
+ const mergedConfig = {
56
+ ...DEFAULT_CONFIG,
57
+ ...config,
58
+ };
59
+ try {
60
+ // Fetch membership status (includes last_sync_at)
61
+ const membershipUrl = new URL(`${API_BASE}/api/v1/graph/membership`);
62
+ membershipUrl.searchParams.set('graphId', graphId);
63
+ const membershipResponse = await fetch(membershipUrl.toString(), {
64
+ headers: {
65
+ Authorization: `Bearer ${token}`,
66
+ 'Content-Type': 'application/json',
67
+ },
68
+ });
69
+ if (membershipResponse.status === 404) {
70
+ // User is not a team member - return non-stale (no team context to sync)
71
+ return createResult({
72
+ isStale: false,
73
+ severity: 'none',
74
+ daysSinceSync: 0,
75
+ lastSyncAt: null,
76
+ changedSinceSync: { adrs: 0, patterns: 0, sprints: 0, total: 0 },
77
+ message: 'Not a team member',
78
+ });
79
+ }
80
+ if (!membershipResponse.ok) {
81
+ // API error - return safe default (non-stale to avoid blocking)
82
+ return createResult({
83
+ isStale: false,
84
+ severity: 'none',
85
+ daysSinceSync: 0,
86
+ lastSyncAt: null,
87
+ changedSinceSync: { adrs: 0, patterns: 0, sprints: 0, total: 0 },
88
+ message: 'Could not verify membership status',
89
+ });
90
+ }
91
+ const membershipData = await membershipResponse.json();
92
+ const lastSyncAt = membershipData.membership?.last_sync_at || null;
93
+ // Calculate days since sync
94
+ const daysSinceSync = calculateDaysSinceSync(lastSyncAt);
95
+ // Determine severity based on thresholds
96
+ const severity = determineSeverity(daysSinceSync, mergedConfig);
97
+ // If not stale, return early (skip expensive changes query)
98
+ if (severity === 'none') {
99
+ return createResult({
100
+ isStale: false,
101
+ severity: 'none',
102
+ daysSinceSync,
103
+ lastSyncAt,
104
+ changedSinceSync: { adrs: 0, patterns: 0, sprints: 0, total: 0 },
105
+ message: formatMessage(severity, daysSinceSync, lastSyncAt, { adrs: 0, patterns: 0, sprints: 0, total: 0 }),
106
+ });
107
+ }
108
+ // Fetch changes since last sync
109
+ const changedSinceSync = await fetchChangesSinceSync(graphId, token, lastSyncAt);
110
+ // Generate human-readable message
111
+ const message = formatMessage(severity, daysSinceSync, lastSyncAt, changedSinceSync);
112
+ return createResult({
113
+ isStale: true,
114
+ severity,
115
+ daysSinceSync,
116
+ lastSyncAt,
117
+ changedSinceSync,
118
+ message,
119
+ });
120
+ }
121
+ catch (error) {
122
+ // Network error - return safe default
123
+ return createResult({
124
+ isStale: false,
125
+ severity: 'none',
126
+ daysSinceSync: 0,
127
+ lastSyncAt: null,
128
+ changedSinceSync: { adrs: 0, patterns: 0, sprints: 0, total: 0 },
129
+ message: 'Could not check staleness (offline?)',
130
+ });
131
+ }
132
+ }
133
+ // =============================================================================
134
+ // CLI Display Helper
135
+ // =============================================================================
136
+ /**
137
+ * Display staleness warning in CLI with appropriate styling
138
+ *
139
+ * @param result - The staleness check result
140
+ */
141
+ export function displayStalenessWarning(result) {
142
+ if (!result.isStale) {
143
+ return;
144
+ }
145
+ console.log('');
146
+ if (result.severity === 'critical') {
147
+ console.log(chalk.red.bold('🚨 Team context is critically stale'));
148
+ console.log(chalk.red(` ${result.message}`));
149
+ }
150
+ else {
151
+ console.log(chalk.yellow('⚠️ Team context may be stale'));
152
+ console.log(chalk.yellow(` ${result.message}`));
153
+ }
154
+ // Show changes breakdown if any
155
+ if (result.changedSinceSync.total > 0) {
156
+ console.log('');
157
+ console.log(chalk.dim(' Changes since last sync:'));
158
+ if (result.changedSinceSync.adrs > 0) {
159
+ console.log(chalk.dim(` - ${result.changedSinceSync.adrs} ADR${result.changedSinceSync.adrs === 1 ? '' : 's'}`));
160
+ }
161
+ if (result.changedSinceSync.patterns > 0) {
162
+ console.log(chalk.dim(` - ${result.changedSinceSync.patterns} Pattern${result.changedSinceSync.patterns === 1 ? '' : 's'}`));
163
+ }
164
+ if (result.changedSinceSync.sprints > 0) {
165
+ console.log(chalk.dim(` - ${result.changedSinceSync.sprints} Sprint${result.changedSinceSync.sprints === 1 ? '' : 's'}`));
166
+ }
167
+ }
168
+ console.log('');
169
+ console.log(chalk.dim(' Run `ginko sync` to pull team updates.'));
170
+ console.log('');
171
+ }
172
+ // =============================================================================
173
+ // Helper Functions
174
+ // =============================================================================
175
+ /**
176
+ * Calculate number of days since last sync
177
+ */
178
+ function calculateDaysSinceSync(lastSyncAt) {
179
+ if (!lastSyncAt) {
180
+ return Infinity;
181
+ }
182
+ const lastSync = new Date(lastSyncAt);
183
+ const now = new Date();
184
+ const diffMs = now.getTime() - lastSync.getTime();
185
+ const daysSinceSync = diffMs / (1000 * 60 * 60 * 24);
186
+ return Math.floor(daysSinceSync);
187
+ }
188
+ /**
189
+ * Determine severity based on days since sync and config thresholds
190
+ */
191
+ function determineSeverity(daysSinceSync, config) {
192
+ if (daysSinceSync >= config.criticalThresholdDays) {
193
+ return 'critical';
194
+ }
195
+ if (daysSinceSync >= config.warningThresholdDays) {
196
+ return 'warning';
197
+ }
198
+ return 'none';
199
+ }
200
+ /**
201
+ * Fetch count of changes since last sync from API
202
+ */
203
+ async function fetchChangesSinceSync(graphId, token, lastSyncAt) {
204
+ try {
205
+ const url = new URL(`${API_BASE}/api/v1/graph/changes`);
206
+ url.searchParams.set('graphId', graphId);
207
+ if (lastSyncAt) {
208
+ url.searchParams.set('since', lastSyncAt);
209
+ }
210
+ const response = await fetch(url.toString(), {
211
+ headers: {
212
+ Authorization: `Bearer ${token}`,
213
+ 'Content-Type': 'application/json',
214
+ },
215
+ });
216
+ if (!response.ok) {
217
+ // Fallback if endpoint not available
218
+ return { adrs: 0, patterns: 0, sprints: 0, total: 0 };
219
+ }
220
+ const data = await response.json();
221
+ // API returns { changes: { ADR: n, Pattern: n, Sprint: n, ... }, total: n }
222
+ return {
223
+ adrs: data.changes?.ADR || 0,
224
+ patterns: data.changes?.Pattern || 0,
225
+ sprints: data.changes?.Sprint || 0,
226
+ total: data.total || 0,
227
+ };
228
+ }
229
+ catch {
230
+ return { adrs: 0, patterns: 0, sprints: 0, total: 0 };
231
+ }
232
+ }
233
+ /**
234
+ * Format human-readable message based on staleness state
235
+ */
236
+ function formatMessage(severity, daysSinceSync, lastSyncAt, changes) {
237
+ if (severity === 'none') {
238
+ if (!lastSyncAt) {
239
+ return 'No sync history';
240
+ }
241
+ return `Last synced ${formatRelativeTime(lastSyncAt)}`;
242
+ }
243
+ if (!lastSyncAt || daysSinceSync === Infinity) {
244
+ return 'Never synced - team context not loaded';
245
+ }
246
+ const changeInfo = changes.total > 0
247
+ ? ` (${changes.total} change${changes.total === 1 ? '' : 's'})`
248
+ : '';
249
+ if (severity === 'critical') {
250
+ return `${daysSinceSync} days since last sync${changeInfo}. Team patterns and ADRs may be outdated.`;
251
+ }
252
+ return `${daysSinceSync} day${daysSinceSync === 1 ? '' : 's'} since last sync${changeInfo}.`;
253
+ }
254
+ /**
255
+ * Format relative time for display
256
+ */
257
+ function formatRelativeTime(dateStr) {
258
+ const date = new Date(dateStr);
259
+ const now = new Date();
260
+ const diffMs = now.getTime() - date.getTime();
261
+ const diffMins = Math.floor(diffMs / (1000 * 60));
262
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
263
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
264
+ if (diffMins < 1)
265
+ return 'just now';
266
+ if (diffMins < 60)
267
+ return `${diffMins} minute${diffMins === 1 ? '' : 's'} ago`;
268
+ if (diffHours < 24)
269
+ return `${diffHours} hour${diffHours === 1 ? '' : 's'} ago`;
270
+ if (diffDays < 7)
271
+ return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
272
+ return date.toLocaleDateString();
273
+ }
274
+ /**
275
+ * Helper to create a StalenessResult with proper types
276
+ */
277
+ function createResult(partial) {
278
+ return partial;
279
+ }
280
+ // =============================================================================
281
+ // Exports for Testing
282
+ // =============================================================================
283
+ export const _internal = {
284
+ calculateDaysSinceSync,
285
+ determineSeverity,
286
+ formatMessage,
287
+ formatRelativeTime,
288
+ DEFAULT_CONFIG,
289
+ };
290
+ //# sourceMappingURL=staleness-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staleness-detector.js","sourceRoot":"","sources":["../../src/lib/staleness-detector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @fileType: utility
3
+ * @status: current
4
+ * @updated: 2026-01-03
5
+ * @tags: [sync, team, reporting, changes, EPIC-008]
6
+ * @related: [../commands/sync/sync-command.ts, ../commands/sync/team-sync.ts, staleness-detector.ts]
7
+ * @priority: high
8
+ * @complexity: medium
9
+ * @dependencies: [chalk]
10
+ */
11
+ /**
12
+ * Change details for a single node type
13
+ */
14
+ export interface TypeChange {
15
+ type: string;
16
+ action: string;
17
+ count: number;
18
+ titles: string[];
19
+ }
20
+ /**
21
+ * Summary of changes for a single team member
22
+ */
23
+ export interface MemberChangeSummary {
24
+ email: string;
25
+ displayName: string | null;
26
+ changes: TypeChange[];
27
+ totalChanges: number;
28
+ }
29
+ /**
30
+ * Summary of all team changes since last sync
31
+ */
32
+ export interface TeamChangeSummary {
33
+ byMember: Map<string, MemberChangeSummary>;
34
+ totalChanges: number;
35
+ sinceSyncAt: string | null;
36
+ period: string;
37
+ }
38
+ /**
39
+ * Raw change record from API
40
+ */
41
+ interface RawChangeRecord {
42
+ id: string;
43
+ type: string;
44
+ title: string;
45
+ action: 'created' | 'updated' | 'deleted';
46
+ editedBy: string;
47
+ editedByName?: string | null;
48
+ editedAt: string;
49
+ }
50
+ /**
51
+ * Fetch team changes since last sync
52
+ *
53
+ * @param graphId - The graph ID to query
54
+ * @param token - Bearer token for authentication
55
+ * @param lastSyncAt - ISO timestamp of last sync (null if never synced)
56
+ * @returns TeamChangeSummary with changes grouped by member
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const summary = await getTeamChangesSinceLast(graphId, token, '2026-01-01T00:00:00Z');
61
+ * if (summary.totalChanges > 0) {
62
+ * displayTeamChangeReport(summary);
63
+ * }
64
+ * ```
65
+ */
66
+ export declare function getTeamChangesSinceLast(graphId: string, token: string, lastSyncAt: string | null): Promise<TeamChangeSummary>;
67
+ /**
68
+ * Aggregate raw changes into team summary
69
+ */
70
+ declare function aggregateChanges(changes: RawChangeRecord[], lastSyncAt: string | null): TeamChangeSummary;
71
+ /**
72
+ * Create empty summary (used for error cases)
73
+ */
74
+ declare function createEmptySummary(lastSyncAt: string | null): TeamChangeSummary;
75
+ /**
76
+ * Calculate human-readable period string
77
+ */
78
+ declare function calculatePeriod(lastSyncAt: string | null): string;
79
+ /**
80
+ * Format team change report as string (for logging or testing)
81
+ *
82
+ * @param summary - The team change summary to format
83
+ * @returns Formatted string with colors stripped for logging
84
+ */
85
+ export declare function formatTeamChangeReport(summary: TeamChangeSummary): string;
86
+ /**
87
+ * Display team change report to console with chalk colors
88
+ *
89
+ * @param summary - The team change summary to display
90
+ */
91
+ export declare function displayTeamChangeReport(summary: TeamChangeSummary): void;
92
+ /**
93
+ * Display compact change summary (single line)
94
+ *
95
+ * @param summary - The team change summary
96
+ * @returns Single line summary string
97
+ */
98
+ export declare function formatCompactSummary(summary: TeamChangeSummary): string;
99
+ /**
100
+ * Format email for display (truncate domain)
101
+ */
102
+ declare function formatEmail(email: string): string;
103
+ export declare const _internal: {
104
+ aggregateChanges: typeof aggregateChanges;
105
+ calculatePeriod: typeof calculatePeriod;
106
+ formatEmail: typeof formatEmail;
107
+ createEmptySummary: typeof createEmptySummary;
108
+ };
109
+ export {};
110
+ //# sourceMappingURL=team-sync-reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-sync-reporter.d.ts","sourceRoot":"","sources":["../../src/lib/team-sync-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAqBH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAYD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,iBAAiB,CAAC,CAqC5B;AAiDD;;GAEG;AACH,iBAAS,gBAAgB,CACvB,OAAO,EAAE,eAAe,EAAE,EAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,iBAAiB,CAgDnB;AAED;;GAEG;AACH,iBAAS,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,CAOxE;AAED;;GAEG;AACH,iBAAS,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAiB1D;AAMD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAuCzE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgDxE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAOvE;AAMD;;GAEG;AACH,iBAAS,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAa1C;AAMD,eAAO,MAAM,SAAS;;;;;CAKrB,CAAC"}