cadr-cli 2.0.1 → 2.0.2

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 (52) hide show
  1. package/dist/analysis/analysis.orchestrator.test.d.ts +2 -0
  2. package/dist/analysis/analysis.orchestrator.test.d.ts.map +1 -0
  3. package/dist/analysis/analysis.orchestrator.test.js +177 -0
  4. package/dist/analysis/analysis.orchestrator.test.js.map +1 -0
  5. package/dist/analysis/strategies/git-strategy.test.d.ts +2 -0
  6. package/dist/analysis/strategies/git-strategy.test.d.ts.map +1 -0
  7. package/dist/analysis/strategies/git-strategy.test.js +147 -0
  8. package/dist/analysis/strategies/git-strategy.test.js.map +1 -0
  9. package/dist/commands/analyze.test.d.ts +2 -0
  10. package/dist/commands/analyze.test.d.ts.map +1 -0
  11. package/dist/commands/analyze.test.js +70 -0
  12. package/dist/commands/analyze.test.js.map +1 -0
  13. package/dist/commands/init.test.js +128 -2
  14. package/dist/commands/init.test.js.map +1 -1
  15. package/dist/config.test.js +167 -0
  16. package/dist/config.test.js.map +1 -1
  17. package/dist/git/git.errors.test.d.ts +2 -0
  18. package/dist/git/git.errors.test.d.ts.map +1 -0
  19. package/dist/git/git.errors.test.js +34 -0
  20. package/dist/git/git.errors.test.js.map +1 -0
  21. package/dist/git/git.operations.test.d.ts +2 -0
  22. package/dist/git/git.operations.test.d.ts.map +1 -0
  23. package/dist/git/git.operations.test.js +164 -0
  24. package/dist/git/git.operations.test.js.map +1 -0
  25. package/dist/llm/llm.test.d.ts +2 -0
  26. package/dist/llm/llm.test.d.ts.map +1 -0
  27. package/dist/llm/llm.test.js +224 -0
  28. package/dist/llm/llm.test.js.map +1 -0
  29. package/dist/llm/response-parser.test.d.ts +2 -0
  30. package/dist/llm/response-parser.test.d.ts.map +1 -0
  31. package/dist/llm/response-parser.test.js +134 -0
  32. package/dist/llm/response-parser.test.js.map +1 -0
  33. package/dist/presenters/console-presenter.test.d.ts +2 -0
  34. package/dist/presenters/console-presenter.test.d.ts.map +1 -0
  35. package/dist/presenters/console-presenter.test.js +227 -0
  36. package/dist/presenters/console-presenter.test.js.map +1 -0
  37. package/dist/version.test.d.ts +1 -2
  38. package/dist/version.test.d.ts.map +1 -1
  39. package/dist/version.test.js +29 -16
  40. package/dist/version.test.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/analysis/analysis.orchestrator.test.ts +237 -0
  43. package/src/analysis/strategies/git-strategy.test.ts +210 -0
  44. package/src/commands/analyze.test.ts +91 -0
  45. package/src/commands/init.test.ts +200 -5
  46. package/src/config.test.ts +232 -2
  47. package/src/git/git.errors.test.ts +43 -0
  48. package/src/git/git.operations.test.ts +222 -0
  49. package/src/llm/llm.test.ts +315 -0
  50. package/src/llm/response-parser.test.ts +170 -0
  51. package/src/presenters/console-presenter.test.ts +259 -0
  52. package/src/version.test.ts +30 -16
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ jest.mock('../providers', () => ({
4
+ getProvider: jest.fn(),
5
+ }));
6
+ jest.mock('../logger', () => ({
7
+ loggerInstance: {
8
+ info: jest.fn(),
9
+ warn: jest.fn(),
10
+ error: jest.fn(),
11
+ debug: jest.fn(),
12
+ },
13
+ }));
14
+ const providers_1 = require("../providers");
15
+ const logger_1 = require("../logger");
16
+ const llm_1 = require("./llm");
17
+ const mockedGetProvider = providers_1.getProvider;
18
+ const mockedLogger = logger_1.loggerInstance;
19
+ const config = {
20
+ provider: 'openai',
21
+ analysis_model: 'gpt-4',
22
+ api_key_env: 'TEST_API_KEY',
23
+ timeout_seconds: 15,
24
+ };
25
+ const baseAnalysisRequest = {
26
+ file_paths: ['src/index.ts'],
27
+ diff_content: 'diff --git a/src/index.ts',
28
+ repository_context: 'test repo',
29
+ analysis_prompt: 'Analyze these changes',
30
+ };
31
+ const baseGenerationRequest = {
32
+ file_paths: ['src/index.ts'],
33
+ diff_content: 'diff --git a/src/index.ts',
34
+ reason: 'significant architectural change',
35
+ generation_prompt: 'Generate ADR for these changes',
36
+ };
37
+ function createMockProvider(analyzeFn) {
38
+ return {
39
+ name: 'openai',
40
+ analyze: analyzeFn,
41
+ };
42
+ }
43
+ beforeEach(() => {
44
+ jest.clearAllMocks();
45
+ process.env.TEST_API_KEY = 'test-key';
46
+ });
47
+ afterEach(() => {
48
+ delete process.env.TEST_API_KEY;
49
+ jest.restoreAllMocks();
50
+ });
51
+ describe('analyzeChanges', () => {
52
+ it('should return error when API key is not set', async () => {
53
+ delete process.env.TEST_API_KEY;
54
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
55
+ expect(response.result).toBeNull();
56
+ expect(response.error).toContain('TEST_API_KEY');
57
+ });
58
+ it('should return successful result when provider returns clean JSON', async () => {
59
+ const mockAnalyze = jest.fn().mockResolvedValue(JSON.stringify({ is_significant: true, reason: 'big' }));
60
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
61
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
62
+ expect(response.result).not.toBeNull();
63
+ expect(response.result.is_significant).toBe(true);
64
+ expect(response.result.reason).toBe('big');
65
+ expect(response.result.timestamp).toBeDefined();
66
+ expect(response.error).toBeUndefined();
67
+ });
68
+ it('should parse JSON wrapped in a code block', async () => {
69
+ const mockAnalyze = jest.fn().mockResolvedValue('```json\n{"is_significant": true, "reason": "refactored"}\n```');
70
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
71
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
72
+ expect(response.result).not.toBeNull();
73
+ expect(response.result.is_significant).toBe(true);
74
+ expect(response.result.reason).toBe('refactored');
75
+ });
76
+ it('should return error when provider returns undefined', async () => {
77
+ const mockAnalyze = jest.fn().mockResolvedValue(undefined);
78
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
79
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
80
+ expect(response.result).toBeNull();
81
+ expect(response.error).toBe('No response content from LLM');
82
+ });
83
+ it('should return error when provider returns invalid JSON', async () => {
84
+ const mockAnalyze = jest.fn().mockResolvedValue('not valid json at all');
85
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
86
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
87
+ expect(response.result).toBeNull();
88
+ expect(response.error).toContain('Failed to parse LLM response as JSON');
89
+ });
90
+ it('should return error when is_significant is not a boolean', async () => {
91
+ const mockAnalyze = jest.fn().mockResolvedValue(JSON.stringify({ is_significant: 'yes', reason: 'something' }));
92
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
93
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
94
+ expect(response.result).toBeNull();
95
+ expect(response.error).toContain('Invalid response format');
96
+ });
97
+ it('should return error when is_significant is true but reason is empty', async () => {
98
+ const mockAnalyze = jest.fn().mockResolvedValue(JSON.stringify({ is_significant: true, reason: '' }));
99
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
100
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
101
+ expect(response.result).toBeNull();
102
+ expect(response.error).toContain('no reason');
103
+ });
104
+ it('should include confidence when value is within valid range', async () => {
105
+ const mockAnalyze = jest.fn().mockResolvedValue(JSON.stringify({ is_significant: true, reason: 'big change', confidence: 0.85 }));
106
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
107
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
108
+ expect(response.result).not.toBeNull();
109
+ expect(response.result.confidence).toBe(0.85);
110
+ });
111
+ it('should not include confidence when value is out of range', async () => {
112
+ const mockAnalyze = jest.fn().mockResolvedValue(JSON.stringify({ is_significant: true, reason: 'big change', confidence: 1.5 }));
113
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
114
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
115
+ expect(response.result).not.toBeNull();
116
+ expect(response.result.confidence).toBeUndefined();
117
+ });
118
+ it('should log warning when estimated tokens exceed 7000', async () => {
119
+ const longPrompt = 'x'.repeat(28004);
120
+ const mockAnalyze = jest.fn().mockResolvedValue(JSON.stringify({ is_significant: false, reason: 'minor' }));
121
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
122
+ const request = { ...baseAnalysisRequest, analysis_prompt: longPrompt };
123
+ await (0, llm_1.analyzeChanges)(config, request);
124
+ expect(mockedLogger.warn).toHaveBeenCalledWith('High token count detected', expect.objectContaining({ estimated_tokens: expect.any(Number) }));
125
+ });
126
+ it('should return auth error when provider throws with status 401', async () => {
127
+ const mockAnalyze = jest.fn().mockRejectedValue({ status: 401, message: 'Unauthorized' });
128
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
129
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
130
+ expect(response.result).toBeNull();
131
+ expect(response.error).toContain('Invalid API key');
132
+ });
133
+ it('should return diff-too-large error when provider throws status 400 with context length message', async () => {
134
+ const mockAnalyze = jest.fn().mockRejectedValue({
135
+ status: 400,
136
+ message: 'maximum context length exceeded with 50000 tokens',
137
+ });
138
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
139
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
140
+ expect(response.result).toBeNull();
141
+ expect(response.error).toContain('Diff too large');
142
+ });
143
+ it('should return rate limit error when provider throws with status 429', async () => {
144
+ const mockAnalyze = jest.fn().mockRejectedValue({ status: 429, message: 'Too many requests' });
145
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
146
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
147
+ expect(response.result).toBeNull();
148
+ expect(response.error).toContain('Rate limit exceeded');
149
+ });
150
+ it('should return timeout error when provider throws with code ETIMEDOUT', async () => {
151
+ const mockAnalyze = jest.fn().mockRejectedValue({ code: 'ETIMEDOUT', message: 'connection timed out' });
152
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
153
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
154
+ expect(response.result).toBeNull();
155
+ expect(response.error).toContain('timeout');
156
+ expect(response.error).toContain('15s');
157
+ });
158
+ it('should return timeout error when provider throws with timeout in message', async () => {
159
+ const mockAnalyze = jest.fn().mockRejectedValue({ message: 'Request timeout after 15000ms' });
160
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
161
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
162
+ expect(response.result).toBeNull();
163
+ expect(response.error).toContain('timeout');
164
+ });
165
+ it('should return network error when provider throws with code ENOTFOUND', async () => {
166
+ const mockAnalyze = jest.fn().mockRejectedValue({ code: 'ENOTFOUND', message: 'getaddrinfo ENOTFOUND' });
167
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
168
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
169
+ expect(response.result).toBeNull();
170
+ expect(response.error).toContain('Network error');
171
+ });
172
+ it('should return generic API error for unknown errors', async () => {
173
+ const mockAnalyze = jest.fn().mockRejectedValue({ message: 'Something unexpected' });
174
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
175
+ const response = await (0, llm_1.analyzeChanges)(config, baseAnalysisRequest);
176
+ expect(response.result).toBeNull();
177
+ expect(response.error).toBe('API error: Something unexpected');
178
+ });
179
+ });
180
+ describe('generateADRContent', () => {
181
+ it('should return error when API key is not set', async () => {
182
+ delete process.env.TEST_API_KEY;
183
+ const response = await (0, llm_1.generateADRContent)(config, baseGenerationRequest);
184
+ expect(response.result).toBeNull();
185
+ expect(response.error).toContain('TEST_API_KEY');
186
+ });
187
+ it('should extract title from markdown h1 and return cleaned content', async () => {
188
+ const markdown = '# Adopt New Database\n\n## Context\nWe need a new DB.';
189
+ const mockAnalyze = jest.fn().mockResolvedValue(markdown);
190
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
191
+ const response = await (0, llm_1.generateADRContent)(config, baseGenerationRequest);
192
+ expect(response.result).not.toBeNull();
193
+ expect(response.result.title).toBe('Adopt New Database');
194
+ expect(response.result.content).toBe(markdown);
195
+ expect(response.result.timestamp).toBeDefined();
196
+ expect(response.error).toBeUndefined();
197
+ });
198
+ it('should strip code block wrapper from markdown response', async () => {
199
+ const inner = '# My Decision\n\n## Context\nSome context.';
200
+ const wrapped = '```markdown\n' + inner + '\n```';
201
+ const mockAnalyze = jest.fn().mockResolvedValue(wrapped);
202
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
203
+ const response = await (0, llm_1.generateADRContent)(config, baseGenerationRequest);
204
+ expect(response.result).not.toBeNull();
205
+ expect(response.result.title).toBe('My Decision');
206
+ expect(response.result.content).toBe(inner);
207
+ });
208
+ it('should use Untitled Decision when markdown has no h1', async () => {
209
+ const markdown = '## Context\nSome context without a title.';
210
+ const mockAnalyze = jest.fn().mockResolvedValue(markdown);
211
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
212
+ const response = await (0, llm_1.generateADRContent)(config, baseGenerationRequest);
213
+ expect(response.result).not.toBeNull();
214
+ expect(response.result.title).toBe('Untitled Decision');
215
+ });
216
+ it('should return auth error when provider throws with status 401', async () => {
217
+ const mockAnalyze = jest.fn().mockRejectedValue({ status: 401, message: 'Unauthorized' });
218
+ mockedGetProvider.mockReturnValue(createMockProvider(mockAnalyze));
219
+ const response = await (0, llm_1.generateADRContent)(config, baseGenerationRequest);
220
+ expect(response.result).toBeNull();
221
+ expect(response.error).toContain('Invalid API key');
222
+ });
223
+ });
224
+ //# sourceMappingURL=llm.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm.test.js","sourceRoot":"","sources":["../../src/llm/llm.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=response-parser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-parser.test.d.ts","sourceRoot":"","sources":["../../src/llm/response-parser.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ jest.mock('../logger', () => ({
4
+ loggerInstance: {
5
+ info: jest.fn(),
6
+ warn: jest.fn(),
7
+ error: jest.fn(),
8
+ debug: jest.fn(),
9
+ },
10
+ }));
11
+ const response_parser_1 = require("./response-parser");
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+ afterEach(() => {
16
+ jest.restoreAllMocks();
17
+ });
18
+ describe('parseAnalysisResponse', () => {
19
+ it('should parse a clean JSON string', () => {
20
+ const input = JSON.stringify({ is_significant: true, reason: 'big change' });
21
+ const result = (0, response_parser_1.parseAnalysisResponse)(input);
22
+ expect(result).toEqual({ is_significant: true, reason: 'big change' });
23
+ });
24
+ it('should extract JSON wrapped in ```json code block', () => {
25
+ const input = '```json\n{"is_significant": true, "reason": "refactored module"}\n```';
26
+ const result = (0, response_parser_1.parseAnalysisResponse)(input);
27
+ expect(result).toEqual({ is_significant: true, reason: 'refactored module' });
28
+ });
29
+ it('should extract JSON wrapped in ``` code block without language', () => {
30
+ const input = '```\n{"is_significant": true, "reason": "new feature"}\n```';
31
+ const result = (0, response_parser_1.parseAnalysisResponse)(input);
32
+ expect(result).toEqual({ is_significant: true, reason: 'new feature' });
33
+ });
34
+ it('should extract JSON embedded in surrounding text', () => {
35
+ const input = 'Here is the analysis:\n{"is_significant": false, "reason": "minor fix"}\nEnd of response.';
36
+ const result = (0, response_parser_1.parseAnalysisResponse)(input);
37
+ expect(result).toEqual({ is_significant: false, reason: 'minor fix' });
38
+ });
39
+ it('should parse is_significant: false with a reason', () => {
40
+ const input = JSON.stringify({ is_significant: false, reason: 'no impact' });
41
+ const result = (0, response_parser_1.parseAnalysisResponse)(input);
42
+ expect(result).toEqual({ is_significant: false, reason: 'no impact' });
43
+ });
44
+ it('should include confidence when within [0, 1]', () => {
45
+ const input = JSON.stringify({ is_significant: true, reason: 'important', confidence: 0.85 });
46
+ const result = (0, response_parser_1.parseAnalysisResponse)(input);
47
+ expect(result).toEqual({ is_significant: true, reason: 'important', confidence: 0.85 });
48
+ });
49
+ it('should not include confidence when out of range (> 1)', () => {
50
+ const input = JSON.stringify({ is_significant: true, reason: 'important', confidence: 1.5 });
51
+ const result = (0, response_parser_1.parseAnalysisResponse)(input);
52
+ expect(result).toEqual({ is_significant: true, reason: 'important' });
53
+ expect(result.confidence).toBeUndefined();
54
+ });
55
+ it('should not include confidence when it is not a number', () => {
56
+ const input = JSON.stringify({ is_significant: true, reason: 'important', confidence: 'high' });
57
+ const result = (0, response_parser_1.parseAnalysisResponse)(input);
58
+ expect(result).toEqual({ is_significant: true, reason: 'important' });
59
+ expect(result.confidence).toBeUndefined();
60
+ });
61
+ it('should throw when is_significant is true but reason is empty', () => {
62
+ const input = JSON.stringify({ is_significant: true, reason: '' });
63
+ expect(() => (0, response_parser_1.parseAnalysisResponse)(input)).toThrow('LLM indicated significant change but provided no reason');
64
+ });
65
+ it('should throw when is_significant is not a boolean', () => {
66
+ const input = JSON.stringify({ is_significant: 'yes', reason: 'something' });
67
+ expect(() => (0, response_parser_1.parseAnalysisResponse)(input)).toThrow('Invalid response format');
68
+ });
69
+ it('should throw when reason is not a string', () => {
70
+ const input = JSON.stringify({ is_significant: true, reason: 123 });
71
+ expect(() => (0, response_parser_1.parseAnalysisResponse)(input)).toThrow('Invalid response format');
72
+ });
73
+ it('should throw on invalid JSON', () => {
74
+ const input = 'not json at all {{{';
75
+ expect(() => (0, response_parser_1.parseAnalysisResponse)(input)).toThrow();
76
+ });
77
+ it('should throw on empty string', () => {
78
+ expect(() => (0, response_parser_1.parseAnalysisResponse)('')).toThrow();
79
+ });
80
+ });
81
+ describe('extractTitleFromMarkdown', () => {
82
+ it('should extract h1 title from markdown', () => {
83
+ const input = '# My Decision Title\n\nSome content here.';
84
+ expect((0, response_parser_1.extractTitleFromMarkdown)(input)).toBe('My Decision Title');
85
+ });
86
+ it('should return "Untitled Decision" when only h2 is present', () => {
87
+ const input = '## Second level\n\nSome content.';
88
+ expect((0, response_parser_1.extractTitleFromMarkdown)(input)).toBe('Untitled Decision');
89
+ });
90
+ it('should return "Untitled Decision" for empty string', () => {
91
+ expect((0, response_parser_1.extractTitleFromMarkdown)('')).toBe('Untitled Decision');
92
+ });
93
+ it('should extract h1 from content wrapped in ```markdown code block', () => {
94
+ const input = '```markdown\n# Wrapped Title\n\nBody text.\n```';
95
+ expect((0, response_parser_1.extractTitleFromMarkdown)(input)).toBe('Wrapped Title');
96
+ });
97
+ it('should trim trailing whitespace from the title', () => {
98
+ const input = '# Title With Spaces \n\nContent.';
99
+ expect((0, response_parser_1.extractTitleFromMarkdown)(input)).toBe('Title With Spaces');
100
+ });
101
+ });
102
+ describe('parseLLMResponse', () => {
103
+ it('should return validator result for valid JSON', () => {
104
+ const input = JSON.stringify({ key: 'value' });
105
+ const validator = (parsed) => parsed;
106
+ const result = (0, response_parser_1.parseLLMResponse)(input, validator);
107
+ expect(result).toEqual({ key: 'value' });
108
+ });
109
+ it('should rethrow with descriptive message when validator throws', () => {
110
+ const input = JSON.stringify({ key: 'value' });
111
+ const validator = () => {
112
+ throw new Error('validation failed');
113
+ };
114
+ expect(() => (0, response_parser_1.parseLLMResponse)(input, validator)).toThrow('Failed to parse LLM response as JSON');
115
+ expect(() => (0, response_parser_1.parseLLMResponse)(input, validator)).toThrow(expect.objectContaining({
116
+ message: expect.stringContaining(input.substring(0, 20)),
117
+ }));
118
+ });
119
+ it('should rethrow with descriptive message on invalid JSON', () => {
120
+ const input = 'totally not json';
121
+ const validator = (parsed) => parsed;
122
+ expect(() => (0, response_parser_1.parseLLMResponse)(input, validator)).toThrow('Failed to parse LLM response as JSON');
123
+ expect(() => (0, response_parser_1.parseLLMResponse)(input, validator)).toThrow(expect.objectContaining({
124
+ message: expect.stringContaining('totally not json'),
125
+ }));
126
+ });
127
+ it('should strip code block wrapping before parsing', () => {
128
+ const input = '```json\n{"status": "ok"}\n```';
129
+ const validator = (parsed) => parsed;
130
+ const result = (0, response_parser_1.parseLLMResponse)(input, validator);
131
+ expect(result).toEqual({ status: 'ok' });
132
+ });
133
+ });
134
+ //# sourceMappingURL=response-parser.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-parser.test.js","sourceRoot":"","sources":["../../src/llm/response-parser.test.ts"],"names":[],"mappings":";;AAAA,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5B,cAAc,EAAE;QACd,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;QACf,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;QACf,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE;QAChB,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE;KACjB;CACF,CAAC,CAAC,CAAC;AAEJ,uDAI2B;AAE3B,UAAU,CAAC,GAAG,EAAE;IACd,IAAI,CAAC,aAAa,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,IAAI,CAAC,eAAe,EAAE,CAAC;AACzB,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;QAC7E,MAAM,MAAM,GAAG,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,KAAK,GAAG,uEAAuE,CAAC;QACtF,MAAM,MAAM,GAAG,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,KAAK,GAAG,6DAA6D,CAAC;QAC5E,MAAM,MAAM,GAAG,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,2FAA2F,CAAC;QAC1G,MAAM,MAAM,GAAG,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;QAC7E,MAAM,MAAM,GAAG,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9F,MAAM,MAAM,GAAG,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7F,MAAM,MAAM,GAAG,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;QACtE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAa,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;QAChG,MAAM,MAAM,GAAG,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;QACtE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAa,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QACnE,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAChD,yDAAyD,CAC1D,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;QAC7E,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACpE,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,KAAK,GAAG,qBAAqB,CAAC;QACpC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,uCAAqB,EAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,uCAAqB,EAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,KAAK,GAAG,2CAA2C,CAAC;QAC1D,MAAM,CAAC,IAAA,0CAAwB,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,KAAK,GAAG,kCAAkC,CAAC;QACjD,MAAM,CAAC,IAAA,0CAAwB,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,IAAA,0CAAwB,EAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,KAAK,GAAG,iDAAiD,CAAC;QAChE,MAAM,CAAC,IAAA,0CAAwB,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,KAAK,GAAG,oCAAoC,CAAC;QACnD,MAAM,CAAC,IAAA,0CAAwB,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,CAAC,MAAe,EAAE,EAAE,CAAC,MAAyB,CAAC;QACjE,MAAM,MAAM,GAAG,IAAA,kCAAgB,EAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,GAAU,EAAE;YAC5B,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACvC,CAAC,CAAC;QACF,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,kCAAgB,EAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CACtD,sCAAsC,CACvC,CAAC;QACF,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,kCAAgB,EAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CACtD,MAAM,CAAC,gBAAgB,CAAC;YACtB,OAAO,EAAE,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;SACzD,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,KAAK,GAAG,kBAAkB,CAAC;QACjC,MAAM,SAAS,GAAG,CAAC,MAAe,EAAE,EAAE,CAAC,MAAM,CAAC;QAC9C,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,kCAAgB,EAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CACtD,sCAAsC,CACvC,CAAC;QACF,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,kCAAgB,EAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CACtD,MAAM,CAAC,gBAAgB,CAAC;YACtB,OAAO,EAAE,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,CAAC;SACrD,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,KAAK,GAAG,gCAAgC,CAAC;QAC/C,MAAM,SAAS,GAAG,CAAC,MAAe,EAAE,EAAE,CAAC,MAA4B,CAAC;QACpE,MAAM,MAAM,GAAG,IAAA,kCAAgB,EAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=console-presenter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-presenter.test.d.ts","sourceRoot":"","sources":["../../src/presenters/console-presenter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const console_presenter_1 = require("./console-presenter");
4
+ describe('ConsolePresenter', () => {
5
+ let logSpy;
6
+ let errorSpy;
7
+ let instance;
8
+ beforeEach(() => {
9
+ jest.clearAllMocks();
10
+ logSpy = jest.spyOn(console, 'log').mockImplementation();
11
+ errorSpy = jest.spyOn(console, 'error').mockImplementation();
12
+ instance = new console_presenter_1.ConsolePresenter();
13
+ });
14
+ afterEach(() => {
15
+ logSpy.mockRestore();
16
+ errorSpy.mockRestore();
17
+ });
18
+ function allLogOutput() {
19
+ return logSpy.mock.calls.map((c) => c.join(' ')).join('\n');
20
+ }
21
+ function allErrorOutput() {
22
+ return errorSpy.mock.calls.map((c) => c.join(' ')).join('\n');
23
+ }
24
+ describe('showConfigError', () => {
25
+ test('outputs Configuration Error to stderr', () => {
26
+ instance.showConfigError();
27
+ const output = allErrorOutput();
28
+ expect(output).toContain('Configuration Error');
29
+ });
30
+ test('suggests running cadr init', () => {
31
+ instance.showConfigError();
32
+ const output = allErrorOutput();
33
+ expect(output).toContain('cadr init');
34
+ });
35
+ });
36
+ describe('showGitError', () => {
37
+ test('outputs Git Error with the provided message to stderr', () => {
38
+ instance.showGitError('not a repository');
39
+ const output = allErrorOutput();
40
+ expect(output).toContain('Git Error: not a repository');
41
+ });
42
+ });
43
+ describe('showReadFilesError', () => {
44
+ test('outputs Failed to read changed files to stderr', () => {
45
+ instance.showReadFilesError();
46
+ const output = allErrorOutput();
47
+ expect(output).toContain('Failed to read changed files');
48
+ });
49
+ });
50
+ describe('showNoChanges', () => {
51
+ test('staged mode mentions staged, git add, and cadr analyze --staged', () => {
52
+ instance.showNoChanges({ mode: 'staged' });
53
+ const output = allLogOutput();
54
+ expect(output).toContain('staged');
55
+ expect(output).toContain('git add');
56
+ expect(output).toContain('cadr analyze --staged');
57
+ });
58
+ test('all mode mentions uncommitted and cadr analyze', () => {
59
+ instance.showNoChanges({ mode: 'all' });
60
+ const output = allLogOutput();
61
+ expect(output).toContain('uncommitted');
62
+ expect(output).toContain('cadr analyze');
63
+ });
64
+ test('branch-diff mode with base and head mentions between main and HEAD', () => {
65
+ instance.showNoChanges({ mode: 'branch-diff', base: 'main', head: 'HEAD' });
66
+ const output = allLogOutput();
67
+ expect(output).toContain('between main and HEAD');
68
+ });
69
+ test('branch-diff mode without base/head defaults to origin/main and HEAD', () => {
70
+ instance.showNoChanges({ mode: 'branch-diff' });
71
+ const output = allLogOutput();
72
+ expect(output).toContain('origin/main');
73
+ expect(output).toContain('HEAD');
74
+ });
75
+ });
76
+ describe('showAnalyzingFiles', () => {
77
+ test('all mode with 2 files shows count and lists both files', () => {
78
+ instance.showAnalyzingFiles(['a.ts', 'b.ts'], { mode: 'all' });
79
+ const output = allLogOutput();
80
+ expect(output).toContain('2 uncommitted files');
81
+ expect(output).toContain('a.ts');
82
+ expect(output).toContain('b.ts');
83
+ });
84
+ test('staged mode with 1 file shows singular file count', () => {
85
+ instance.showAnalyzingFiles(['a.ts'], { mode: 'staged' });
86
+ const output = allLogOutput();
87
+ expect(output).toContain('1 staged file');
88
+ });
89
+ test('branch-diff mode shows files changed between base and head', () => {
90
+ instance.showAnalyzingFiles(['a.ts', 'b.ts'], { mode: 'branch-diff', base: 'main', head: 'feat' });
91
+ const output = allLogOutput();
92
+ expect(output).toContain('2 files changed between main and feat');
93
+ });
94
+ });
95
+ describe('showNoDiffContent', () => {
96
+ test('outputs No diff content found', () => {
97
+ instance.showNoDiffContent();
98
+ const output = allLogOutput();
99
+ expect(output).toContain('No diff content found');
100
+ });
101
+ });
102
+ describe('showSendingToLLM', () => {
103
+ test('staged mode mentions staged changes, provider, and model', () => {
104
+ instance.showSendingToLLM({ mode: 'staged' }, 'openai', 'gpt-4');
105
+ const output = allLogOutput();
106
+ expect(output).toContain('staged changes');
107
+ expect(output).toContain('openai');
108
+ expect(output).toContain('gpt-4');
109
+ });
110
+ test('all mode mentions uncommitted changes', () => {
111
+ instance.showSendingToLLM({ mode: 'all' }, 'gemini', 'gemini-pro');
112
+ const output = allLogOutput();
113
+ expect(output).toContain('uncommitted changes');
114
+ });
115
+ test('branch-diff mode mentions changes', () => {
116
+ instance.showSendingToLLM({ mode: 'branch-diff' }, 'openai', 'gpt-4');
117
+ const output = allLogOutput();
118
+ expect(output).toContain('changes');
119
+ });
120
+ });
121
+ describe('showAnalysisFailed', () => {
122
+ test('outputs Analysis failed and the error message to stderr', () => {
123
+ instance.showAnalysisFailed('some error');
124
+ const output = allErrorOutput();
125
+ expect(output).toContain('Analysis failed');
126
+ expect(output).toContain('some error');
127
+ });
128
+ test('shows Unknown error occurred when no argument provided', () => {
129
+ instance.showAnalysisFailed();
130
+ const output = allErrorOutput();
131
+ expect(output).toContain('Unknown error occurred');
132
+ });
133
+ });
134
+ describe('showAnalysisComplete', () => {
135
+ test('outputs Analysis Complete', () => {
136
+ instance.showAnalysisComplete();
137
+ const output = allLogOutput();
138
+ expect(output).toContain('Analysis Complete');
139
+ });
140
+ });
141
+ describe('showSignificantResult', () => {
142
+ const baseSummary = {
143
+ fileCount: 5,
144
+ mode: 'all',
145
+ isSignificant: true,
146
+ reason: 'big change',
147
+ };
148
+ test('outputs ARCHITECTURALLY SIGNIFICANT with reason and confidence', () => {
149
+ instance.showSignificantResult({ ...baseSummary, confidence: 0.9 });
150
+ const output = allLogOutput();
151
+ expect(output).toContain('ARCHITECTURALLY SIGNIFICANT');
152
+ expect(output).toContain('big change');
153
+ expect(output).toContain('90%');
154
+ });
155
+ test('does not show percentage when confidence is not provided', () => {
156
+ instance.showSignificantResult(baseSummary);
157
+ const output = allLogOutput();
158
+ expect(output).toContain('ARCHITECTURALLY SIGNIFICANT');
159
+ expect(output).not.toContain('%');
160
+ });
161
+ });
162
+ describe('showNotSignificantResult', () => {
163
+ const baseSummary = {
164
+ fileCount: 2,
165
+ mode: 'all',
166
+ isSignificant: false,
167
+ reason: 'trivial',
168
+ };
169
+ test('outputs NOT ARCHITECTURALLY SIGNIFICANT with reason and confidence', () => {
170
+ instance.showNotSignificantResult({ ...baseSummary, confidence: 0.5 });
171
+ const output = allLogOutput();
172
+ expect(output).toContain('NOT ARCHITECTURALLY SIGNIFICANT');
173
+ expect(output).toContain('trivial');
174
+ expect(output).toContain('50%');
175
+ });
176
+ test('does not show percentage when confidence is not provided', () => {
177
+ instance.showNotSignificantResult(baseSummary);
178
+ const output = allLogOutput();
179
+ expect(output).toContain('NOT ARCHITECTURALLY SIGNIFICANT');
180
+ expect(output).not.toContain('%');
181
+ });
182
+ });
183
+ describe('showGeneratingADR', () => {
184
+ test('outputs Generating ADR', () => {
185
+ instance.showGeneratingADR();
186
+ const output = allLogOutput();
187
+ expect(output).toContain('Generating ADR');
188
+ });
189
+ });
190
+ describe('showGenerationFailed', () => {
191
+ test('outputs ADR generation failed and error message to stderr', () => {
192
+ instance.showGenerationFailed('error msg');
193
+ const output = allErrorOutput();
194
+ expect(output).toContain('ADR generation failed');
195
+ expect(output).toContain('error msg');
196
+ });
197
+ });
198
+ describe('showADRSuccess', () => {
199
+ test('outputs Success, file path, and Next steps', () => {
200
+ instance.showADRSuccess('/path/to/adr.md');
201
+ const output = allLogOutput();
202
+ expect(output).toContain('Success');
203
+ expect(output).toContain('/path/to/adr.md');
204
+ expect(output).toContain('Next steps');
205
+ });
206
+ });
207
+ describe('showSkippingGeneration', () => {
208
+ test('outputs Skipping ADR generation', () => {
209
+ instance.showSkippingGeneration();
210
+ const output = allLogOutput();
211
+ expect(output).toContain('Skipping ADR generation');
212
+ });
213
+ });
214
+ describe('showUnexpectedError', () => {
215
+ test('outputs unexpected error to stderr', () => {
216
+ instance.showUnexpectedError();
217
+ const output = allErrorOutput();
218
+ expect(output).toContain('unexpected error');
219
+ });
220
+ });
221
+ describe('presenter export', () => {
222
+ test('is an instance of ConsolePresenter', () => {
223
+ expect(console_presenter_1.presenter).toBeInstanceOf(console_presenter_1.ConsolePresenter);
224
+ });
225
+ });
226
+ });
227
+ //# sourceMappingURL=console-presenter.test.js.map