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.
Files changed (118) hide show
  1. package/dist/adr/adr.d.ts +17 -0
  2. package/dist/adr/adr.d.ts.map +1 -0
  3. package/dist/{adr.js → adr/adr.js} +4 -44
  4. package/dist/adr/adr.js.map +1 -0
  5. package/dist/adr/adr.test.d.ts +5 -0
  6. package/dist/{adr.test.d.ts.map → adr/adr.test.d.ts.map} +1 -1
  7. package/dist/{adr.test.js → adr/adr.test.js} +0 -14
  8. package/dist/adr/adr.test.js.map +1 -0
  9. package/dist/adr/index.d.ts +2 -0
  10. package/dist/adr/index.d.ts.map +1 -0
  11. package/dist/adr/index.js +18 -0
  12. package/dist/adr/index.js.map +1 -0
  13. package/dist/analysis/analysis.orchestrator.d.ts +14 -0
  14. package/dist/analysis/analysis.orchestrator.d.ts.map +1 -0
  15. package/dist/analysis/analysis.orchestrator.js +175 -0
  16. package/dist/analysis/analysis.orchestrator.js.map +1 -0
  17. package/dist/analysis/strategies/git-strategy.d.ts +22 -0
  18. package/dist/analysis/strategies/git-strategy.d.ts.map +1 -0
  19. package/dist/analysis/strategies/git-strategy.js +114 -0
  20. package/dist/analysis/strategies/git-strategy.js.map +1 -0
  21. package/dist/commands/analyze.js +3 -3
  22. package/dist/commands/analyze.js.map +1 -1
  23. package/dist/commands/status.d.ts +11 -0
  24. package/dist/commands/status.d.ts.map +1 -0
  25. package/dist/commands/status.js +69 -0
  26. package/dist/commands/status.js.map +1 -0
  27. package/dist/commands/status.test.d.ts +2 -0
  28. package/dist/commands/status.test.d.ts.map +1 -0
  29. package/dist/commands/status.test.js +83 -0
  30. package/dist/commands/status.test.js.map +1 -0
  31. package/dist/git/git.errors.d.ts +6 -0
  32. package/dist/git/git.errors.d.ts.map +1 -0
  33. package/dist/git/git.errors.js +15 -0
  34. package/dist/git/git.errors.js.map +1 -0
  35. package/dist/git/git.operations.d.ts +12 -0
  36. package/dist/git/git.operations.d.ts.map +1 -0
  37. package/dist/git/git.operations.js +64 -0
  38. package/dist/git/git.operations.js.map +1 -0
  39. package/dist/git/index.d.ts +4 -0
  40. package/dist/git/index.d.ts.map +1 -0
  41. package/dist/git/index.js +19 -0
  42. package/dist/git/index.js.map +1 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +5 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/llm/index.d.ts +3 -0
  47. package/dist/llm/index.d.ts.map +1 -0
  48. package/dist/llm/index.js +19 -0
  49. package/dist/llm/index.js.map +1 -0
  50. package/dist/llm/llm.d.ts +35 -0
  51. package/dist/llm/llm.d.ts.map +1 -0
  52. package/dist/{llm.js → llm/llm.js} +16 -58
  53. package/dist/llm/llm.js.map +1 -0
  54. package/dist/{prompts.d.ts → llm/prompts.d.ts} +1 -38
  55. package/dist/llm/prompts.d.ts.map +1 -0
  56. package/dist/{prompts.js → llm/prompts.js} +9 -54
  57. package/dist/llm/prompts.js.map +1 -0
  58. package/dist/llm/response-parser.d.ts +9 -0
  59. package/dist/llm/response-parser.d.ts.map +1 -0
  60. package/dist/llm/response-parser.js +67 -0
  61. package/dist/llm/response-parser.js.map +1 -0
  62. package/dist/presenters/console-presenter.d.ts +35 -0
  63. package/dist/presenters/console-presenter.d.ts.map +1 -0
  64. package/dist/presenters/console-presenter.js +114 -0
  65. package/dist/presenters/console-presenter.js.map +1 -0
  66. package/package.json +1 -1
  67. package/src/{adr.test.ts → adr/adr.test.ts} +10 -23
  68. package/src/{adr.ts → adr/adr.ts} +7 -48
  69. package/src/adr/index.ts +1 -0
  70. package/src/analysis/analysis.orchestrator.ts +175 -0
  71. package/src/analysis/strategies/git-strategy.ts +106 -0
  72. package/src/commands/analyze.ts +8 -9
  73. package/src/commands/status.test.ts +64 -0
  74. package/src/commands/status.ts +71 -0
  75. package/src/git/git.errors.ts +10 -0
  76. package/src/git/git.operations.ts +85 -0
  77. package/src/git/index.ts +3 -0
  78. package/src/index.ts +6 -0
  79. package/src/llm/index.ts +2 -0
  80. package/src/{llm.ts → llm/llm.ts} +46 -107
  81. package/src/{prompts.ts → llm/prompts.ts} +30 -72
  82. package/src/llm/response-parser.ts +90 -0
  83. package/src/presenters/console-presenter.ts +152 -0
  84. package/dist/adr.d.ts +0 -50
  85. package/dist/adr.d.ts.map +0 -1
  86. package/dist/adr.js.map +0 -1
  87. package/dist/adr.test.d.ts +0 -8
  88. package/dist/adr.test.js.map +0 -1
  89. package/dist/analysis.d.ts +0 -24
  90. package/dist/analysis.d.ts.map +0 -1
  91. package/dist/analysis.js +0 -281
  92. package/dist/analysis.js.map +0 -1
  93. package/dist/analysis.test.d.ts +0 -8
  94. package/dist/analysis.test.d.ts.map +0 -1
  95. package/dist/analysis.test.js +0 -351
  96. package/dist/analysis.test.js.map +0 -1
  97. package/dist/git.d.ts +0 -54
  98. package/dist/git.d.ts.map +0 -1
  99. package/dist/git.js +0 -204
  100. package/dist/git.js.map +0 -1
  101. package/dist/llm.d.ts +0 -73
  102. package/dist/llm.d.ts.map +0 -1
  103. package/dist/llm.js.map +0 -1
  104. package/dist/llm.test.d.ts +0 -2
  105. package/dist/llm.test.d.ts.map +0 -1
  106. package/dist/llm.test.js +0 -592
  107. package/dist/llm.test.js.map +0 -1
  108. package/dist/prompts.d.ts.map +0 -1
  109. package/dist/prompts.js.map +0 -1
  110. package/dist/prompts.test.d.ts +0 -2
  111. package/dist/prompts.test.d.ts.map +0 -1
  112. package/dist/prompts.test.js +0 -427
  113. package/dist/prompts.test.js.map +0 -1
  114. package/src/analysis.test.ts +0 -396
  115. package/src/analysis.ts +0 -262
  116. package/src/git.ts +0 -300
  117. package/src/llm.test.ts +0 -701
  118. 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,10 @@
1
+ export class GitError extends Error {
2
+ constructor(
3
+ message: string,
4
+ public readonly code: 'NOT_GIT_REPO' | 'GIT_NOT_FOUND' | 'GIT_ERROR',
5
+ public readonly originalError?: Error
6
+ ) {
7
+ super(message);
8
+ this.name = 'GitError';
9
+ }
10
+ }
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ export * from './git.errors';
2
+ export * from './git.operations';
3
+ export type { DiffOptions } from './git.operations';
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
@@ -0,0 +1,2 @@
1
+ export * from './llm';
2
+ export * from './prompts';
@@ -1,17 +1,7 @@
1
- /**
2
- * LLM Client Module
3
- *
4
- * Provider-based wrapper for analyzing code changes.
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
- * Analyze staged changes using OpenAI LLM
50
- *
51
- * @param config - Analysis configuration with API settings
52
- * @param request - Analysis request with code changes
53
- * @returns Promise resolving to analysis response with result or error
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 = 'Diff too large for model context window. Try:\n' +
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, // Using same model per user request
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
- * Formats the generation prompt with actual diff data
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
- ? data.file_paths.join('\n')
137
- : 'No files';
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
- // eslint-disable-next-line no-console
116
+ /* eslint-disable-next-line no-console */
161
117
  console.log(`\n💭 ${reason}\n`);
162
-
163
- rl.question('📝 Would you like to generate an ADR for this change? (Press ENTER or type "yes" to confirm, "no" to skip): ', (answer) => {
164
- rl.close();
165
-
166
- const normalized = answer.trim().toLowerCase();
167
-
168
- // Accept: empty (ENTER), "y", "yes"
169
- const confirmed = normalized === '' || normalized === 'y' || normalized === 'yes';
170
-
171
- resolve(confirmed);
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
  }