cadr-cli 0.0.1 → 1.9.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.
- package/dist/adr.d.ts +50 -0
- package/dist/adr.d.ts.map +1 -0
- package/dist/adr.js +156 -0
- package/dist/adr.js.map +1 -0
- package/dist/adr.test.d.ts +8 -0
- package/dist/adr.test.d.ts.map +1 -0
- package/dist/adr.test.js +256 -0
- package/dist/adr.test.js.map +1 -0
- package/dist/analysis.d.ts +24 -0
- package/dist/analysis.d.ts.map +1 -0
- package/dist/analysis.js +281 -0
- package/dist/analysis.js.map +1 -0
- package/dist/analysis.test.d.ts +8 -0
- package/dist/analysis.test.d.ts.map +1 -0
- package/dist/analysis.test.js +351 -0
- package/dist/analysis.test.js.map +1 -0
- package/dist/commands/analyze.d.ts +14 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +56 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +93 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +2 -0
- package/dist/commands/init.test.d.ts.map +1 -0
- package/dist/commands/init.test.js +56 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +208 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +97 -0
- package/dist/config.test.js.map +1 -0
- package/dist/git.d.ts +42 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +157 -0
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +78 -62
- package/dist/index.js.map +1 -1
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +51 -0
- package/dist/index.test.js.map +1 -0
- package/dist/llm.d.ts +73 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +263 -0
- package/dist/llm.js.map +1 -0
- package/dist/llm.test.d.ts +2 -0
- package/dist/llm.test.d.ts.map +1 -0
- package/dist/llm.test.js +592 -0
- package/dist/llm.test.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +5 -3
- package/dist/logger.js.map +1 -1
- package/dist/logger.test.d.ts +2 -0
- package/dist/logger.test.d.ts.map +1 -0
- package/dist/logger.test.js +78 -0
- package/dist/logger.test.js.map +1 -0
- package/dist/prompts.d.ts +49 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +195 -0
- package/dist/prompts.js.map +1 -0
- package/dist/prompts.test.d.ts +2 -0
- package/dist/prompts.test.d.ts.map +1 -0
- package/dist/prompts.test.js +427 -0
- package/dist/prompts.test.js.map +1 -0
- package/dist/providers/gemini.d.ts +3 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +39 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +6 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +3 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +25 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/registry.d.ts +4 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +16 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.d.ts +12 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +5 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/version.test.d.ts +3 -0
- package/dist/version.test.d.ts.map +1 -0
- package/dist/version.test.js +25 -0
- package/dist/version.test.js.map +1 -0
- package/package.json +14 -5
- package/src/adr.test.ts +278 -0
- package/src/adr.ts +136 -0
- package/src/analysis.test.ts +396 -0
- package/src/analysis.ts +262 -0
- package/src/commands/analyze.ts +56 -0
- package/src/commands/init.test.ts +27 -0
- package/src/commands/init.ts +99 -0
- package/src/config.test.ts +79 -0
- package/src/config.ts +214 -0
- package/src/git.ts +240 -0
- package/src/index.test.ts +59 -0
- package/src/index.ts +80 -60
- package/src/llm.test.ts +701 -0
- package/src/llm.ts +344 -0
- package/src/logger.test.ts +90 -0
- package/src/logger.ts +6 -3
- package/src/prompts.test.ts +515 -0
- package/src/prompts.ts +174 -0
- package/src/providers/gemini.ts +41 -0
- package/src/providers/index.ts +1 -0
- package/src/providers/openai.ts +22 -0
- package/src/providers/registry.ts +16 -0
- package/src/providers/types.ts +14 -0
- package/src/version.test.ts +29 -0
- package/bin/cadr.js +0 -16
package/dist/analysis.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Analysis Orchestration Module
|
|
4
|
+
*
|
|
5
|
+
* Coordinates the complete analysis flow: config loading, git operations,
|
|
6
|
+
* prompt formatting, LLM analysis, and result display.
|
|
7
|
+
* Implements fail-open principle per constitution requirements.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.runAnalysis = runAnalysis;
|
|
44
|
+
const config_1 = require("./config");
|
|
45
|
+
const git_1 = require("./git");
|
|
46
|
+
const prompts_1 = require("./prompts");
|
|
47
|
+
const llm_1 = require("./llm");
|
|
48
|
+
const logger_1 = require("./logger");
|
|
49
|
+
const adr_1 = require("./adr");
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
/**
|
|
52
|
+
* Run complete analysis workflow
|
|
53
|
+
*
|
|
54
|
+
* This function orchestrates the entire analysis process:
|
|
55
|
+
* 1. Load configuration
|
|
56
|
+
* 2. Get changed files and diff based on options
|
|
57
|
+
* 3. Format LLM prompt
|
|
58
|
+
* 4. Call LLM for analysis
|
|
59
|
+
* 5. Display results
|
|
60
|
+
*
|
|
61
|
+
* Follows fail-open principle: always exits cleanly, never throws.
|
|
62
|
+
*
|
|
63
|
+
* @param diffOptions - Options specifying which changes to analyze (defaults to all uncommitted)
|
|
64
|
+
*/
|
|
65
|
+
async function runAnalysis(diffOptions = { mode: 'all' }) {
|
|
66
|
+
try {
|
|
67
|
+
logger_1.loggerInstance.info('Starting analysis workflow');
|
|
68
|
+
// Step 1: Load configuration
|
|
69
|
+
const configPath = (0, config_1.getDefaultConfigPath)();
|
|
70
|
+
const config = await (0, config_1.loadConfig)(configPath);
|
|
71
|
+
if (!config) {
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.error('\n❌ Configuration Error');
|
|
74
|
+
// eslint-disable-next-line no-console
|
|
75
|
+
console.error('Configuration file not found or invalid.');
|
|
76
|
+
// eslint-disable-next-line no-console
|
|
77
|
+
console.error('\n💡 Run `cadr init` to create a configuration file.\n');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Step 2: Get changed files based on diff options
|
|
81
|
+
let changedFiles;
|
|
82
|
+
try {
|
|
83
|
+
changedFiles = await (0, git_1.getChangedFiles)(diffOptions);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof git_1.GitError) {
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.error(`\n❌ Git Error: ${error.message}\n`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.error('\n❌ Failed to read changed files\n');
|
|
93
|
+
}
|
|
94
|
+
logger_1.loggerInstance.error('Failed to get changed files', { error, mode: diffOptions.mode });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Check if there are changed files
|
|
98
|
+
const modeText = diffOptions.mode === 'staged' ? 'staged' :
|
|
99
|
+
diffOptions.mode === 'branch-diff' ? `between ${diffOptions.base || 'origin/main'} and ${diffOptions.head || 'HEAD'}` :
|
|
100
|
+
'uncommitted';
|
|
101
|
+
if (changedFiles.length === 0) {
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.log(`\nℹ️ No changes to analyze ${diffOptions.mode === 'branch-diff' ? modeText : `(${modeText})`}`);
|
|
104
|
+
if (diffOptions.mode === 'staged') {
|
|
105
|
+
// eslint-disable-next-line no-console
|
|
106
|
+
console.log('💡 Stage some files first:');
|
|
107
|
+
// eslint-disable-next-line no-console
|
|
108
|
+
console.log(' git add <files>');
|
|
109
|
+
// eslint-disable-next-line no-console
|
|
110
|
+
console.log(' cadr analyze --staged\n');
|
|
111
|
+
}
|
|
112
|
+
else if (diffOptions.mode === 'branch-diff') {
|
|
113
|
+
// eslint-disable-next-line no-console
|
|
114
|
+
console.log('💡 No changes found between specified git references.\n');
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// eslint-disable-next-line no-console
|
|
118
|
+
console.log('💡 Make some changes first, then run:');
|
|
119
|
+
// eslint-disable-next-line no-console
|
|
120
|
+
console.log(' cadr analyze\n');
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Display files being analyzed
|
|
125
|
+
const fileCountText = diffOptions.mode === 'branch-diff' ?
|
|
126
|
+
`${changedFiles.length} file${changedFiles.length === 1 ? '' : 's'} changed ${modeText}` :
|
|
127
|
+
`${changedFiles.length} ${modeText} file${changedFiles.length === 1 ? '' : 's'}`;
|
|
128
|
+
// eslint-disable-next-line no-console
|
|
129
|
+
console.log(`\n📝 Analyzing ${fileCountText}:`);
|
|
130
|
+
changedFiles.forEach((file) => {
|
|
131
|
+
// eslint-disable-next-line no-console
|
|
132
|
+
console.log(` • ${file}`);
|
|
133
|
+
});
|
|
134
|
+
// eslint-disable-next-line no-console
|
|
135
|
+
console.log('');
|
|
136
|
+
// Step 3: Get diff content
|
|
137
|
+
let diffContent;
|
|
138
|
+
try {
|
|
139
|
+
diffContent = await (0, git_1.getDiff)(diffOptions);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
// eslint-disable-next-line no-console
|
|
143
|
+
console.error('\n❌ Failed to read diff content\n');
|
|
144
|
+
logger_1.loggerInstance.error('Failed to get diff', { error, mode: diffOptions.mode });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Check if diff is empty
|
|
148
|
+
if (!diffContent || diffContent.trim().length === 0) {
|
|
149
|
+
// eslint-disable-next-line no-console
|
|
150
|
+
console.log('\nℹ️ No diff content found\n');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Step 4: Format prompt
|
|
154
|
+
const repositoryContext = path.basename(process.cwd());
|
|
155
|
+
const prompt = (0, prompts_1.formatPrompt)(prompts_1.ANALYSIS_PROMPT_V1, {
|
|
156
|
+
file_paths: changedFiles,
|
|
157
|
+
diff_content: diffContent,
|
|
158
|
+
});
|
|
159
|
+
// Display analysis start
|
|
160
|
+
const analysisText = diffOptions.mode === 'staged' ? 'staged changes' :
|
|
161
|
+
diffOptions.mode === 'branch-diff' ? 'changes' :
|
|
162
|
+
'uncommitted changes';
|
|
163
|
+
// eslint-disable-next-line no-console
|
|
164
|
+
console.log(`🔍 Analyzing ${analysisText} for architectural significance...\n`);
|
|
165
|
+
// eslint-disable-next-line no-console
|
|
166
|
+
console.log(`🤖 Sending to ${config.provider} ${config.analysis_model}...\n`);
|
|
167
|
+
// Step 5: Call LLM for analysis
|
|
168
|
+
const response = await (0, llm_1.analyzeChanges)(config, {
|
|
169
|
+
file_paths: changedFiles,
|
|
170
|
+
diff_content: diffContent,
|
|
171
|
+
repository_context: repositoryContext,
|
|
172
|
+
analysis_prompt: prompt,
|
|
173
|
+
});
|
|
174
|
+
// Step 6: Display results
|
|
175
|
+
if (!response.result || response.error) {
|
|
176
|
+
// eslint-disable-next-line no-console
|
|
177
|
+
console.error('\n❌ Analysis failed');
|
|
178
|
+
// eslint-disable-next-line no-console
|
|
179
|
+
console.error(`\n${response.error || 'Unknown error occurred'}\n`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const result = response.result;
|
|
183
|
+
// Display analysis result
|
|
184
|
+
// eslint-disable-next-line no-console
|
|
185
|
+
console.log('✅ Analysis Complete\n');
|
|
186
|
+
if (result.is_significant) {
|
|
187
|
+
// eslint-disable-next-line no-console
|
|
188
|
+
console.log('📊 Result: ✨ ARCHITECTURALLY SIGNIFICANT');
|
|
189
|
+
// eslint-disable-next-line no-console
|
|
190
|
+
console.log(`💭 Reasoning: ${result.reason}\n`);
|
|
191
|
+
if (result.confidence) {
|
|
192
|
+
// eslint-disable-next-line no-console
|
|
193
|
+
console.log(`🎯 Confidence: ${(result.confidence * 100).toFixed(0)}%\n`);
|
|
194
|
+
}
|
|
195
|
+
// Prompt user for ADR generation
|
|
196
|
+
const shouldGenerate = await (0, prompts_1.promptForGeneration)(result.reason);
|
|
197
|
+
if (shouldGenerate) {
|
|
198
|
+
// eslint-disable-next-line no-console
|
|
199
|
+
console.log('\n🧠 Generating ADR draft...\n');
|
|
200
|
+
// Format generation prompt
|
|
201
|
+
const generationPrompt = (0, prompts_1.formatGenerationPrompt)({
|
|
202
|
+
file_paths: changedFiles,
|
|
203
|
+
diff_content: diffContent,
|
|
204
|
+
});
|
|
205
|
+
// Call LLM to generate ADR content
|
|
206
|
+
const generationResponse = await (0, llm_1.generateADRContent)(config, {
|
|
207
|
+
file_paths: changedFiles,
|
|
208
|
+
diff_content: diffContent,
|
|
209
|
+
reason: result.reason,
|
|
210
|
+
generation_prompt: generationPrompt,
|
|
211
|
+
});
|
|
212
|
+
if (!generationResponse.result || generationResponse.error) {
|
|
213
|
+
// eslint-disable-next-line no-console
|
|
214
|
+
console.error('\n❌ ADR generation failed');
|
|
215
|
+
// eslint-disable-next-line no-console
|
|
216
|
+
console.error(`\n${generationResponse.error || 'Unknown error occurred'}\n`);
|
|
217
|
+
logger_1.loggerInstance.error('ADR generation failed', { error: generationResponse.error });
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
// Save ADR to file
|
|
221
|
+
const saveResult = (0, adr_1.saveADR)(generationResponse.result.content, generationResponse.result.title);
|
|
222
|
+
if (saveResult.success && saveResult.filePath) {
|
|
223
|
+
// eslint-disable-next-line no-console
|
|
224
|
+
console.log('✅ Success! Draft ADR created\n');
|
|
225
|
+
// eslint-disable-next-line no-console
|
|
226
|
+
console.log(`📄 File: ${saveResult.filePath}\n`);
|
|
227
|
+
// eslint-disable-next-line no-console
|
|
228
|
+
console.log('💡 Next steps:');
|
|
229
|
+
// eslint-disable-next-line no-console
|
|
230
|
+
console.log(' 1. Review and refine the generated ADR');
|
|
231
|
+
// eslint-disable-next-line no-console
|
|
232
|
+
console.log(' 2. Commit it alongside your code changes\n');
|
|
233
|
+
logger_1.loggerInstance.info('ADR generation workflow completed successfully', {
|
|
234
|
+
filePath: saveResult.filePath,
|
|
235
|
+
title: generationResponse.result.title,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// eslint-disable-next-line no-console
|
|
240
|
+
console.error('\n❌ Failed to save ADR');
|
|
241
|
+
// eslint-disable-next-line no-console
|
|
242
|
+
console.error(`\n${saveResult.error || 'Unknown error occurred'}\n`);
|
|
243
|
+
logger_1.loggerInstance.error('Failed to save ADR', { error: saveResult.error });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// User declined generation
|
|
249
|
+
// eslint-disable-next-line no-console
|
|
250
|
+
console.log('\n📋 Skipping ADR generation');
|
|
251
|
+
// eslint-disable-next-line no-console
|
|
252
|
+
console.log('🎯 Recommendation: Consider documenting this decision manually.\n');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// eslint-disable-next-line no-console
|
|
257
|
+
console.log('📊 Result: ℹ️ NOT ARCHITECTURALLY SIGNIFICANT');
|
|
258
|
+
// eslint-disable-next-line no-console
|
|
259
|
+
console.log(`💭 Reasoning: ${result.reason}\n`);
|
|
260
|
+
if (result.confidence) {
|
|
261
|
+
// eslint-disable-next-line no-console
|
|
262
|
+
console.log(`🎯 Confidence: ${(result.confidence * 100).toFixed(0)}%\n`);
|
|
263
|
+
}
|
|
264
|
+
// eslint-disable-next-line no-console
|
|
265
|
+
console.log('✅ No ADR needed for these changes.\n');
|
|
266
|
+
}
|
|
267
|
+
logger_1.loggerInstance.info('Analysis workflow completed successfully', {
|
|
268
|
+
is_significant: result.is_significant,
|
|
269
|
+
file_count: changedFiles.length,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
// Final catch-all for any unexpected errors (fail-open)
|
|
274
|
+
logger_1.loggerInstance.error('Unexpected error in analysis workflow', { error });
|
|
275
|
+
// eslint-disable-next-line no-console
|
|
276
|
+
console.error('\n❌ An unexpected error occurred');
|
|
277
|
+
// eslint-disable-next-line no-console
|
|
278
|
+
console.error('Please check the logs for more details.\n');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=analysis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis.js","sourceRoot":"","sources":["../src/analysis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis.test.d.ts","sourceRoot":"","sources":["../src/analysis.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Analysis Module Integration Tests
|
|
4
|
+
*
|
|
5
|
+
* Tests for the complete analysis workflow including generation.
|
|
6
|
+
* Following TDD: These tests are written BEFORE implementation.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
/* eslint-disable no-console */
|
|
43
|
+
const analysis_1 = require("./analysis");
|
|
44
|
+
const config = __importStar(require("./config"));
|
|
45
|
+
const git = __importStar(require("./git"));
|
|
46
|
+
const llm = __importStar(require("./llm"));
|
|
47
|
+
const prompts = __importStar(require("./prompts"));
|
|
48
|
+
const adr = __importStar(require("./adr"));
|
|
49
|
+
// Mock all dependencies
|
|
50
|
+
jest.mock('./config');
|
|
51
|
+
jest.mock('./git');
|
|
52
|
+
jest.mock('./llm');
|
|
53
|
+
jest.mock('./prompts');
|
|
54
|
+
jest.mock('./adr');
|
|
55
|
+
describe('Analysis with Generation Integration', () => {
|
|
56
|
+
const mockConfig = {
|
|
57
|
+
provider: 'openai',
|
|
58
|
+
analysis_model: 'gpt-4',
|
|
59
|
+
api_key_env: 'OPENAI_API_KEY',
|
|
60
|
+
timeout_seconds: 15
|
|
61
|
+
};
|
|
62
|
+
const mockDiffOptions = { mode: 'staged' };
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
jest.clearAllMocks();
|
|
65
|
+
// Mock console methods to silence output during tests
|
|
66
|
+
jest.spyOn(console, 'log').mockImplementation();
|
|
67
|
+
jest.spyOn(console, 'error').mockImplementation();
|
|
68
|
+
// Default mocks
|
|
69
|
+
config.loadConfig.mockResolvedValue(mockConfig);
|
|
70
|
+
git.getChangedFiles.mockResolvedValue([
|
|
71
|
+
'src/database.ts',
|
|
72
|
+
'src/config.ts'
|
|
73
|
+
]);
|
|
74
|
+
git.getDiff.mockResolvedValue(`
|
|
75
|
+
diff --git a/src/database.ts b/src/database.ts
|
|
76
|
+
+import pg from 'pg';
|
|
77
|
+
+export const database = new pg.Pool();
|
|
78
|
+
`);
|
|
79
|
+
});
|
|
80
|
+
afterEach(() => {
|
|
81
|
+
jest.restoreAllMocks();
|
|
82
|
+
});
|
|
83
|
+
describe('runAnalysis without generation', () => {
|
|
84
|
+
test('completes successfully when change is not significant', async () => {
|
|
85
|
+
llm.analyzeChanges.mockResolvedValue({
|
|
86
|
+
result: {
|
|
87
|
+
is_significant: false,
|
|
88
|
+
reason: 'Minor code formatting changes',
|
|
89
|
+
timestamp: new Date().toISOString()
|
|
90
|
+
},
|
|
91
|
+
error: undefined
|
|
92
|
+
});
|
|
93
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
94
|
+
expect(config.loadConfig).toHaveBeenCalled();
|
|
95
|
+
expect(git.getChangedFiles).toHaveBeenCalledWith(mockDiffOptions);
|
|
96
|
+
expect(git.getDiff).toHaveBeenCalledWith(mockDiffOptions);
|
|
97
|
+
expect(llm.analyzeChanges).toHaveBeenCalled();
|
|
98
|
+
// Should not prompt for generation if not significant
|
|
99
|
+
expect(prompts.promptForGeneration).not.toHaveBeenCalled();
|
|
100
|
+
});
|
|
101
|
+
test('handles missing configuration gracefully', async () => {
|
|
102
|
+
config.loadConfig.mockResolvedValue(null);
|
|
103
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
104
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Configuration'));
|
|
105
|
+
expect(git.getChangedFiles).not.toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
test('handles no changed files gracefully', async () => {
|
|
108
|
+
git.getChangedFiles.mockResolvedValue([]);
|
|
109
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
110
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('No changes'));
|
|
111
|
+
expect(git.getDiff).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
test('handles git errors gracefully', async () => {
|
|
114
|
+
git.getChangedFiles.mockRejectedValue(new Error('Git error'));
|
|
115
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
116
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Failed'));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe('runAnalysis with generation - user confirms', () => {
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
llm.analyzeChanges.mockResolvedValue({
|
|
122
|
+
result: {
|
|
123
|
+
is_significant: true,
|
|
124
|
+
reason: 'Introduces PostgreSQL as primary datastore',
|
|
125
|
+
timestamp: new Date().toISOString()
|
|
126
|
+
},
|
|
127
|
+
error: undefined
|
|
128
|
+
});
|
|
129
|
+
prompts.promptForGeneration.mockResolvedValue(true); // User confirms
|
|
130
|
+
});
|
|
131
|
+
test('prompts for generation when change is significant', async () => {
|
|
132
|
+
llm.generateADRContent.mockResolvedValue({
|
|
133
|
+
result: {
|
|
134
|
+
content: '# Use PostgreSQL\n\n* Status: accepted',
|
|
135
|
+
title: 'Use PostgreSQL',
|
|
136
|
+
timestamp: new Date().toISOString()
|
|
137
|
+
},
|
|
138
|
+
error: undefined
|
|
139
|
+
});
|
|
140
|
+
adr.saveADR.mockReturnValue({
|
|
141
|
+
success: true,
|
|
142
|
+
filePath: 'docs/adr/0001-use-postgresql.md'
|
|
143
|
+
});
|
|
144
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
145
|
+
expect(prompts.promptForGeneration).toHaveBeenCalledWith('Introduces PostgreSQL as primary datastore');
|
|
146
|
+
});
|
|
147
|
+
test('generates ADR when user confirms', async () => {
|
|
148
|
+
llm.generateADRContent.mockResolvedValue({
|
|
149
|
+
result: {
|
|
150
|
+
content: '# Use PostgreSQL\n\n* Status: accepted',
|
|
151
|
+
title: 'Use PostgreSQL',
|
|
152
|
+
timestamp: new Date().toISOString()
|
|
153
|
+
},
|
|
154
|
+
error: undefined
|
|
155
|
+
});
|
|
156
|
+
adr.saveADR.mockReturnValue({
|
|
157
|
+
success: true,
|
|
158
|
+
filePath: 'docs/adr/0001-use-postgresql.md'
|
|
159
|
+
});
|
|
160
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
161
|
+
expect(llm.generateADRContent).toHaveBeenCalled();
|
|
162
|
+
const generationCall = llm.generateADRContent.mock.calls[0];
|
|
163
|
+
expect(generationCall[0]).toEqual(mockConfig);
|
|
164
|
+
expect(generationCall[1]).toMatchObject({
|
|
165
|
+
reason: 'Introduces PostgreSQL as primary datastore'
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
test('saves ADR file after successful generation', async () => {
|
|
169
|
+
const mockADRContent = '# Use PostgreSQL\n\n* Status: accepted\n\n## Context\n\nWe need a database.';
|
|
170
|
+
llm.generateADRContent.mockResolvedValue({
|
|
171
|
+
result: {
|
|
172
|
+
content: mockADRContent,
|
|
173
|
+
title: 'Use PostgreSQL',
|
|
174
|
+
timestamp: new Date().toISOString()
|
|
175
|
+
},
|
|
176
|
+
error: undefined
|
|
177
|
+
});
|
|
178
|
+
adr.saveADR.mockReturnValue({
|
|
179
|
+
success: true,
|
|
180
|
+
filePath: 'docs/adr/0001-use-postgresql.md'
|
|
181
|
+
});
|
|
182
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
183
|
+
expect(adr.saveADR).toHaveBeenCalledWith(mockADRContent, 'Use PostgreSQL');
|
|
184
|
+
});
|
|
185
|
+
test('displays success message with file path', async () => {
|
|
186
|
+
const filePath = 'docs/adr/0001-use-postgresql.md';
|
|
187
|
+
llm.generateADRContent.mockResolvedValue({
|
|
188
|
+
result: {
|
|
189
|
+
content: '# Use PostgreSQL\n\n* Status: accepted',
|
|
190
|
+
title: 'Use PostgreSQL',
|
|
191
|
+
timestamp: new Date().toISOString()
|
|
192
|
+
},
|
|
193
|
+
error: undefined
|
|
194
|
+
});
|
|
195
|
+
adr.saveADR.mockReturnValue({
|
|
196
|
+
success: true,
|
|
197
|
+
filePath
|
|
198
|
+
});
|
|
199
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
200
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Success'));
|
|
201
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining(filePath));
|
|
202
|
+
});
|
|
203
|
+
test('displays next steps after successful generation', async () => {
|
|
204
|
+
llm.generateADRContent.mockResolvedValue({
|
|
205
|
+
result: {
|
|
206
|
+
content: '# Use PostgreSQL\n\n* Status: accepted',
|
|
207
|
+
title: 'Use PostgreSQL',
|
|
208
|
+
timestamp: new Date().toISOString()
|
|
209
|
+
},
|
|
210
|
+
error: undefined
|
|
211
|
+
});
|
|
212
|
+
adr.saveADR.mockReturnValue({
|
|
213
|
+
success: true,
|
|
214
|
+
filePath: 'docs/adr/0001-use-postgresql.md'
|
|
215
|
+
});
|
|
216
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
217
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Next steps'));
|
|
218
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Review'));
|
|
219
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Commit'));
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe('runAnalysis with generation - user declines', () => {
|
|
223
|
+
beforeEach(() => {
|
|
224
|
+
llm.analyzeChanges.mockResolvedValue({
|
|
225
|
+
result: {
|
|
226
|
+
is_significant: true,
|
|
227
|
+
reason: 'Introduces Redis caching layer',
|
|
228
|
+
timestamp: new Date().toISOString()
|
|
229
|
+
},
|
|
230
|
+
error: undefined
|
|
231
|
+
});
|
|
232
|
+
prompts.promptForGeneration.mockResolvedValue(false); // User declines
|
|
233
|
+
});
|
|
234
|
+
test('skips generation when user declines', async () => {
|
|
235
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
236
|
+
expect(prompts.promptForGeneration).toHaveBeenCalled();
|
|
237
|
+
expect(llm.generateADRContent).not.toHaveBeenCalled();
|
|
238
|
+
expect(adr.saveADR).not.toHaveBeenCalled();
|
|
239
|
+
});
|
|
240
|
+
test('displays skip message when user declines', async () => {
|
|
241
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
242
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Skipping'));
|
|
243
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('manual'));
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
describe('runAnalysis with generation - error handling', () => {
|
|
247
|
+
beforeEach(() => {
|
|
248
|
+
llm.analyzeChanges.mockResolvedValue({
|
|
249
|
+
result: {
|
|
250
|
+
is_significant: true,
|
|
251
|
+
reason: 'Introduces Kafka event streaming',
|
|
252
|
+
timestamp: new Date().toISOString()
|
|
253
|
+
},
|
|
254
|
+
error: undefined
|
|
255
|
+
});
|
|
256
|
+
prompts.promptForGeneration.mockResolvedValue(true);
|
|
257
|
+
});
|
|
258
|
+
test('handles generation errors gracefully', async () => {
|
|
259
|
+
llm.generateADRContent.mockResolvedValue({
|
|
260
|
+
result: null,
|
|
261
|
+
error: 'API rate limit exceeded'
|
|
262
|
+
});
|
|
263
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
264
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('generation failed'));
|
|
265
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('rate limit'));
|
|
266
|
+
expect(adr.saveADR).not.toHaveBeenCalled();
|
|
267
|
+
});
|
|
268
|
+
test('handles file save errors gracefully', async () => {
|
|
269
|
+
llm.generateADRContent.mockResolvedValue({
|
|
270
|
+
result: {
|
|
271
|
+
content: '# Use Kafka\n\n* Status: accepted',
|
|
272
|
+
title: 'Use Kafka',
|
|
273
|
+
timestamp: new Date().toISOString()
|
|
274
|
+
},
|
|
275
|
+
error: undefined
|
|
276
|
+
});
|
|
277
|
+
adr.saveADR.mockReturnValue({
|
|
278
|
+
success: false,
|
|
279
|
+
error: 'Permission denied'
|
|
280
|
+
});
|
|
281
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
282
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Failed to save'));
|
|
283
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Permission denied'));
|
|
284
|
+
});
|
|
285
|
+
test('continues workflow on generation error (fail-open)', async () => {
|
|
286
|
+
llm.generateADRContent.mockResolvedValue({
|
|
287
|
+
result: null,
|
|
288
|
+
error: 'Network error'
|
|
289
|
+
});
|
|
290
|
+
// Should complete without throwing
|
|
291
|
+
await expect((0, analysis_1.runAnalysis)(mockDiffOptions)).resolves.not.toThrow();
|
|
292
|
+
});
|
|
293
|
+
test('continues workflow on save error (fail-open)', async () => {
|
|
294
|
+
llm.generateADRContent.mockResolvedValue({
|
|
295
|
+
result: {
|
|
296
|
+
content: '# Decision\n\n* Status: accepted',
|
|
297
|
+
title: 'Decision',
|
|
298
|
+
timestamp: new Date().toISOString()
|
|
299
|
+
},
|
|
300
|
+
error: undefined
|
|
301
|
+
});
|
|
302
|
+
adr.saveADR.mockReturnValue({
|
|
303
|
+
success: false,
|
|
304
|
+
error: 'Disk full'
|
|
305
|
+
});
|
|
306
|
+
// Should complete without throwing
|
|
307
|
+
await expect((0, analysis_1.runAnalysis)(mockDiffOptions)).resolves.not.toThrow();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
describe('runAnalysis - complete workflow', () => {
|
|
311
|
+
test('completes full happy path workflow', async () => {
|
|
312
|
+
// Analysis detects significance
|
|
313
|
+
llm.analyzeChanges.mockResolvedValue({
|
|
314
|
+
result: {
|
|
315
|
+
is_significant: true,
|
|
316
|
+
reason: 'Introduces GraphQL API layer',
|
|
317
|
+
timestamp: new Date().toISOString()
|
|
318
|
+
},
|
|
319
|
+
error: undefined
|
|
320
|
+
});
|
|
321
|
+
// User confirms
|
|
322
|
+
prompts.promptForGeneration.mockResolvedValue(true);
|
|
323
|
+
// Generation succeeds
|
|
324
|
+
llm.generateADRContent.mockResolvedValue({
|
|
325
|
+
result: {
|
|
326
|
+
content: '# Use GraphQL\n\n* Status: accepted',
|
|
327
|
+
title: 'Use GraphQL',
|
|
328
|
+
timestamp: new Date().toISOString()
|
|
329
|
+
},
|
|
330
|
+
error: undefined
|
|
331
|
+
});
|
|
332
|
+
// Save succeeds
|
|
333
|
+
adr.saveADR.mockReturnValue({
|
|
334
|
+
success: true,
|
|
335
|
+
filePath: 'docs/adr/0001-use-graphql.md'
|
|
336
|
+
});
|
|
337
|
+
await (0, analysis_1.runAnalysis)(mockDiffOptions);
|
|
338
|
+
// Verify complete workflow executed
|
|
339
|
+
expect(config.loadConfig).toHaveBeenCalled();
|
|
340
|
+
expect(git.getChangedFiles).toHaveBeenCalled();
|
|
341
|
+
expect(git.getDiff).toHaveBeenCalled();
|
|
342
|
+
expect(llm.analyzeChanges).toHaveBeenCalled();
|
|
343
|
+
expect(prompts.promptForGeneration).toHaveBeenCalled();
|
|
344
|
+
expect(llm.generateADRContent).toHaveBeenCalled();
|
|
345
|
+
expect(adr.saveADR).toHaveBeenCalled();
|
|
346
|
+
// Verify success output
|
|
347
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Success'));
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
//# sourceMappingURL=analysis.test.js.map
|