cadr-cli 1.10.0 → 2.0.1
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/adr.d.ts +17 -0
- package/dist/adr/adr.d.ts.map +1 -0
- package/dist/{adr.js → adr/adr.js} +4 -44
- package/dist/adr/adr.js.map +1 -0
- package/dist/adr/adr.test.d.ts +5 -0
- package/dist/{adr.test.d.ts.map → adr/adr.test.d.ts.map} +1 -1
- package/dist/{adr.test.js → adr/adr.test.js} +0 -14
- package/dist/adr/adr.test.js.map +1 -0
- package/dist/adr/index.d.ts +2 -0
- package/dist/adr/index.d.ts.map +1 -0
- package/dist/adr/index.js +18 -0
- package/dist/adr/index.js.map +1 -0
- package/dist/analysis/analysis.orchestrator.d.ts +14 -0
- package/dist/analysis/analysis.orchestrator.d.ts.map +1 -0
- package/dist/analysis/analysis.orchestrator.js +175 -0
- package/dist/analysis/analysis.orchestrator.js.map +1 -0
- package/dist/analysis/strategies/git-strategy.d.ts +22 -0
- package/dist/analysis/strategies/git-strategy.d.ts.map +1 -0
- package/dist/analysis/strategies/git-strategy.js +114 -0
- package/dist/analysis/strategies/git-strategy.js.map +1 -0
- package/dist/commands/analyze.js +3 -3
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/status.d.ts +11 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +69 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/status.test.d.ts +2 -0
- package/dist/commands/status.test.d.ts.map +1 -0
- package/dist/commands/status.test.js +83 -0
- package/dist/commands/status.test.js.map +1 -0
- package/dist/git/git.errors.d.ts +6 -0
- package/dist/git/git.errors.d.ts.map +1 -0
- package/dist/git/git.errors.js +15 -0
- package/dist/git/git.errors.js.map +1 -0
- package/dist/git/git.operations.d.ts +12 -0
- package/dist/git/git.operations.d.ts.map +1 -0
- package/dist/git/git.operations.js +64 -0
- package/dist/git/git.operations.js.map +1 -0
- package/dist/git/index.d.ts +4 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +19 -0
- package/dist/git/index.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/index.d.ts +3 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +19 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/llm.d.ts +35 -0
- package/dist/llm/llm.d.ts.map +1 -0
- package/dist/{llm.js → llm/llm.js} +16 -58
- package/dist/llm/llm.js.map +1 -0
- package/dist/{prompts.d.ts → llm/prompts.d.ts} +1 -38
- package/dist/llm/prompts.d.ts.map +1 -0
- package/dist/{prompts.js → llm/prompts.js} +9 -54
- package/dist/llm/prompts.js.map +1 -0
- package/dist/llm/response-parser.d.ts +9 -0
- package/dist/llm/response-parser.d.ts.map +1 -0
- package/dist/llm/response-parser.js +67 -0
- package/dist/llm/response-parser.js.map +1 -0
- package/dist/presenters/console-presenter.d.ts +35 -0
- package/dist/presenters/console-presenter.d.ts.map +1 -0
- package/dist/presenters/console-presenter.js +114 -0
- package/dist/presenters/console-presenter.js.map +1 -0
- package/package.json +1 -1
- package/src/{adr.test.ts → adr/adr.test.ts} +10 -23
- package/src/{adr.ts → adr/adr.ts} +7 -48
- package/src/adr/index.ts +1 -0
- package/src/analysis/analysis.orchestrator.ts +175 -0
- package/src/analysis/strategies/git-strategy.ts +106 -0
- package/src/commands/analyze.ts +8 -9
- package/src/commands/status.test.ts +64 -0
- package/src/commands/status.ts +71 -0
- package/src/git/git.errors.ts +10 -0
- package/src/git/git.operations.ts +85 -0
- package/src/git/index.ts +3 -0
- package/src/index.ts +6 -0
- package/src/llm/index.ts +2 -0
- package/src/{llm.ts → llm/llm.ts} +46 -107
- package/src/{prompts.ts → llm/prompts.ts} +30 -72
- package/src/llm/response-parser.ts +90 -0
- package/src/presenters/console-presenter.ts +152 -0
- package/dist/adr.d.ts +0 -50
- package/dist/adr.d.ts.map +0 -1
- package/dist/adr.js.map +0 -1
- package/dist/adr.test.d.ts +0 -8
- package/dist/adr.test.js.map +0 -1
- package/dist/analysis.d.ts +0 -24
- package/dist/analysis.d.ts.map +0 -1
- package/dist/analysis.js +0 -281
- package/dist/analysis.js.map +0 -1
- package/dist/analysis.test.d.ts +0 -8
- package/dist/analysis.test.d.ts.map +0 -1
- package/dist/analysis.test.js +0 -351
- package/dist/analysis.test.js.map +0 -1
- package/dist/git.d.ts +0 -54
- package/dist/git.d.ts.map +0 -1
- package/dist/git.js +0 -204
- package/dist/git.js.map +0 -1
- package/dist/llm.d.ts +0 -73
- package/dist/llm.d.ts.map +0 -1
- package/dist/llm.js.map +0 -1
- package/dist/llm.test.d.ts +0 -2
- package/dist/llm.test.d.ts.map +0 -1
- package/dist/llm.test.js +0 -592
- package/dist/llm.test.js.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js.map +0 -1
- package/dist/prompts.test.d.ts +0 -2
- package/dist/prompts.test.d.ts.map +0 -1
- package/dist/prompts.test.js +0 -427
- package/dist/prompts.test.js.map +0 -1
- package/src/analysis.test.ts +0 -396
- package/src/analysis.ts +0 -262
- package/src/git.ts +0 -300
- package/src/llm.test.ts +0 -701
- package/src/prompts.test.ts +0 -515
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Command
|
|
3
|
+
*
|
|
4
|
+
* Provides information about the current cADR configuration and environment.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { loadConfig, getDefaultConfigPath } from '../config';
|
|
8
|
+
import { loggerInstance as logger } from '../logger';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Execute the status command
|
|
13
|
+
* Displays configuration and environment status
|
|
14
|
+
*/
|
|
15
|
+
export async function statusCommand(): Promise<void> {
|
|
16
|
+
try {
|
|
17
|
+
const configPath = getDefaultConfigPath();
|
|
18
|
+
const configExists = existsSync(configPath);
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
console.log('\n🔍 cADR Status');
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
console.log('='.repeat(30));
|
|
24
|
+
|
|
25
|
+
// Config Status
|
|
26
|
+
// eslint-disable-next-line no-console
|
|
27
|
+
console.log(`\n📄 Configuration:`);
|
|
28
|
+
if (configExists) {
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.log(` Path: ${configPath}`);
|
|
31
|
+
|
|
32
|
+
const config = await loadConfig(configPath);
|
|
33
|
+
if (config) {
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
console.log(` Provider: ${config.provider}`);
|
|
36
|
+
// eslint-disable-next-line no-console
|
|
37
|
+
console.log(` Model: ${config.analysis_model}`);
|
|
38
|
+
|
|
39
|
+
// API Key Check
|
|
40
|
+
const apiKeySet = !!process.env[config.api_key_env];
|
|
41
|
+
// eslint-disable-next-line no-console
|
|
42
|
+
console.log(` API Key (${config.api_key_env}): ${apiKeySet ? '✅ Set' : '❌ Not Set'}`);
|
|
43
|
+
} else {
|
|
44
|
+
// eslint-disable-next-line no-console
|
|
45
|
+
console.log(' ❌ Error loading configuration details.');
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
console.log(` Path: ${configPath} (❌ Not Found)`);
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.log(' Run `cadr init` to create one.');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Git Status (Basic check)
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.log(`\n🛠️ Environment:`);
|
|
57
|
+
// eslint-disable-next-line no-console
|
|
58
|
+
console.log(` OS: ${process.platform}`);
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.log(` Node: ${process.version}`);
|
|
61
|
+
|
|
62
|
+
// eslint-disable-next-line no-console
|
|
63
|
+
console.log('\nDone.\n');
|
|
64
|
+
|
|
65
|
+
logger.info('Status command completed');
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.error('Status command failed', { error });
|
|
68
|
+
// eslint-disable-next-line no-console
|
|
69
|
+
console.error('\n❌ Failed to retrieve status. Check logs for details.\n');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { GitError } from './git.errors';
|
|
4
|
+
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
|
|
7
|
+
export interface DiffOptions {
|
|
8
|
+
mode: 'staged' | 'all' | 'branch-diff';
|
|
9
|
+
base?: string;
|
|
10
|
+
head?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function handleGitError(error: unknown, operation: string): never {
|
|
14
|
+
const errorWithCode = error as { code?: number };
|
|
15
|
+
|
|
16
|
+
if (errorWithCode.code === 128) {
|
|
17
|
+
throw new GitError(
|
|
18
|
+
`Not in a Git repository. Please run 'cadr' from within a Git repository.`,
|
|
19
|
+
'NOT_GIT_REPO',
|
|
20
|
+
error instanceof Error ? error : new Error(String(error))
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (errorWithCode.code === 127) {
|
|
25
|
+
throw new GitError(
|
|
26
|
+
'Git is not installed. Please install Git and try again.',
|
|
27
|
+
'GIT_NOT_FOUND',
|
|
28
|
+
error instanceof Error ? error : new Error(String(error))
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
throw new GitError(
|
|
33
|
+
`Unable to ${operation}. Please check repository permissions.`,
|
|
34
|
+
'GIT_ERROR',
|
|
35
|
+
error instanceof Error ? error : new Error(String(error))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function execGitCommand(command: string): Promise<string> {
|
|
40
|
+
try {
|
|
41
|
+
const { stdout } = await execAsync(command);
|
|
42
|
+
return stdout;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
handleGitError(error, command.split(' ')[1] || 'execute git command');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseFileList(stdout: string): string[] {
|
|
49
|
+
return stdout
|
|
50
|
+
.split('\n')
|
|
51
|
+
.map((file) => file.trim())
|
|
52
|
+
.filter((file) => file.length > 0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getStagedFiles(): Promise<string[]> {
|
|
56
|
+
const stdout = await execGitCommand('git diff --cached --name-only');
|
|
57
|
+
return parseFileList(stdout);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function getStagedDiff(): Promise<string> {
|
|
61
|
+
return execGitCommand('git diff --cached --unified=1');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function getAllChanges(): Promise<string[]> {
|
|
65
|
+
const stdout = await execGitCommand('git diff HEAD --name-only');
|
|
66
|
+
return parseFileList(stdout);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function getAllDiff(): Promise<string> {
|
|
70
|
+
return execGitCommand('git diff HEAD --unified=1');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function getChangedFiles(options: DiffOptions): Promise<string[]> {
|
|
74
|
+
if (options.mode === 'staged') {
|
|
75
|
+
return getStagedFiles();
|
|
76
|
+
}
|
|
77
|
+
return getAllChanges();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function getDiff(options: DiffOptions): Promise<string> {
|
|
81
|
+
if (options.mode === 'staged') {
|
|
82
|
+
return getStagedDiff();
|
|
83
|
+
}
|
|
84
|
+
return getAllDiff();
|
|
85
|
+
}
|
package/src/git/index.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { initCommand } from './commands/init';
|
|
2
2
|
import { analyzeCommand } from './commands/analyze';
|
|
3
|
+
import { statusCommand } from './commands/status';
|
|
3
4
|
|
|
4
5
|
// Version constants
|
|
5
6
|
const CORE_VERSION = '0.0.1';
|
|
@@ -16,6 +17,7 @@ USAGE
|
|
|
16
17
|
COMMANDS
|
|
17
18
|
init Create a cadr.yaml configuration file
|
|
18
19
|
analyze Analyze code changes and generate ADRs (default)
|
|
20
|
+
status Show current configuration and environment status
|
|
19
21
|
help Show this help message
|
|
20
22
|
|
|
21
23
|
ANALYZE OPTIONS
|
|
@@ -85,6 +87,10 @@ if (require.main === module) {
|
|
|
85
87
|
case 'analyze':
|
|
86
88
|
await analyzeCommand(args);
|
|
87
89
|
break;
|
|
90
|
+
|
|
91
|
+
case 'status':
|
|
92
|
+
await statusCommand();
|
|
93
|
+
break;
|
|
88
94
|
|
|
89
95
|
default:
|
|
90
96
|
// Unknown command - show error and help
|
package/src/llm/index.ts
ADDED
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Implements fail-open error handling per constitution requirements.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { getProvider } from './providers';
|
|
9
|
-
import { AnalysisConfig } from './config';
|
|
10
|
-
import { loggerInstance as logger } from './logger';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Analysis request data structure
|
|
14
|
-
*/
|
|
1
|
+
import { getProvider } from '../providers';
|
|
2
|
+
import { AnalysisConfig } from '../config';
|
|
3
|
+
import { loggerInstance as logger } from '../logger';
|
|
4
|
+
|
|
15
5
|
export interface AnalysisRequest {
|
|
16
6
|
file_paths: string[];
|
|
17
7
|
diff_content: string;
|
|
@@ -19,17 +9,6 @@ export interface AnalysisRequest {
|
|
|
19
9
|
analysis_prompt: string;
|
|
20
10
|
}
|
|
21
11
|
|
|
22
|
-
/**
|
|
23
|
-
* Rough token estimation (1 token ≈ 4 characters for English text)
|
|
24
|
-
* This is a conservative estimate
|
|
25
|
-
*/
|
|
26
|
-
function estimateTokens(text: string): number {
|
|
27
|
-
return Math.ceil(text.length / 4);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Analysis result from LLM
|
|
32
|
-
*/
|
|
33
12
|
export interface AnalysisResult {
|
|
34
13
|
is_significant: boolean;
|
|
35
14
|
reason: string;
|
|
@@ -37,27 +16,38 @@ export interface AnalysisResult {
|
|
|
37
16
|
timestamp: string;
|
|
38
17
|
}
|
|
39
18
|
|
|
40
|
-
/**
|
|
41
|
-
* Analysis response including potential errors
|
|
42
|
-
*/
|
|
43
19
|
export interface AnalysisResponse {
|
|
44
20
|
result: AnalysisResult | null;
|
|
45
21
|
error?: string;
|
|
46
22
|
}
|
|
47
23
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
24
|
+
export interface GenerationRequest {
|
|
25
|
+
file_paths: string[];
|
|
26
|
+
diff_content: string;
|
|
27
|
+
reason: string;
|
|
28
|
+
generation_prompt: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface GenerationResult {
|
|
32
|
+
content: string;
|
|
33
|
+
title: string;
|
|
34
|
+
timestamp: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface GenerationResponse {
|
|
38
|
+
result: GenerationResult | null;
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function estimateTokens(text: string): number {
|
|
43
|
+
return Math.ceil(text.length / 4);
|
|
44
|
+
}
|
|
45
|
+
|
|
55
46
|
export async function analyzeChanges(
|
|
56
47
|
config: AnalysisConfig,
|
|
57
48
|
request: AnalysisRequest
|
|
58
49
|
): Promise<AnalysisResponse> {
|
|
59
50
|
try {
|
|
60
|
-
// Check if API key is available
|
|
61
51
|
const apiKey = process.env[config.api_key_env];
|
|
62
52
|
if (!apiKey) {
|
|
63
53
|
logger.warn('API key not found in environment', {
|
|
@@ -65,21 +55,19 @@ export async function analyzeChanges(
|
|
|
65
55
|
});
|
|
66
56
|
return {
|
|
67
57
|
result: null,
|
|
68
|
-
error: `API key not found: ${config.api_key_env} environment variable is not set
|
|
58
|
+
error: `API key not found: ${config.api_key_env} environment variable is not set`,
|
|
69
59
|
};
|
|
70
60
|
}
|
|
71
61
|
|
|
72
|
-
// Estimate tokens for logging and validation
|
|
73
62
|
const estimatedTokens = estimateTokens(request.analysis_prompt);
|
|
74
|
-
|
|
63
|
+
|
|
75
64
|
logger.info('Sending analysis request to LLM', {
|
|
76
65
|
provider: config.provider,
|
|
77
66
|
model: config.analysis_model,
|
|
78
67
|
file_count: request.file_paths.length,
|
|
79
68
|
estimated_tokens: estimatedTokens,
|
|
80
69
|
});
|
|
81
|
-
|
|
82
|
-
// Warn if token estimate is high (most models have 8k-32k limits)
|
|
70
|
+
|
|
83
71
|
if (estimatedTokens > 7000) {
|
|
84
72
|
logger.warn('High token count detected', {
|
|
85
73
|
estimated_tokens: estimatedTokens,
|
|
@@ -99,28 +87,24 @@ export async function analyzeChanges(
|
|
|
99
87
|
logger.warn('No response content from LLM', { provider: config.provider });
|
|
100
88
|
return {
|
|
101
89
|
result: null,
|
|
102
|
-
error: 'No response content from LLM'
|
|
90
|
+
error: 'No response content from LLM',
|
|
103
91
|
};
|
|
104
92
|
}
|
|
105
93
|
|
|
106
|
-
// Parse JSON response - handle markdown-wrapped JSON
|
|
107
94
|
let parsedResponse: { is_significant: boolean; reason: string; confidence?: number };
|
|
108
95
|
try {
|
|
109
|
-
// Try to extract JSON from markdown code blocks if present
|
|
110
96
|
let jsonContent = responseContent.trim();
|
|
111
|
-
|
|
112
|
-
// Remove markdown code block if present: ```json ... ``` or ``` ... ```
|
|
97
|
+
|
|
113
98
|
const codeBlockMatch = jsonContent.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
114
99
|
if (codeBlockMatch) {
|
|
115
100
|
jsonContent = codeBlockMatch[1].trim();
|
|
116
101
|
}
|
|
117
|
-
|
|
118
|
-
// Try to find JSON object if there's surrounding text
|
|
102
|
+
|
|
119
103
|
const jsonMatch = jsonContent.match(/\{[\s\S]*\}/);
|
|
120
104
|
if (jsonMatch) {
|
|
121
105
|
jsonContent = jsonMatch[0];
|
|
122
106
|
}
|
|
123
|
-
|
|
107
|
+
|
|
124
108
|
parsedResponse = JSON.parse(jsonContent);
|
|
125
109
|
} catch (parseError) {
|
|
126
110
|
logger.warn('Failed to parse LLM response as JSON', {
|
|
@@ -129,11 +113,10 @@ export async function analyzeChanges(
|
|
|
129
113
|
});
|
|
130
114
|
return {
|
|
131
115
|
result: null,
|
|
132
|
-
error: `Failed to parse LLM response as JSON. Response was:\n${responseContent.substring(0, 200)}
|
|
116
|
+
error: `Failed to parse LLM response as JSON. Response was:\n${responseContent.substring(0, 200)}...`,
|
|
133
117
|
};
|
|
134
118
|
}
|
|
135
119
|
|
|
136
|
-
// Validate response format
|
|
137
120
|
if (
|
|
138
121
|
typeof parsedResponse.is_significant !== 'boolean' ||
|
|
139
122
|
typeof parsedResponse.reason !== 'string'
|
|
@@ -143,29 +126,26 @@ export async function analyzeChanges(
|
|
|
143
126
|
});
|
|
144
127
|
return {
|
|
145
128
|
result: null,
|
|
146
|
-
error: `Invalid response format from LLM. Expected {is_significant: boolean, reason: string}, got: ${JSON.stringify(parsedResponse).substring(0, 150)}
|
|
129
|
+
error: `Invalid response format from LLM. Expected {is_significant: boolean, reason: string}, got: ${JSON.stringify(parsedResponse).substring(0, 150)}...`,
|
|
147
130
|
};
|
|
148
131
|
}
|
|
149
|
-
|
|
150
|
-
// Reason is required when is_significant is true, but can be empty when false
|
|
132
|
+
|
|
151
133
|
if (parsedResponse.is_significant && !parsedResponse.reason) {
|
|
152
134
|
logger.warn('Missing reason for significant change', {
|
|
153
135
|
response: parsedResponse,
|
|
154
136
|
});
|
|
155
137
|
return {
|
|
156
138
|
result: null,
|
|
157
|
-
error: 'LLM indicated significant change but provided no reason'
|
|
139
|
+
error: 'LLM indicated significant change but provided no reason',
|
|
158
140
|
};
|
|
159
141
|
}
|
|
160
142
|
|
|
161
|
-
// Build result with timestamp
|
|
162
143
|
const result: AnalysisResult = {
|
|
163
144
|
is_significant: parsedResponse.is_significant,
|
|
164
145
|
reason: parsedResponse.reason,
|
|
165
146
|
timestamp: new Date().toISOString(),
|
|
166
147
|
};
|
|
167
148
|
|
|
168
|
-
// Include confidence if provided
|
|
169
149
|
if (
|
|
170
150
|
typeof parsedResponse.confidence === 'number' &&
|
|
171
151
|
parsedResponse.confidence >= 0 &&
|
|
@@ -181,22 +161,20 @@ export async function analyzeChanges(
|
|
|
181
161
|
|
|
182
162
|
return { result, error: undefined };
|
|
183
163
|
} catch (error) {
|
|
184
|
-
// Fail-open: log error and return descriptive error message
|
|
185
164
|
const errorObj = error as { status?: number; code?: string; message?: string };
|
|
186
165
|
let errorMessage: string;
|
|
187
166
|
|
|
188
|
-
// Check for specific error types and provide helpful messages
|
|
189
167
|
if (errorObj.status === 401) {
|
|
190
168
|
errorMessage = 'Invalid API key - please check your API key configuration';
|
|
191
169
|
logger.warn('LLM API authentication failed', { error: errorObj });
|
|
192
170
|
} else if (errorObj.status === 400 && errorObj.message?.includes('maximum context length')) {
|
|
193
|
-
// Extract token counts from error message if available
|
|
194
171
|
const tokenMatch = errorObj.message.match(/(\d+)\s+tokens/g);
|
|
195
|
-
errorMessage =
|
|
172
|
+
errorMessage =
|
|
173
|
+
'Diff too large for model context window. Try:\n' +
|
|
196
174
|
' • Stage fewer files at once\n' +
|
|
197
175
|
' • Use a model with larger context window in cadr.yaml\n' +
|
|
198
176
|
' • Add ignore patterns to filter large files';
|
|
199
|
-
logger.warn('LLM context length exceeded', {
|
|
177
|
+
logger.warn('LLM context length exceeded', {
|
|
200
178
|
error: errorObj,
|
|
201
179
|
tokens: tokenMatch,
|
|
202
180
|
});
|
|
@@ -218,46 +196,11 @@ export async function analyzeChanges(
|
|
|
218
196
|
}
|
|
219
197
|
}
|
|
220
198
|
|
|
221
|
-
/**
|
|
222
|
-
* Generation request data structure
|
|
223
|
-
*/
|
|
224
|
-
export interface GenerationRequest {
|
|
225
|
-
file_paths: string[];
|
|
226
|
-
diff_content: string;
|
|
227
|
-
reason: string;
|
|
228
|
-
generation_prompt: string;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Generation result from LLM
|
|
233
|
-
*/
|
|
234
|
-
export interface GenerationResult {
|
|
235
|
-
content: string;
|
|
236
|
-
title: string;
|
|
237
|
-
timestamp: string;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Generation response including potential errors
|
|
242
|
-
*/
|
|
243
|
-
export interface GenerationResponse {
|
|
244
|
-
result: GenerationResult | null;
|
|
245
|
-
error?: string;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Generate ADR content using LLM
|
|
250
|
-
*
|
|
251
|
-
* @param config - Analysis configuration with API settings
|
|
252
|
-
* @param request - Generation request with code changes
|
|
253
|
-
* @returns Promise resolving to generation response with result or error
|
|
254
|
-
*/
|
|
255
199
|
export async function generateADRContent(
|
|
256
200
|
config: AnalysisConfig,
|
|
257
201
|
request: GenerationRequest
|
|
258
202
|
): Promise<GenerationResponse> {
|
|
259
203
|
try {
|
|
260
|
-
// Check if API key is available
|
|
261
204
|
const apiKey = process.env[config.api_key_env];
|
|
262
205
|
if (!apiKey) {
|
|
263
206
|
logger.warn('API key not found in environment for generation', {
|
|
@@ -265,7 +208,7 @@ export async function generateADRContent(
|
|
|
265
208
|
});
|
|
266
209
|
return {
|
|
267
210
|
result: null,
|
|
268
|
-
error: `API key not found: ${config.api_key_env} environment variable is not set
|
|
211
|
+
error: `API key not found: ${config.api_key_env} environment variable is not set`,
|
|
269
212
|
};
|
|
270
213
|
}
|
|
271
214
|
|
|
@@ -278,30 +221,27 @@ export async function generateADRContent(
|
|
|
278
221
|
const provider = getProvider(config.provider);
|
|
279
222
|
const responseContent = await provider.analyze(request.generation_prompt, {
|
|
280
223
|
apiKey,
|
|
281
|
-
model: config.analysis_model,
|
|
224
|
+
model: config.analysis_model,
|
|
282
225
|
timeoutMs: config.timeout_seconds * 1000,
|
|
283
226
|
});
|
|
284
227
|
|
|
285
228
|
if (!responseContent) {
|
|
286
|
-
logger.warn('No response content from LLM for generation', {
|
|
287
|
-
provider: config.provider
|
|
229
|
+
logger.warn('No response content from LLM for generation', {
|
|
230
|
+
provider: config.provider,
|
|
288
231
|
});
|
|
289
232
|
return {
|
|
290
233
|
result: null,
|
|
291
|
-
error: 'No response content from LLM'
|
|
234
|
+
error: 'No response content from LLM',
|
|
292
235
|
};
|
|
293
236
|
}
|
|
294
237
|
|
|
295
|
-
// Clean up the response - remove markdown code fences if LLM added them
|
|
296
238
|
let cleanedContent = responseContent.trim();
|
|
297
|
-
|
|
298
|
-
// Remove markdown code block if present
|
|
239
|
+
|
|
299
240
|
const codeBlockMatch = cleanedContent.match(/```(?:markdown|md)?\s*\n?([\s\S]*?)\n?```/);
|
|
300
241
|
if (codeBlockMatch) {
|
|
301
242
|
cleanedContent = codeBlockMatch[1].trim();
|
|
302
243
|
}
|
|
303
244
|
|
|
304
|
-
// Extract title from first line (should be # Title)
|
|
305
245
|
const titleMatch = cleanedContent.match(/^#\s+(.+)$/m);
|
|
306
246
|
const title = titleMatch ? titleMatch[1].trim() : 'Untitled Decision';
|
|
307
247
|
|
|
@@ -318,7 +258,6 @@ export async function generateADRContent(
|
|
|
318
258
|
|
|
319
259
|
return { result, error: undefined };
|
|
320
260
|
} catch (error) {
|
|
321
|
-
// Fail-open: log error and return descriptive error message
|
|
322
261
|
const errorObj = error as { status?: number; code?: string; message?: string };
|
|
323
262
|
let errorMessage: string;
|
|
324
263
|
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompts Module
|
|
3
|
-
*
|
|
4
|
-
* Contains versioned prompt templates for LLM analysis and ADR generation.
|
|
5
|
-
* Follows the constitution requirement for versioned prompts.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import * as readline from 'readline';
|
|
9
2
|
|
|
10
|
-
/**
|
|
11
|
-
* Version 1 of the analysis prompt template.
|
|
12
|
-
*
|
|
13
|
-
* This prompt is designed to analyze code changes for architectural significance.
|
|
14
|
-
* It uses specific criteria for determining significance and enforces strict JSON output.
|
|
15
|
-
*/
|
|
16
3
|
export const ANALYSIS_PROMPT_V1 = `
|
|
17
4
|
You are an expert principal engineer and software architect acting as a meticulous code reviewer. Your sole task is to determine if the provided git diff represents an architecturally significant change that warrants an Architectural Decision Record (ADR).
|
|
18
5
|
|
|
@@ -35,34 +22,6 @@ Respond ONLY with a single, minified JSON object with no preamble, no markdown,
|
|
|
35
22
|
The "reason" should be a concise, one-sentence explanation for your decision, suitable for showing to a developer. If the change is not significant, the reason should be an empty string.
|
|
36
23
|
`;
|
|
37
24
|
|
|
38
|
-
/**
|
|
39
|
-
* Formats a prompt template by replacing placeholders with actual data.
|
|
40
|
-
*
|
|
41
|
-
* @param template - The prompt template with placeholders
|
|
42
|
-
* @param data - Object containing file_paths and diff_content
|
|
43
|
-
* @returns Formatted prompt with placeholders replaced
|
|
44
|
-
*/
|
|
45
|
-
export function formatPrompt(
|
|
46
|
-
template: string,
|
|
47
|
-
data: { file_paths: string[], diff_content: string }
|
|
48
|
-
): string {
|
|
49
|
-
// Format file paths as a readable list
|
|
50
|
-
const formattedFilePaths = data.file_paths.length > 0
|
|
51
|
-
? data.file_paths.join('\n')
|
|
52
|
-
: 'No files';
|
|
53
|
-
|
|
54
|
-
// Replace placeholders with actual data
|
|
55
|
-
return template
|
|
56
|
-
.replace('{file_paths}', formattedFilePaths)
|
|
57
|
-
.replace('{diff_content}', data.diff_content);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Version 1 of the generation prompt template.
|
|
62
|
-
*
|
|
63
|
-
* This prompt generates ADRs following the MADR (Markdown Architectural Decision Records) format.
|
|
64
|
-
* MADR is a lean template for documenting architectural decisions in a structured way.
|
|
65
|
-
*/
|
|
66
25
|
export const GENERATION_PROMPT_V1 = `
|
|
67
26
|
You are an expert software architect. Your task is to write a comprehensive Architectural Decision Record (ADR) following the MADR (Markdown Architectural Decision Records) template.
|
|
68
27
|
|
|
@@ -123,33 +82,30 @@ IMPORTANT INSTRUCTIONS:
|
|
|
123
82
|
Respond ONLY with the markdown content of the ADR. Do not include any preamble, explanation, or markdown code fences. Start directly with the # title.
|
|
124
83
|
`;
|
|
125
84
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
* @param data - Object containing file_paths and diff_content
|
|
130
|
-
* @returns Formatted generation prompt
|
|
131
|
-
*/
|
|
132
|
-
export function formatGenerationPrompt(
|
|
133
|
-
data: { file_paths: string[], diff_content: string }
|
|
85
|
+
export function formatPrompt(
|
|
86
|
+
template: string,
|
|
87
|
+
data: { file_paths: string[]; diff_content: string }
|
|
134
88
|
): string {
|
|
135
|
-
const formattedFilePaths = data.file_paths.length > 0
|
|
136
|
-
|
|
137
|
-
|
|
89
|
+
const formattedFilePaths = data.file_paths.length > 0 ? data.file_paths.join('\n') : 'No files';
|
|
90
|
+
|
|
91
|
+
return template
|
|
92
|
+
.replace('{file_paths}', formattedFilePaths)
|
|
93
|
+
.replace('{diff_content}', data.diff_content);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function formatGenerationPrompt(data: {
|
|
97
|
+
file_paths: string[];
|
|
98
|
+
diff_content: string;
|
|
99
|
+
}): string {
|
|
100
|
+
const formattedFilePaths = data.file_paths.length > 0 ? data.file_paths.join('\n') : 'No files';
|
|
138
101
|
|
|
139
102
|
const currentDate = new Date().toISOString().split('T')[0];
|
|
140
103
|
|
|
141
|
-
return GENERATION_PROMPT_V1
|
|
142
|
-
.replace('{file_paths}', formattedFilePaths)
|
|
104
|
+
return GENERATION_PROMPT_V1.replace('{file_paths}', formattedFilePaths)
|
|
143
105
|
.replace('{diff_content}', data.diff_content)
|
|
144
106
|
.replace('{current_date}', currentDate);
|
|
145
107
|
}
|
|
146
108
|
|
|
147
|
-
/**
|
|
148
|
-
* Prompt user for ADR generation confirmation
|
|
149
|
-
*
|
|
150
|
-
* @param reason - The reason why this change is architecturally significant
|
|
151
|
-
* @returns Promise<boolean> - true if user wants to generate, false otherwise
|
|
152
|
-
*/
|
|
153
109
|
export async function promptForGeneration(reason: string): Promise<boolean> {
|
|
154
110
|
return new Promise((resolve) => {
|
|
155
111
|
const rl = readline.createInterface({
|
|
@@ -157,18 +113,20 @@ export async function promptForGeneration(reason: string): Promise<boolean> {
|
|
|
157
113
|
output: process.stdout,
|
|
158
114
|
});
|
|
159
115
|
|
|
160
|
-
|
|
116
|
+
/* eslint-disable-next-line no-console */
|
|
161
117
|
console.log(`\n💭 ${reason}\n`);
|
|
162
|
-
|
|
163
|
-
rl.question(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
118
|
+
|
|
119
|
+
rl.question(
|
|
120
|
+
'📝 Would you like to generate an ADR for this change? (Press ENTER or type "yes" to confirm, "no" to skip): ',
|
|
121
|
+
(answer) => {
|
|
122
|
+
rl.close();
|
|
123
|
+
|
|
124
|
+
const normalized = answer.trim().toLowerCase();
|
|
125
|
+
|
|
126
|
+
const confirmed = normalized === '' || normalized === 'y' || normalized === 'yes';
|
|
127
|
+
|
|
128
|
+
resolve(confirmed);
|
|
129
|
+
}
|
|
130
|
+
);
|
|
173
131
|
});
|
|
174
132
|
}
|