cadr-cli 2.0.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 (104) 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/git/git.errors.d.ts +6 -0
  24. package/dist/git/git.errors.d.ts.map +1 -0
  25. package/dist/git/git.errors.js +15 -0
  26. package/dist/git/git.errors.js.map +1 -0
  27. package/dist/git/git.operations.d.ts +12 -0
  28. package/dist/git/git.operations.d.ts.map +1 -0
  29. package/dist/git/git.operations.js +64 -0
  30. package/dist/git/git.operations.js.map +1 -0
  31. package/dist/git/index.d.ts +4 -0
  32. package/dist/git/index.d.ts.map +1 -0
  33. package/dist/git/index.js +19 -0
  34. package/dist/git/index.js.map +1 -0
  35. package/dist/llm/index.d.ts +3 -0
  36. package/dist/llm/index.d.ts.map +1 -0
  37. package/dist/llm/index.js +19 -0
  38. package/dist/llm/index.js.map +1 -0
  39. package/dist/llm/llm.d.ts +35 -0
  40. package/dist/llm/llm.d.ts.map +1 -0
  41. package/dist/{llm.js → llm/llm.js} +16 -58
  42. package/dist/llm/llm.js.map +1 -0
  43. package/dist/{prompts.d.ts → llm/prompts.d.ts} +1 -38
  44. package/dist/llm/prompts.d.ts.map +1 -0
  45. package/dist/{prompts.js → llm/prompts.js} +9 -54
  46. package/dist/llm/prompts.js.map +1 -0
  47. package/dist/llm/response-parser.d.ts +9 -0
  48. package/dist/llm/response-parser.d.ts.map +1 -0
  49. package/dist/llm/response-parser.js +67 -0
  50. package/dist/llm/response-parser.js.map +1 -0
  51. package/dist/presenters/console-presenter.d.ts +35 -0
  52. package/dist/presenters/console-presenter.d.ts.map +1 -0
  53. package/dist/presenters/console-presenter.js +114 -0
  54. package/dist/presenters/console-presenter.js.map +1 -0
  55. package/package.json +1 -1
  56. package/src/{adr.test.ts → adr/adr.test.ts} +10 -23
  57. package/src/{adr.ts → adr/adr.ts} +7 -48
  58. package/src/adr/index.ts +1 -0
  59. package/src/analysis/analysis.orchestrator.ts +175 -0
  60. package/src/analysis/strategies/git-strategy.ts +106 -0
  61. package/src/commands/analyze.ts +8 -9
  62. package/src/git/git.errors.ts +10 -0
  63. package/src/git/git.operations.ts +85 -0
  64. package/src/git/index.ts +3 -0
  65. package/src/llm/index.ts +2 -0
  66. package/src/{llm.ts → llm/llm.ts} +46 -107
  67. package/src/{prompts.ts → llm/prompts.ts} +30 -72
  68. package/src/llm/response-parser.ts +90 -0
  69. package/src/presenters/console-presenter.ts +152 -0
  70. package/dist/adr.d.ts +0 -50
  71. package/dist/adr.d.ts.map +0 -1
  72. package/dist/adr.js.map +0 -1
  73. package/dist/adr.test.d.ts +0 -8
  74. package/dist/adr.test.js.map +0 -1
  75. package/dist/analysis.d.ts +0 -24
  76. package/dist/analysis.d.ts.map +0 -1
  77. package/dist/analysis.js +0 -281
  78. package/dist/analysis.js.map +0 -1
  79. package/dist/analysis.test.d.ts +0 -8
  80. package/dist/analysis.test.d.ts.map +0 -1
  81. package/dist/analysis.test.js +0 -351
  82. package/dist/analysis.test.js.map +0 -1
  83. package/dist/git.d.ts +0 -54
  84. package/dist/git.d.ts.map +0 -1
  85. package/dist/git.js +0 -204
  86. package/dist/git.js.map +0 -1
  87. package/dist/llm.d.ts +0 -73
  88. package/dist/llm.d.ts.map +0 -1
  89. package/dist/llm.js.map +0 -1
  90. package/dist/llm.test.d.ts +0 -2
  91. package/dist/llm.test.d.ts.map +0 -1
  92. package/dist/llm.test.js +0 -592
  93. package/dist/llm.test.js.map +0 -1
  94. package/dist/prompts.d.ts.map +0 -1
  95. package/dist/prompts.js.map +0 -1
  96. package/dist/prompts.test.d.ts +0 -2
  97. package/dist/prompts.test.d.ts.map +0 -1
  98. package/dist/prompts.test.js +0 -427
  99. package/dist/prompts.test.js.map +0 -1
  100. package/src/analysis.test.ts +0 -396
  101. package/src/analysis.ts +0 -262
  102. package/src/git.ts +0 -300
  103. package/src/llm.test.ts +0 -701
  104. package/src/prompts.test.ts +0 -515
@@ -0,0 +1,175 @@
1
+ /* eslint-disable no-console */
2
+ import { loadConfig, getDefaultConfigPath } from '../config';
3
+ import type { DiffOptions } from '../git/index';
4
+ import {
5
+ formatPrompt,
6
+ ANALYSIS_PROMPT_V1,
7
+ formatGenerationPrompt,
8
+ promptForGeneration,
9
+ } from '../llm/prompts';
10
+ import { analyzeChanges, generateADRContent } from '../llm/llm';
11
+ import { loggerInstance as logger } from '../logger';
12
+ import { saveADR } from '../adr/adr';
13
+ import * as path from 'path';
14
+ import { createGitStrategy, type GitStrategy } from './strategies/git-strategy';
15
+ import { presenter, type AnalysisSummary } from '../presenters/console-presenter';
16
+
17
+ export interface AnalysisResult {
18
+ is_significant: boolean;
19
+ reason: string;
20
+ confidence?: number;
21
+ timestamp: string;
22
+ }
23
+
24
+ export interface GenerationResult {
25
+ content: string;
26
+ title: string;
27
+ timestamp: string;
28
+ }
29
+
30
+ async function runAnalysisInternal(diffOptions: DiffOptions): Promise<void> {
31
+ const configPath = getDefaultConfigPath();
32
+ const config = await loadConfig(configPath);
33
+
34
+ if (!config) {
35
+ presenter.showConfigError();
36
+ return;
37
+ }
38
+
39
+ const gitStrategy: GitStrategy = createGitStrategy(diffOptions);
40
+
41
+ let changedFiles: string[];
42
+ try {
43
+ changedFiles = await gitStrategy.getFiles();
44
+ } catch (error) {
45
+ const gitError = error as { name?: string; message?: string };
46
+ if (gitError.name === 'GitError') {
47
+ presenter.showGitError(gitError.message || 'Unknown Git error');
48
+ } else {
49
+ presenter.showReadFilesError();
50
+ }
51
+ logger.error('Failed to get changed files', { error, mode: diffOptions.mode });
52
+ return;
53
+ }
54
+
55
+ if (changedFiles.length === 0) {
56
+ presenter.showNoChanges(diffOptions);
57
+ return;
58
+ }
59
+
60
+ presenter.showAnalyzingFiles(changedFiles, diffOptions);
61
+
62
+ let diffContent: string;
63
+ try {
64
+ diffContent = await gitStrategy.getDiff();
65
+ } catch (error) {
66
+ presenter.showReadFilesError();
67
+ logger.error('Failed to get diff', { error, mode: diffOptions.mode });
68
+ return;
69
+ }
70
+
71
+ if (!diffContent || diffContent.trim().length === 0) {
72
+ presenter.showNoDiffContent();
73
+ return;
74
+ }
75
+
76
+ const repositoryContext = path.basename(process.cwd());
77
+ const prompt = formatPrompt(ANALYSIS_PROMPT_V1, {
78
+ file_paths: changedFiles,
79
+ diff_content: diffContent,
80
+ });
81
+
82
+ presenter.showSendingToLLM(diffOptions, config.provider, config.analysis_model);
83
+
84
+ const response = await analyzeChanges(config, {
85
+ file_paths: changedFiles,
86
+ diff_content: diffContent,
87
+ repository_context: repositoryContext,
88
+ analysis_prompt: prompt,
89
+ });
90
+
91
+ if (!response.result || response.error) {
92
+ presenter.showAnalysisFailed(response.error);
93
+ return;
94
+ }
95
+
96
+ const result = response.result;
97
+
98
+ presenter.showAnalysisComplete();
99
+
100
+ if (result.is_significant) {
101
+ const summary: AnalysisSummary = {
102
+ fileCount: changedFiles.length,
103
+ mode: diffOptions.mode,
104
+ isSignificant: true,
105
+ reason: result.reason,
106
+ confidence: result.confidence,
107
+ };
108
+ presenter.showSignificantResult(summary);
109
+
110
+ const shouldGenerate = await promptForGeneration(result.reason);
111
+
112
+ if (shouldGenerate) {
113
+ presenter.showGeneratingADR();
114
+
115
+ const generationPrompt = formatGenerationPrompt({
116
+ file_paths: changedFiles,
117
+ diff_content: diffContent,
118
+ });
119
+
120
+ const generationResponse = await generateADRContent(config, {
121
+ file_paths: changedFiles,
122
+ diff_content: diffContent,
123
+ reason: result.reason,
124
+ generation_prompt: generationPrompt,
125
+ });
126
+
127
+ if (!generationResponse.result || generationResponse.error) {
128
+ presenter.showGenerationFailed(generationResponse.error);
129
+ logger.error('ADR generation failed', { error: generationResponse.error });
130
+ } else {
131
+ const saveResult = saveADR(
132
+ generationResponse.result.content,
133
+ generationResponse.result.title
134
+ );
135
+
136
+ if (saveResult.success && saveResult.filePath) {
137
+ presenter.showADRSuccess(saveResult.filePath);
138
+ logger.info('ADR generation workflow completed successfully', {
139
+ filePath: saveResult.filePath,
140
+ title: generationResponse.result.title,
141
+ });
142
+ } else {
143
+ presenter.showADRSaveError(saveResult.error);
144
+ logger.error('Failed to save ADR', { error: saveResult.error });
145
+ }
146
+ }
147
+ } else {
148
+ presenter.showSkippingGeneration();
149
+ }
150
+ } else {
151
+ const summary: AnalysisSummary = {
152
+ fileCount: changedFiles.length,
153
+ mode: diffOptions.mode,
154
+ isSignificant: false,
155
+ reason: result.reason,
156
+ confidence: result.confidence,
157
+ };
158
+ presenter.showNotSignificantResult(summary);
159
+ }
160
+
161
+ logger.info('Analysis workflow completed successfully', {
162
+ is_significant: result.is_significant,
163
+ file_count: changedFiles.length,
164
+ });
165
+ }
166
+
167
+ export async function runAnalysis(diffOptions: DiffOptions = { mode: 'all' }): Promise<void> {
168
+ try {
169
+ logger.info('Starting analysis workflow');
170
+ await runAnalysisInternal(diffOptions);
171
+ } catch (error) {
172
+ logger.error('Unexpected error in analysis workflow', { error });
173
+ presenter.showUnexpectedError();
174
+ }
175
+ }
@@ -0,0 +1,106 @@
1
+ import {
2
+ getStagedFiles,
3
+ getStagedDiff,
4
+ getAllChanges,
5
+ getAllDiff,
6
+ type DiffOptions,
7
+ } from '../../git/git.operations';
8
+ import { GitError } from '../../git';
9
+
10
+ export interface GitStrategy {
11
+ getFiles(): Promise<string[]>;
12
+ getDiff(): Promise<string>;
13
+ }
14
+
15
+ export class StagedChangesStrategy implements GitStrategy {
16
+ async getFiles(): Promise<string[]> {
17
+ return getStagedFiles();
18
+ }
19
+
20
+ async getDiff(): Promise<string> {
21
+ return getStagedDiff();
22
+ }
23
+ }
24
+
25
+ export class AllChangesStrategy implements GitStrategy {
26
+ async getFiles(): Promise<string[]> {
27
+ return getAllChanges();
28
+ }
29
+
30
+ async getDiff(): Promise<string> {
31
+ return getAllDiff();
32
+ }
33
+ }
34
+
35
+ export class BranchDiffStrategy implements GitStrategy {
36
+ constructor(
37
+ private base: string,
38
+ private head: string
39
+ ) {}
40
+
41
+ async getFiles(): Promise<string[]> {
42
+ const { exec } = await import('child_process');
43
+ const { promisify } = await import('util');
44
+ const execAsync = promisify(exec);
45
+
46
+ try {
47
+ const { stdout } = await execAsync(`git diff --name-only ${this.base}...${this.head}`);
48
+ return stdout
49
+ .split('\n')
50
+ .map((f) => f.trim())
51
+ .filter((f) => f.length > 0);
52
+ } catch (error) {
53
+ const errorWithCode = error as { code?: number };
54
+ if (errorWithCode.code === 128) {
55
+ throw new GitError(
56
+ `Invalid git reference: ${this.base} or ${this.head}. Please ensure both references exist.`,
57
+ 'GIT_ERROR',
58
+ error instanceof Error ? error : new Error(String(error))
59
+ );
60
+ }
61
+ throw new GitError(
62
+ 'Unable to read Git repository. Please check repository permissions.',
63
+ 'GIT_ERROR',
64
+ error instanceof Error ? error : new Error(String(error))
65
+ );
66
+ }
67
+ }
68
+
69
+ async getDiff(): Promise<string> {
70
+ const { exec } = await import('child_process');
71
+ const { promisify } = await import('util');
72
+ const execAsync = promisify(exec);
73
+
74
+ try {
75
+ const { stdout } = await execAsync(`git diff ${this.base}...${this.head} --unified=1`);
76
+ return stdout;
77
+ } catch (error) {
78
+ const errorWithCode = error as { code?: number };
79
+ if (errorWithCode.code === 128) {
80
+ throw new GitError(
81
+ `Invalid git reference: ${this.base} or ${this.head}. Please ensure both references exist.`,
82
+ 'GIT_ERROR',
83
+ error instanceof Error ? error : new Error(String(error))
84
+ );
85
+ }
86
+ throw new GitError(
87
+ 'Unable to read Git repository. Please check repository permissions.',
88
+ 'GIT_ERROR',
89
+ error instanceof Error ? error : new Error(String(error))
90
+ );
91
+ }
92
+ }
93
+ }
94
+
95
+ export function createGitStrategy(options: DiffOptions): GitStrategy {
96
+ switch (options.mode) {
97
+ case 'staged':
98
+ return new StagedChangesStrategy();
99
+ case 'all':
100
+ return new AllChangesStrategy();
101
+ case 'branch-diff':
102
+ return new BranchDiffStrategy(options.base || 'origin/main', options.head || 'HEAD');
103
+ default:
104
+ return new AllChangesStrategy();
105
+ }
106
+ }
@@ -1,31 +1,31 @@
1
1
  /**
2
2
  * Analyze Command
3
- *
3
+ *
4
4
  * Triggers LLM-powered analysis of code changes.
5
5
  * Thin wrapper around analysis orchestration module.
6
6
  */
7
7
 
8
- import { runAnalysis } from '../analysis';
8
+ import { runAnalysis } from '../analysis/analysis.orchestrator';
9
9
  import { DiffOptions } from '../git';
10
10
  import { loggerInstance as logger } from '../logger';
11
11
 
12
12
  /**
13
13
  * Execute the analyze command
14
14
  * Analyzes code changes for architectural significance
15
- *
15
+ *
16
16
  * @param args - Command line arguments (e.g., ['--staged'], ['--all'])
17
17
  */
18
18
  export async function analyzeCommand(args: string[] = []): Promise<void> {
19
19
  try {
20
20
  // Parse command line flags to determine diff options
21
21
  const diffOptions: DiffOptions = { mode: 'all' }; // Default to all uncommitted
22
-
22
+
23
23
  // Check for --base flag (implies branch-diff mode)
24
24
  const baseIndex = args.indexOf('--base');
25
25
  if (baseIndex !== -1 && baseIndex + 1 < args.length) {
26
26
  diffOptions.mode = 'branch-diff';
27
27
  diffOptions.base = args[baseIndex + 1];
28
-
28
+
29
29
  // Check for optional --head flag
30
30
  const headIndex = args.indexOf('--head');
31
31
  if (headIndex !== -1 && headIndex + 1 < args.length) {
@@ -36,11 +36,11 @@ export async function analyzeCommand(args: string[] = []): Promise<void> {
36
36
  } else if (args.includes('--all')) {
37
37
  diffOptions.mode = 'all';
38
38
  }
39
-
40
- logger.info('Analyze command started', {
39
+
40
+ logger.info('Analyze command started', {
41
41
  mode: diffOptions.mode,
42
42
  base: diffOptions.base,
43
- head: diffOptions.head
43
+ head: diffOptions.head,
44
44
  });
45
45
  await runAnalysis(diffOptions);
46
46
  logger.info('Analyze command completed');
@@ -53,4 +53,3 @@ export async function analyzeCommand(args: string[] = []): Promise<void> {
53
53
  console.error('Please check the logs for more details.\n');
54
54
  }
55
55
  }
56
-
@@ -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';
@@ -0,0 +1,2 @@
1
+ export * from './llm';
2
+ export * from './prompts';