ghagga 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +90 -19
  2. package/dist/commands/hooks/index.d.ts +9 -0
  3. package/dist/commands/hooks/index.d.ts.map +1 -0
  4. package/dist/commands/hooks/index.js +16 -0
  5. package/dist/commands/hooks/index.js.map +1 -0
  6. package/dist/commands/hooks/install.d.ts +13 -0
  7. package/dist/commands/hooks/install.d.ts.map +1 -0
  8. package/dist/commands/hooks/install.js +64 -0
  9. package/dist/commands/hooks/install.js.map +1 -0
  10. package/dist/commands/hooks/install.test.d.ts +11 -0
  11. package/dist/commands/hooks/install.test.d.ts.map +1 -0
  12. package/dist/commands/hooks/install.test.js +157 -0
  13. package/dist/commands/hooks/install.test.js.map +1 -0
  14. package/dist/commands/hooks/status.d.ts +12 -0
  15. package/dist/commands/hooks/status.d.ts.map +1 -0
  16. package/dist/commands/hooks/status.js +38 -0
  17. package/dist/commands/hooks/status.js.map +1 -0
  18. package/dist/commands/hooks/status.test.d.ts +11 -0
  19. package/dist/commands/hooks/status.test.d.ts.map +1 -0
  20. package/dist/commands/hooks/status.test.js +123 -0
  21. package/dist/commands/hooks/status.test.js.map +1 -0
  22. package/dist/commands/hooks/uninstall.d.ts +12 -0
  23. package/dist/commands/hooks/uninstall.d.ts.map +1 -0
  24. package/dist/commands/hooks/uninstall.js +44 -0
  25. package/dist/commands/hooks/uninstall.js.map +1 -0
  26. package/dist/commands/hooks/uninstall.test.d.ts +10 -0
  27. package/dist/commands/hooks/uninstall.test.d.ts.map +1 -0
  28. package/dist/commands/hooks/uninstall.test.js +120 -0
  29. package/dist/commands/hooks/uninstall.test.js.map +1 -0
  30. package/dist/commands/login.d.ts.map +1 -1
  31. package/dist/commands/login.js +17 -14
  32. package/dist/commands/login.js.map +1 -1
  33. package/dist/commands/login.test.d.ts +9 -0
  34. package/dist/commands/login.test.d.ts.map +1 -0
  35. package/dist/commands/login.test.js +138 -0
  36. package/dist/commands/login.test.js.map +1 -0
  37. package/dist/commands/logout.d.ts.map +1 -1
  38. package/dist/commands/logout.js +4 -3
  39. package/dist/commands/logout.js.map +1 -1
  40. package/dist/commands/logout.test.d.ts +8 -0
  41. package/dist/commands/logout.test.d.ts.map +1 -0
  42. package/dist/commands/logout.test.js +61 -0
  43. package/dist/commands/logout.test.js.map +1 -0
  44. package/dist/commands/memory/clear.d.ts +11 -0
  45. package/dist/commands/memory/clear.d.ts.map +1 -0
  46. package/dist/commands/memory/clear.js +45 -0
  47. package/dist/commands/memory/clear.js.map +1 -0
  48. package/dist/commands/memory/clear.test.d.ts +11 -0
  49. package/dist/commands/memory/clear.test.d.ts.map +1 -0
  50. package/dist/commands/memory/clear.test.js +178 -0
  51. package/dist/commands/memory/clear.test.js.map +1 -0
  52. package/dist/commands/memory/delete.d.ts +11 -0
  53. package/dist/commands/memory/delete.d.ts.map +1 -0
  54. package/dist/commands/memory/delete.js +38 -0
  55. package/dist/commands/memory/delete.js.map +1 -0
  56. package/dist/commands/memory/delete.test.d.ts +11 -0
  57. package/dist/commands/memory/delete.test.d.ts.map +1 -0
  58. package/dist/commands/memory/delete.test.js +176 -0
  59. package/dist/commands/memory/delete.test.js.map +1 -0
  60. package/dist/commands/memory/index.d.ts +10 -0
  61. package/dist/commands/memory/index.d.ts.map +1 -0
  62. package/dist/commands/memory/index.js +23 -0
  63. package/dist/commands/memory/index.js.map +1 -0
  64. package/dist/commands/memory/list.d.ts +11 -0
  65. package/dist/commands/memory/list.d.ts.map +1 -0
  66. package/dist/commands/memory/list.js +47 -0
  67. package/dist/commands/memory/list.js.map +1 -0
  68. package/dist/commands/memory/list.test.d.ts +10 -0
  69. package/dist/commands/memory/list.test.d.ts.map +1 -0
  70. package/dist/commands/memory/list.test.js +135 -0
  71. package/dist/commands/memory/list.test.js.map +1 -0
  72. package/dist/commands/memory/search.d.ts +12 -0
  73. package/dist/commands/memory/search.d.ts.map +1 -0
  74. package/dist/commands/memory/search.js +44 -0
  75. package/dist/commands/memory/search.js.map +1 -0
  76. package/dist/commands/memory/search.test.d.ts +11 -0
  77. package/dist/commands/memory/search.test.d.ts.map +1 -0
  78. package/dist/commands/memory/search.test.js +122 -0
  79. package/dist/commands/memory/search.test.js.map +1 -0
  80. package/dist/commands/memory/show.d.ts +10 -0
  81. package/dist/commands/memory/show.d.ts.map +1 -0
  82. package/dist/commands/memory/show.js +51 -0
  83. package/dist/commands/memory/show.js.map +1 -0
  84. package/dist/commands/memory/show.test.d.ts +11 -0
  85. package/dist/commands/memory/show.test.d.ts.map +1 -0
  86. package/dist/commands/memory/show.test.js +156 -0
  87. package/dist/commands/memory/show.test.js.map +1 -0
  88. package/dist/commands/memory/stats.d.ts +11 -0
  89. package/dist/commands/memory/stats.d.ts.map +1 -0
  90. package/dist/commands/memory/stats.js +63 -0
  91. package/dist/commands/memory/stats.js.map +1 -0
  92. package/dist/commands/memory/stats.test.d.ts +10 -0
  93. package/dist/commands/memory/stats.test.d.ts.map +1 -0
  94. package/dist/commands/memory/stats.test.js +122 -0
  95. package/dist/commands/memory/stats.test.js.map +1 -0
  96. package/dist/commands/memory/utils.d.ts +44 -0
  97. package/dist/commands/memory/utils.d.ts.map +1 -0
  98. package/dist/commands/memory/utils.js +90 -0
  99. package/dist/commands/memory/utils.js.map +1 -0
  100. package/dist/commands/memory/utils.test.d.ts +10 -0
  101. package/dist/commands/memory/utils.test.d.ts.map +1 -0
  102. package/dist/commands/memory/utils.test.js +93 -0
  103. package/dist/commands/memory/utils.test.js.map +1 -0
  104. package/dist/commands/review-commit-msg.d.ts +28 -0
  105. package/dist/commands/review-commit-msg.d.ts.map +1 -0
  106. package/dist/commands/review-commit-msg.js +126 -0
  107. package/dist/commands/review-commit-msg.js.map +1 -0
  108. package/dist/commands/review-commit-msg.test.d.ts +11 -0
  109. package/dist/commands/review-commit-msg.test.d.ts.map +1 -0
  110. package/dist/commands/review-commit-msg.test.js +126 -0
  111. package/dist/commands/review-commit-msg.test.js.map +1 -0
  112. package/dist/commands/review.d.ts +7 -0
  113. package/dist/commands/review.d.ts.map +1 -1
  114. package/dist/commands/review.js +138 -103
  115. package/dist/commands/review.js.map +1 -1
  116. package/dist/commands/review.test.js +267 -1
  117. package/dist/commands/review.test.js.map +1 -1
  118. package/dist/commands/status.d.ts.map +1 -1
  119. package/dist/commands/status.js +12 -11
  120. package/dist/commands/status.js.map +1 -1
  121. package/dist/commands/status.test.d.ts +8 -0
  122. package/dist/commands/status.test.d.ts.map +1 -0
  123. package/dist/commands/status.test.js +106 -0
  124. package/dist/commands/status.test.js.map +1 -0
  125. package/dist/index.d.ts +5 -4
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +48 -16
  128. package/dist/index.js.map +1 -1
  129. package/dist/lib/config.d.ts +1 -0
  130. package/dist/lib/config.d.ts.map +1 -1
  131. package/dist/lib/config.js +1 -1
  132. package/dist/lib/config.js.map +1 -1
  133. package/dist/lib/config.test.d.ts +9 -0
  134. package/dist/lib/config.test.d.ts.map +1 -0
  135. package/dist/lib/config.test.js +181 -0
  136. package/dist/lib/config.test.js.map +1 -0
  137. package/dist/lib/git-hooks.d.ts +19 -0
  138. package/dist/lib/git-hooks.d.ts.map +1 -0
  139. package/dist/lib/git-hooks.js +129 -0
  140. package/dist/lib/git-hooks.js.map +1 -0
  141. package/dist/lib/git-hooks.test.d.ts +11 -0
  142. package/dist/lib/git-hooks.test.d.ts.map +1 -0
  143. package/dist/lib/git-hooks.test.js +178 -0
  144. package/dist/lib/git-hooks.test.js.map +1 -0
  145. package/dist/lib/git.d.ts +33 -0
  146. package/dist/lib/git.d.ts.map +1 -0
  147. package/dist/lib/git.js +85 -0
  148. package/dist/lib/git.js.map +1 -0
  149. package/dist/lib/git.test.d.ts +2 -0
  150. package/dist/lib/git.test.d.ts.map +1 -0
  151. package/dist/lib/git.test.js +65 -0
  152. package/dist/lib/git.test.js.map +1 -0
  153. package/dist/lib/hook-templates.d.ts +12 -0
  154. package/dist/lib/hook-templates.d.ts.map +1 -0
  155. package/dist/lib/hook-templates.js +52 -0
  156. package/dist/lib/hook-templates.js.map +1 -0
  157. package/dist/lib/hook-templates.test.d.ts +11 -0
  158. package/dist/lib/hook-templates.test.d.ts.map +1 -0
  159. package/dist/lib/hook-templates.test.js +76 -0
  160. package/dist/lib/hook-templates.test.js.map +1 -0
  161. package/dist/lib/hooks-types.d.ts +30 -0
  162. package/dist/lib/hooks-types.d.ts.map +1 -0
  163. package/dist/lib/hooks-types.js +9 -0
  164. package/dist/lib/hooks-types.js.map +1 -0
  165. package/dist/lib/oauth.test.d.ts +8 -0
  166. package/dist/lib/oauth.test.d.ts.map +1 -0
  167. package/dist/lib/oauth.test.js +212 -0
  168. package/dist/lib/oauth.test.js.map +1 -0
  169. package/dist/ui/__tests__/format.test.d.ts +7 -0
  170. package/dist/ui/__tests__/format.test.d.ts.map +1 -0
  171. package/dist/ui/__tests__/format.test.js +220 -0
  172. package/dist/ui/__tests__/format.test.js.map +1 -0
  173. package/dist/ui/__tests__/theme.test.d.ts +7 -0
  174. package/dist/ui/__tests__/theme.test.d.ts.map +1 -0
  175. package/dist/ui/__tests__/theme.test.js +79 -0
  176. package/dist/ui/__tests__/theme.test.js.map +1 -0
  177. package/dist/ui/__tests__/tui.test.d.ts +9 -0
  178. package/dist/ui/__tests__/tui.test.d.ts.map +1 -0
  179. package/dist/ui/__tests__/tui.test.js +222 -0
  180. package/dist/ui/__tests__/tui.test.js.map +1 -0
  181. package/dist/ui/format.d.ts +38 -0
  182. package/dist/ui/format.d.ts.map +1 -0
  183. package/dist/ui/format.js +136 -0
  184. package/dist/ui/format.js.map +1 -0
  185. package/dist/ui/theme.d.ts +26 -0
  186. package/dist/ui/theme.d.ts.map +1 -0
  187. package/dist/ui/theme.js +63 -0
  188. package/dist/ui/theme.js.map +1 -0
  189. package/dist/ui/tui.d.ts +44 -0
  190. package/dist/ui/tui.d.ts.map +1 -0
  191. package/dist/ui/tui.js +121 -0
  192. package/dist/ui/tui.js.map +1 -0
  193. package/package.json +4 -2
@@ -0,0 +1,212 @@
1
+ /**
2
+ * OAuth device flow tests.
3
+ *
4
+ * Tests requestDeviceCode(), pollForAccessToken(), and fetchGitHubUser()
5
+ * with mocked global fetch and fake timers for the polling loop.
6
+ */
7
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
8
+ import { GITHUB_CLIENT_ID, requestDeviceCode, pollForAccessToken, fetchGitHubUser, } from './oauth.js';
9
+ // ─── Mock global fetch ──────────────────────────────────────────
10
+ const mockFetch = vi.fn();
11
+ vi.stubGlobal('fetch', mockFetch);
12
+ // ─── Helpers ────────────────────────────────────────────────────
13
+ function jsonResponse(data, status = 200) {
14
+ return {
15
+ ok: status >= 200 && status < 300,
16
+ status,
17
+ json: () => Promise.resolve(data),
18
+ text: () => Promise.resolve(JSON.stringify(data)),
19
+ };
20
+ }
21
+ function errorResponse(status, body = 'error') {
22
+ return {
23
+ ok: false,
24
+ status,
25
+ json: () => Promise.resolve({}),
26
+ text: () => Promise.resolve(body),
27
+ };
28
+ }
29
+ // ─── Tests ──────────────────────────────────────────────────────
30
+ describe('GITHUB_CLIENT_ID', () => {
31
+ it('is exported and equals the expected public client ID', () => {
32
+ expect(GITHUB_CLIENT_ID).toBe('Ov23liyYpSgDqOLUFa5k');
33
+ });
34
+ });
35
+ describe('requestDeviceCode', () => {
36
+ beforeEach(() => {
37
+ vi.clearAllMocks();
38
+ });
39
+ it('sends POST to github.com/login/device/code with client_id', async () => {
40
+ mockFetch.mockResolvedValueOnce(jsonResponse({
41
+ device_code: 'dc_123',
42
+ user_code: 'ABCD-1234',
43
+ verification_uri: 'https://github.com/login/device',
44
+ expires_in: 900,
45
+ interval: 5,
46
+ }));
47
+ const result = await requestDeviceCode();
48
+ expect(mockFetch).toHaveBeenCalledWith('https://github.com/login/device/code', expect.objectContaining({
49
+ method: 'POST',
50
+ headers: expect.objectContaining({
51
+ Accept: 'application/json',
52
+ 'Content-Type': 'application/json',
53
+ }),
54
+ }));
55
+ // Verify client_id is in the body
56
+ const callArgs = mockFetch.mock.calls[0];
57
+ const body = JSON.parse(callArgs[1].body);
58
+ expect(body.client_id).toBe(GITHUB_CLIENT_ID);
59
+ expect(result.device_code).toBe('dc_123');
60
+ expect(result.user_code).toBe('ABCD-1234');
61
+ expect(result.verification_uri).toBe('https://github.com/login/device');
62
+ });
63
+ it('throws on non-ok response with status and body', async () => {
64
+ mockFetch.mockResolvedValueOnce(errorResponse(422, 'validation failed'));
65
+ await expect(requestDeviceCode()).rejects.toThrow('Failed to request device code: 422 validation failed');
66
+ });
67
+ });
68
+ describe('pollForAccessToken', () => {
69
+ beforeEach(() => {
70
+ vi.clearAllMocks();
71
+ vi.useFakeTimers();
72
+ });
73
+ afterEach(() => {
74
+ vi.useRealTimers();
75
+ });
76
+ // Helper: run pollForAccessToken and advance timers
77
+ async function runPoll(deviceCode = 'dc_123', interval = 1, expiresIn = 60) {
78
+ const promise = pollForAccessToken(deviceCode, interval, expiresIn);
79
+ // Advance through the initial sleep + poll cycles
80
+ // We need to flush several timer rounds
81
+ for (let i = 0; i < 10; i++) {
82
+ await vi.advanceTimersByTimeAsync(interval * 1000 + 100);
83
+ }
84
+ return promise;
85
+ }
86
+ it('returns AccessTokenResponse on success after first poll', async () => {
87
+ mockFetch.mockResolvedValueOnce(jsonResponse({
88
+ access_token: 'gho_abc123',
89
+ token_type: 'bearer',
90
+ scope: '',
91
+ }));
92
+ const promise = pollForAccessToken('dc_123', 1, 60);
93
+ await vi.advanceTimersByTimeAsync(1100);
94
+ const result = await promise;
95
+ expect(result.access_token).toBe('gho_abc123');
96
+ expect(result.token_type).toBe('bearer');
97
+ });
98
+ it('keeps polling on authorization_pending, returns on success', async () => {
99
+ // First call: pending
100
+ mockFetch.mockResolvedValueOnce(jsonResponse({ error: 'authorization_pending' }));
101
+ // Second call: success
102
+ mockFetch.mockResolvedValueOnce(jsonResponse({
103
+ access_token: 'gho_xyz',
104
+ token_type: 'bearer',
105
+ scope: '',
106
+ }));
107
+ const promise = pollForAccessToken('dc_123', 1, 60);
108
+ // First poll (sleep 1s + fetch)
109
+ await vi.advanceTimersByTimeAsync(1100);
110
+ // Second poll (sleep 1s + fetch)
111
+ await vi.advanceTimersByTimeAsync(1100);
112
+ const result = await promise;
113
+ expect(mockFetch).toHaveBeenCalledTimes(2);
114
+ expect(result.access_token).toBe('gho_xyz');
115
+ });
116
+ it('increases interval by 5 on slow_down and continues polling', async () => {
117
+ // First call: slow_down
118
+ mockFetch.mockResolvedValueOnce(jsonResponse({ error: 'slow_down', interval: 5 }));
119
+ // Second call: success (after longer wait)
120
+ mockFetch.mockResolvedValueOnce(jsonResponse({
121
+ access_token: 'gho_slow',
122
+ token_type: 'bearer',
123
+ scope: '',
124
+ }));
125
+ const promise = pollForAccessToken('dc_123', 1, 120);
126
+ // First poll: 1s sleep
127
+ await vi.advanceTimersByTimeAsync(1100);
128
+ // After slow_down: new interval is (5 + 5) = 10s
129
+ await vi.advanceTimersByTimeAsync(10100);
130
+ const result = await promise;
131
+ expect(result.access_token).toBe('gho_slow');
132
+ });
133
+ it('throws "Device code expired" on expired_token error', async () => {
134
+ mockFetch.mockResolvedValueOnce(jsonResponse({ error: 'expired_token' }));
135
+ const promise = pollForAccessToken('dc_123', 1, 60);
136
+ // Attach rejection handler immediately to prevent unhandled rejection
137
+ const rejection = promise.catch((e) => e);
138
+ await vi.advanceTimersByTimeAsync(1100);
139
+ const error = await rejection;
140
+ expect(error).toBeInstanceOf(Error);
141
+ expect(error.message).toContain('Device code expired');
142
+ });
143
+ it('throws "Authorization was denied" on access_denied error', async () => {
144
+ mockFetch.mockResolvedValueOnce(jsonResponse({ error: 'access_denied' }));
145
+ const promise = pollForAccessToken('dc_123', 1, 60);
146
+ const rejection = promise.catch((e) => e);
147
+ await vi.advanceTimersByTimeAsync(1100);
148
+ const error = await rejection;
149
+ expect(error).toBeInstanceOf(Error);
150
+ expect(error.message).toContain('Authorization was denied');
151
+ });
152
+ it('throws "OAuth error" with description on unknown error', async () => {
153
+ mockFetch.mockResolvedValueOnce(jsonResponse({
154
+ error: 'server_error',
155
+ error_description: 'Something went wrong',
156
+ }));
157
+ const promise = pollForAccessToken('dc_123', 1, 60);
158
+ const rejection = promise.catch((e) => e);
159
+ await vi.advanceTimersByTimeAsync(1100);
160
+ const error = await rejection;
161
+ expect(error).toBeInstanceOf(Error);
162
+ expect(error.message).toContain('OAuth error: server_error — Something went wrong');
163
+ });
164
+ it('throws "OAuth error" without description when none provided', async () => {
165
+ mockFetch.mockResolvedValueOnce(jsonResponse({ error: 'unknown_error' }));
166
+ const promise = pollForAccessToken('dc_123', 1, 60);
167
+ const rejection = promise.catch((e) => e);
168
+ await vi.advanceTimersByTimeAsync(1100);
169
+ const error = await rejection;
170
+ expect(error).toBeInstanceOf(Error);
171
+ expect(error.message).toContain('OAuth error: unknown_error');
172
+ });
173
+ it('throws timeout error when deadline is exceeded', async () => {
174
+ // Keep returning authorization_pending until deadline passes
175
+ mockFetch.mockImplementation(() => Promise.resolve(jsonResponse({ error: 'authorization_pending' })));
176
+ // Use very short expiry so we can trigger timeout
177
+ const promise = pollForAccessToken('dc_123', 1, 2);
178
+ const rejection = promise.catch((e) => e);
179
+ // Advance past the 2-second deadline
180
+ await vi.advanceTimersByTimeAsync(1100); // first poll
181
+ await vi.advanceTimersByTimeAsync(1100); // second poll — now past deadline
182
+ const error = await rejection;
183
+ expect(error).toBeInstanceOf(Error);
184
+ expect(error.message).toContain('Device code expired (timeout)');
185
+ });
186
+ });
187
+ describe('fetchGitHubUser', () => {
188
+ beforeEach(() => {
189
+ vi.clearAllMocks();
190
+ });
191
+ it('sends GET to api.github.com/user with Bearer token', async () => {
192
+ mockFetch.mockResolvedValueOnce(jsonResponse({
193
+ login: 'testuser',
194
+ id: 12345,
195
+ avatar_url: 'https://github.com/avatar.png',
196
+ }));
197
+ const user = await fetchGitHubUser('gho_token123');
198
+ expect(mockFetch).toHaveBeenCalledWith('https://api.github.com/user', expect.objectContaining({
199
+ headers: expect.objectContaining({
200
+ Authorization: 'Bearer gho_token123',
201
+ }),
202
+ }));
203
+ expect(user.login).toBe('testuser');
204
+ expect(user.id).toBe(12345);
205
+ expect(user.avatar_url).toBe('https://github.com/avatar.png');
206
+ });
207
+ it('throws on non-ok response with status', async () => {
208
+ mockFetch.mockResolvedValueOnce(errorResponse(401));
209
+ await expect(fetchGitHubUser('bad_token')).rejects.toThrow('Failed to fetch GitHub user: 401');
210
+ });
211
+ });
212
+ //# sourceMappingURL=oauth.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.test.js","sourceRoot":"","sources":["../../src/lib/oauth.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Unit tests for pure formatting functions in format.ts.
3
+ *
4
+ * All functions are pure (string in, string out) — no mocking needed.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=format.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.test.d.ts","sourceRoot":"","sources":["../../../src/ui/__tests__/format.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Unit tests for pure formatting functions in format.ts.
3
+ *
4
+ * All functions are pure (string in, string out) — no mocking needed.
5
+ */
6
+ import { describe, it, expect } from 'vitest';
7
+ import { formatTable, formatSize, formatId, truncate, formatKeyValue, formatMarkdownResult, } from '../format.js';
8
+ // ─── Helpers ────────────────────────────────────────────────────
9
+ function makeResult(overrides = {}) {
10
+ return {
11
+ status: 'PASSED',
12
+ summary: 'All good.',
13
+ findings: [],
14
+ staticAnalysis: {
15
+ semgrep: { status: 'skipped', findings: [], executionTimeMs: 0 },
16
+ trivy: { status: 'skipped', findings: [], executionTimeMs: 0 },
17
+ cpd: { status: 'skipped', findings: [], executionTimeMs: 0 },
18
+ },
19
+ memoryContext: null,
20
+ metadata: {
21
+ mode: 'simple',
22
+ provider: 'anthropic',
23
+ model: 'claude-sonnet-4-20250514',
24
+ tokensUsed: 1000,
25
+ executionTimeMs: 2000,
26
+ toolsRun: [],
27
+ toolsSkipped: [],
28
+ },
29
+ ...overrides,
30
+ };
31
+ }
32
+ // ─── formatTable ────────────────────────────────────────────────
33
+ describe('formatTable', () => {
34
+ it('should format a table with headers, separator, and data rows', () => {
35
+ const result = formatTable(['Name', 'Age'], [['Alice', '30'], ['Bob', '25']], [10, 5]);
36
+ const lines = result.split('\n');
37
+ expect(lines).toHaveLength(4); // header + separator + 2 rows
38
+ expect(lines[0]).toBe('Name Age ');
39
+ expect(lines[1]).toBe('────────── ─────');
40
+ expect(lines[2]).toBe('Alice 30 ');
41
+ expect(lines[3]).toBe('Bob 25 ');
42
+ });
43
+ it('should format a table with a single row', () => {
44
+ const result = formatTable(['ID'], [['42']], [4]);
45
+ const lines = result.split('\n');
46
+ expect(lines).toHaveLength(3); // header + separator + 1 row
47
+ expect(lines[2]).toBe('42 ');
48
+ });
49
+ it('should format a table with no data rows', () => {
50
+ const result = formatTable(['Col'], [], [6]);
51
+ const lines = result.split('\n');
52
+ expect(lines).toHaveLength(2); // header + separator only
53
+ expect(lines[0]).toBe('Col ');
54
+ expect(lines[1]).toBe('──────');
55
+ });
56
+ it('should pad columns to specified widths', () => {
57
+ const result = formatTable(['A', 'B'], [['x', 'y']], [8, 8]);
58
+ const lines = result.split('\n');
59
+ // Each column is padded to 8 chars
60
+ expect(lines[0]).toBe('A B ');
61
+ expect(lines[2]).toBe('x y ');
62
+ });
63
+ });
64
+ // ─── formatSize ─────────────────────────────────────────────────
65
+ describe('formatSize', () => {
66
+ it('should format bytes below 1024 as "N bytes"', () => {
67
+ expect(formatSize(0)).toBe('0 bytes');
68
+ expect(formatSize(512)).toBe('512 bytes');
69
+ expect(formatSize(1023)).toBe('1023 bytes');
70
+ });
71
+ it('should format bytes in KB range', () => {
72
+ expect(formatSize(1024)).toBe('1.0 KB');
73
+ expect(formatSize(1536)).toBe('1.5 KB');
74
+ expect(formatSize(10240)).toBe('10.0 KB');
75
+ });
76
+ it('should format bytes in MB range', () => {
77
+ expect(formatSize(1048576)).toBe('1.0 MB');
78
+ expect(formatSize(5242880)).toBe('5.0 MB');
79
+ });
80
+ it('should handle boundary value 1024 as KB', () => {
81
+ expect(formatSize(1024)).toBe('1.0 KB');
82
+ });
83
+ it('should handle boundary value 1048576 as MB', () => {
84
+ expect(formatSize(1048576)).toBe('1.0 MB');
85
+ });
86
+ });
87
+ // ─── formatId ───────────────────────────────────────────────────
88
+ describe('formatId', () => {
89
+ it('should zero-pad an ID to 8 characters', () => {
90
+ expect(formatId(42)).toBe('00000042');
91
+ });
92
+ it('should handle a large ID without extra padding', () => {
93
+ expect(formatId(12345678)).toBe('12345678');
94
+ });
95
+ it('should handle ID of 0', () => {
96
+ expect(formatId(0)).toBe('00000000');
97
+ });
98
+ });
99
+ // ─── truncate ───────────────────────────────────────────────────
100
+ describe('truncate', () => {
101
+ it('should return the string unchanged when shorter than maxLen', () => {
102
+ expect(truncate('hello', 10)).toBe('hello');
103
+ });
104
+ it('should return the string unchanged when exactly at maxLen', () => {
105
+ expect(truncate('hello', 5)).toBe('hello');
106
+ });
107
+ it('should truncate and append "..." when over maxLen', () => {
108
+ expect(truncate('hello world', 8)).toBe('hello...');
109
+ });
110
+ it('should handle an empty string', () => {
111
+ expect(truncate('', 5)).toBe('');
112
+ });
113
+ });
114
+ // ─── formatKeyValue ─────────────────────────────────────────────
115
+ describe('formatKeyValue', () => {
116
+ it('should format with default indent of 3', () => {
117
+ const result = formatKeyValue('Provider', 'github');
118
+ // 3 spaces indent + 'Provider' padEnd(12) + 2 spaces + 'github'
119
+ expect(result).toBe(' Provider github');
120
+ });
121
+ it('should format with a custom indent', () => {
122
+ const result = formatKeyValue('Key', 'value', 5);
123
+ expect(result).toBe(' Key value');
124
+ });
125
+ it('should pad labels to 12 characters', () => {
126
+ const result = formatKeyValue('A', 'B');
127
+ // 3 spaces indent + 'A' padded to 12 + 2 spaces + 'B'
128
+ expect(result).toBe(' A B');
129
+ });
130
+ });
131
+ // ─── formatMarkdownResult ───────────────────────────────────────
132
+ describe('formatMarkdownResult', () => {
133
+ it('should include the header with status emoji', () => {
134
+ const result = formatMarkdownResult(makeResult());
135
+ expect(result).toContain('GHAGGA Code Review');
136
+ expect(result).toContain('PASSED');
137
+ });
138
+ it('should include mode, model, time, and tokens in metadata line', () => {
139
+ const result = formatMarkdownResult(makeResult());
140
+ expect(result).toContain('Mode: simple');
141
+ expect(result).toContain('Model: claude-sonnet-4-20250514');
142
+ expect(result).toContain('Time: 2.0s');
143
+ expect(result).toContain('Tokens: 1000');
144
+ });
145
+ it('should show summary section', () => {
146
+ const result = formatMarkdownResult(makeResult({ summary: 'Looking great!' }));
147
+ expect(result).toContain('## Summary');
148
+ expect(result).toContain('Looking great!');
149
+ });
150
+ it('should show "Nice work!" when there are no findings', () => {
151
+ const result = formatMarkdownResult(makeResult({ findings: [] }));
152
+ expect(result).toContain('No findings. Nice work!');
153
+ });
154
+ it('should group findings by source in render order', () => {
155
+ const result = formatMarkdownResult(makeResult({
156
+ findings: [
157
+ { severity: 'high', category: 'security', file: 'a.ts', message: 'AI issue', source: 'ai' },
158
+ { severity: 'medium', category: 'style', file: 'b.ts', message: 'Semgrep issue', source: 'semgrep' },
159
+ ],
160
+ }));
161
+ const semgrepIdx = result.indexOf('Semgrep');
162
+ const aiIdx = result.indexOf('AI Review');
163
+ // Semgrep should appear before AI
164
+ expect(semgrepIdx).toBeLessThan(aiIdx);
165
+ });
166
+ it('should render finding with line number as file:line', () => {
167
+ const result = formatMarkdownResult(makeResult({
168
+ findings: [
169
+ { severity: 'high', category: 'bug', file: 'main.ts', line: 42, message: 'Bug here', source: 'ai' },
170
+ ],
171
+ }));
172
+ expect(result).toContain('main.ts:42');
173
+ });
174
+ it('should render finding without line number as just file', () => {
175
+ const result = formatMarkdownResult(makeResult({
176
+ findings: [
177
+ { severity: 'low', category: 'style', file: 'index.ts', message: 'Minor', source: 'ai' },
178
+ ],
179
+ }));
180
+ expect(result).toContain('index.ts');
181
+ expect(result).not.toContain('index.ts:');
182
+ });
183
+ it('should render suggestion when present', () => {
184
+ const result = formatMarkdownResult(makeResult({
185
+ findings: [
186
+ { severity: 'medium', category: 'perf', file: 'x.ts', message: 'Slow', suggestion: 'Use cache', source: 'ai' },
187
+ ],
188
+ }));
189
+ expect(result).toContain('Use cache');
190
+ });
191
+ it('should include static analysis summary when tools ran', () => {
192
+ const result = formatMarkdownResult(makeResult({
193
+ metadata: {
194
+ mode: 'simple',
195
+ provider: 'anthropic',
196
+ model: 'claude-sonnet-4-20250514',
197
+ tokensUsed: 1000,
198
+ executionTimeMs: 2000,
199
+ toolsRun: ['semgrep', 'trivy'],
200
+ toolsSkipped: ['cpd'],
201
+ },
202
+ }));
203
+ expect(result).toContain('## Static Analysis');
204
+ expect(result).toContain('Tools run: semgrep, trivy');
205
+ expect(result).toContain('Tools skipped: cpd');
206
+ });
207
+ it('should omit static analysis section when no tools ran or skipped', () => {
208
+ const result = formatMarkdownResult(makeResult());
209
+ expect(result).not.toContain('## Static Analysis');
210
+ });
211
+ it('should include the footer', () => {
212
+ const result = formatMarkdownResult(makeResult());
213
+ expect(result).toContain('Powered by GHAGGA');
214
+ });
215
+ it('should render FAILED status emoji', () => {
216
+ const result = formatMarkdownResult(makeResult({ status: 'FAILED' }));
217
+ expect(result).toContain('FAILED');
218
+ });
219
+ });
220
+ //# sourceMappingURL=format.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.test.js","sourceRoot":"","sources":["../../../src/ui/__tests__/format.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Unit tests for theme constants and resolveStepIcon function.
3
+ *
4
+ * Pure constants — no mocking needed.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=theme.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.test.d.ts","sourceRoot":"","sources":["../../../src/ui/__tests__/theme.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Unit tests for theme constants and resolveStepIcon function.
3
+ *
4
+ * Pure constants — no mocking needed.
5
+ */
6
+ import { describe, it, expect } from 'vitest';
7
+ import { BRAND, STATUS_EMOJI, SEVERITY_EMOJI, STEP_ICON, SOURCE_LABELS, resolveStepIcon, } from '../theme.js';
8
+ // ─── Constants ──────────────────────────────────────────────────
9
+ describe('BRAND', () => {
10
+ it('should have name and tagline strings', () => {
11
+ expect(BRAND).toHaveProperty('name');
12
+ expect(BRAND).toHaveProperty('tagline');
13
+ expect(typeof BRAND.name).toBe('string');
14
+ expect(typeof BRAND.tagline).toBe('string');
15
+ expect(BRAND.name).toBe('GHAGGA');
16
+ });
17
+ });
18
+ describe('STATUS_EMOJI', () => {
19
+ it('should have all 4 ReviewStatus keys', () => {
20
+ expect(STATUS_EMOJI).toHaveProperty('PASSED');
21
+ expect(STATUS_EMOJI).toHaveProperty('FAILED');
22
+ expect(STATUS_EMOJI).toHaveProperty('NEEDS_HUMAN_REVIEW');
23
+ expect(STATUS_EMOJI).toHaveProperty('SKIPPED');
24
+ expect(Object.keys(STATUS_EMOJI)).toHaveLength(4);
25
+ });
26
+ });
27
+ describe('SEVERITY_EMOJI', () => {
28
+ it('should have all 5 FindingSeverity keys', () => {
29
+ expect(SEVERITY_EMOJI).toHaveProperty('critical');
30
+ expect(SEVERITY_EMOJI).toHaveProperty('high');
31
+ expect(SEVERITY_EMOJI).toHaveProperty('medium');
32
+ expect(SEVERITY_EMOJI).toHaveProperty('low');
33
+ expect(SEVERITY_EMOJI).toHaveProperty('info');
34
+ expect(Object.keys(SEVERITY_EMOJI)).toHaveLength(5);
35
+ });
36
+ });
37
+ describe('STEP_ICON', () => {
38
+ it('should have all 13 known step keys', () => {
39
+ const expectedKeys = [
40
+ 'validate', 'parse-diff', 'detect-stacks', 'token-budget',
41
+ 'static-analysis', 'static-results', 'agent-start', 'simple-call',
42
+ 'simple-done', 'workflow-start', 'workflow-synthesis',
43
+ 'consensus-start', 'consensus-voting',
44
+ ];
45
+ for (const key of expectedKeys) {
46
+ expect(STEP_ICON).toHaveProperty(key);
47
+ }
48
+ expect(Object.keys(STEP_ICON)).toHaveLength(13);
49
+ });
50
+ });
51
+ describe('SOURCE_LABELS', () => {
52
+ it('should have all 4 source keys', () => {
53
+ expect(SOURCE_LABELS).toHaveProperty('semgrep');
54
+ expect(SOURCE_LABELS).toHaveProperty('trivy');
55
+ expect(SOURCE_LABELS).toHaveProperty('cpd');
56
+ expect(SOURCE_LABELS).toHaveProperty('ai');
57
+ expect(Object.keys(SOURCE_LABELS)).toHaveLength(4);
58
+ });
59
+ });
60
+ // ─── resolveStepIcon ────────────────────────────────────────────
61
+ describe('resolveStepIcon', () => {
62
+ it('should return the STEP_ICON value for a known step', () => {
63
+ expect(resolveStepIcon('validate')).toBe(STEP_ICON['validate']);
64
+ expect(resolveStepIcon('agent-start')).toBe(STEP_ICON['agent-start']);
65
+ });
66
+ it('should return specialist icon for "specialist-*" steps', () => {
67
+ expect(resolveStepIcon('specialist-security')).toBe('👤');
68
+ expect(resolveStepIcon('specialist-performance')).toBe('👤');
69
+ });
70
+ it('should return vote icon for "vote-*" steps', () => {
71
+ expect(resolveStepIcon('vote-1')).toBe('🗳️');
72
+ expect(resolveStepIcon('vote-final')).toBe('🗳️');
73
+ });
74
+ it('should return fallback icon for unknown steps', () => {
75
+ expect(resolveStepIcon('unknown-step')).toBe('▸');
76
+ expect(resolveStepIcon('')).toBe('▸');
77
+ });
78
+ });
79
+ //# sourceMappingURL=theme.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.test.js","sourceRoot":"","sources":["../../../src/ui/__tests__/theme.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * TUI facade tests.
3
+ *
4
+ * Tests the init/isPlain mode resolution, intro/outro, log.* methods,
5
+ * and spinner() in both plain and styled modes.
6
+ * Mocks @clack/prompts and console.log/error.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=tui.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.test.d.ts","sourceRoot":"","sources":["../../../src/ui/__tests__/tui.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}