codereview-aia 0.1.0

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 (153) hide show
  1. package/.cr-aia.yml +23 -0
  2. package/.crignore +0 -0
  3. package/dist/index.js +27 -0
  4. package/package.json +85 -0
  5. package/src/analysis/FindingsExtractor.ts +431 -0
  6. package/src/analysis/ai-detection/analyzers/BaseAnalyzer.ts +267 -0
  7. package/src/analysis/ai-detection/analyzers/DocumentationAnalyzer.ts +622 -0
  8. package/src/analysis/ai-detection/analyzers/GitHistoryAnalyzer.ts +430 -0
  9. package/src/analysis/ai-detection/core/AIDetectionEngine.ts +467 -0
  10. package/src/analysis/ai-detection/types/DetectionTypes.ts +406 -0
  11. package/src/analysis/ai-detection/utils/SubmissionConverter.ts +390 -0
  12. package/src/analysis/context/ReviewContext.ts +378 -0
  13. package/src/analysis/context/index.ts +7 -0
  14. package/src/analysis/index.ts +8 -0
  15. package/src/analysis/tokens/TokenAnalysisFormatter.ts +154 -0
  16. package/src/analysis/tokens/TokenAnalyzer.ts +747 -0
  17. package/src/analysis/tokens/index.ts +8 -0
  18. package/src/clients/base/abstractClient.ts +190 -0
  19. package/src/clients/base/httpClient.ts +160 -0
  20. package/src/clients/base/index.ts +12 -0
  21. package/src/clients/base/modelDetection.ts +107 -0
  22. package/src/clients/base/responseProcessor.ts +586 -0
  23. package/src/clients/factory/clientFactory.ts +55 -0
  24. package/src/clients/factory/index.ts +8 -0
  25. package/src/clients/implementations/index.ts +8 -0
  26. package/src/clients/implementations/openRouterClient.ts +411 -0
  27. package/src/clients/openRouterClient.ts +863 -0
  28. package/src/clients/openRouterClientWrapper.ts +44 -0
  29. package/src/clients/utils/directoryStructure.ts +52 -0
  30. package/src/clients/utils/index.ts +11 -0
  31. package/src/clients/utils/languageDetection.ts +44 -0
  32. package/src/clients/utils/promptFormatter.ts +105 -0
  33. package/src/clients/utils/promptLoader.ts +53 -0
  34. package/src/clients/utils/tokenCounter.ts +297 -0
  35. package/src/core/ApiClientSelector.ts +37 -0
  36. package/src/core/ConfigurationService.ts +591 -0
  37. package/src/core/ConsolidationService.ts +423 -0
  38. package/src/core/InteractiveDisplayManager.ts +81 -0
  39. package/src/core/OutputManager.ts +275 -0
  40. package/src/core/ReviewGenerator.ts +140 -0
  41. package/src/core/fileDiscovery.ts +237 -0
  42. package/src/core/handlers/EstimationHandler.ts +104 -0
  43. package/src/core/handlers/FileProcessingHandler.ts +204 -0
  44. package/src/core/handlers/OutputHandler.ts +125 -0
  45. package/src/core/handlers/ReviewExecutor.ts +104 -0
  46. package/src/core/reviewOrchestrator.ts +333 -0
  47. package/src/core/utils/ModelInfoUtils.ts +56 -0
  48. package/src/formatters/outputFormatter.ts +62 -0
  49. package/src/formatters/utils/IssueFormatters.ts +83 -0
  50. package/src/formatters/utils/JsonFormatter.ts +77 -0
  51. package/src/formatters/utils/MarkdownFormatters.ts +609 -0
  52. package/src/formatters/utils/MetadataFormatter.ts +269 -0
  53. package/src/formatters/utils/ModelInfoExtractor.ts +115 -0
  54. package/src/index.ts +27 -0
  55. package/src/plugins/PluginInterface.ts +50 -0
  56. package/src/plugins/PluginManager.ts +126 -0
  57. package/src/prompts/PromptManager.ts +69 -0
  58. package/src/prompts/cache/PromptCache.ts +50 -0
  59. package/src/prompts/promptText/common/variables/css-frameworks.json +33 -0
  60. package/src/prompts/promptText/common/variables/framework-versions.json +45 -0
  61. package/src/prompts/promptText/frameworks/react/comprehensive.hbs +19 -0
  62. package/src/prompts/promptText/languages/css/comprehensive.hbs +18 -0
  63. package/src/prompts/promptText/languages/generic/comprehensive.hbs +20 -0
  64. package/src/prompts/promptText/languages/html/comprehensive.hbs +18 -0
  65. package/src/prompts/promptText/languages/javascript/comprehensive.hbs +18 -0
  66. package/src/prompts/promptText/languages/python/comprehensive.hbs +18 -0
  67. package/src/prompts/promptText/languages/typescript/comprehensive.hbs +18 -0
  68. package/src/runtime/auth/service.ts +58 -0
  69. package/src/runtime/auth/session.ts +103 -0
  70. package/src/runtime/auth/types.ts +11 -0
  71. package/src/runtime/cliEntry.ts +196 -0
  72. package/src/runtime/errors.ts +13 -0
  73. package/src/runtime/fileCollector.ts +188 -0
  74. package/src/runtime/manifest.ts +64 -0
  75. package/src/runtime/openrouterProxy.ts +45 -0
  76. package/src/runtime/proxyConfig.ts +94 -0
  77. package/src/runtime/proxyEnvironment.ts +71 -0
  78. package/src/runtime/reportMerge.ts +102 -0
  79. package/src/runtime/reporting/markdownReportBuilder.ts +138 -0
  80. package/src/runtime/reporting/reportDataCollector.ts +234 -0
  81. package/src/runtime/reporting/summaryGenerator.ts +86 -0
  82. package/src/runtime/reviewPipeline.ts +155 -0
  83. package/src/runtime/runAiCodeReview.ts +153 -0
  84. package/src/runtime/runtimeConfig.ts +5 -0
  85. package/src/runtime/ui/Layout.tsx +57 -0
  86. package/src/runtime/ui/RuntimeApp.tsx +150 -0
  87. package/src/runtime/ui/inkModules.ts +73 -0
  88. package/src/runtime/ui/screens/AuthScreen.tsx +128 -0
  89. package/src/runtime/ui/screens/ModeSelection.tsx +52 -0
  90. package/src/runtime/ui/screens/ProgressScreen.tsx +55 -0
  91. package/src/runtime/ui/screens/ResultsScreen.tsx +76 -0
  92. package/src/strategies/ArchitecturalReviewStrategy.ts +54 -0
  93. package/src/strategies/CodingTestReviewStrategy.ts +920 -0
  94. package/src/strategies/ConsolidatedReviewStrategy.ts +59 -0
  95. package/src/strategies/ExtractPatternsReviewStrategy.ts +64 -0
  96. package/src/strategies/MultiPassReviewStrategy.ts +785 -0
  97. package/src/strategies/ReviewStrategy.ts +64 -0
  98. package/src/strategies/StrategyFactory.ts +79 -0
  99. package/src/strategies/index.ts +14 -0
  100. package/src/tokenizers/baseTokenizer.ts +61 -0
  101. package/src/tokenizers/gptTokenizer.ts +27 -0
  102. package/src/tokenizers/index.ts +8 -0
  103. package/src/types/apiResponses.ts +40 -0
  104. package/src/types/cli.ts +24 -0
  105. package/src/types/common.ts +39 -0
  106. package/src/types/configuration.ts +201 -0
  107. package/src/types/handlebars.d.ts +5 -0
  108. package/src/types/patch.d.ts +25 -0
  109. package/src/types/review.ts +294 -0
  110. package/src/types/reviewContext.d.ts +65 -0
  111. package/src/types/reviewSchema.ts +181 -0
  112. package/src/types/structuredReview.ts +167 -0
  113. package/src/types/tokenAnalysis.ts +56 -0
  114. package/src/utils/FileReader.ts +93 -0
  115. package/src/utils/FileWriter.ts +76 -0
  116. package/src/utils/PathGenerator.ts +97 -0
  117. package/src/utils/api/apiUtils.ts +14 -0
  118. package/src/utils/api/index.ts +1 -0
  119. package/src/utils/apiErrorHandler.ts +287 -0
  120. package/src/utils/ciDataCollector.ts +252 -0
  121. package/src/utils/codingTestConfigLoader.ts +466 -0
  122. package/src/utils/dependencies/aiDependencyAnalyzer.ts +454 -0
  123. package/src/utils/detection/frameworkDetector.ts +879 -0
  124. package/src/utils/detection/index.ts +10 -0
  125. package/src/utils/detection/projectTypeDetector.ts +518 -0
  126. package/src/utils/diagramGenerator.ts +206 -0
  127. package/src/utils/errorLogger.ts +60 -0
  128. package/src/utils/estimationUtils.ts +407 -0
  129. package/src/utils/fileFilters.ts +373 -0
  130. package/src/utils/fileSystem.ts +57 -0
  131. package/src/utils/index.ts +36 -0
  132. package/src/utils/logger.ts +240 -0
  133. package/src/utils/pathValidator.ts +98 -0
  134. package/src/utils/priorityFilter.ts +59 -0
  135. package/src/utils/projectDocs.ts +189 -0
  136. package/src/utils/promptPaths.ts +29 -0
  137. package/src/utils/promptTemplateManager.ts +157 -0
  138. package/src/utils/review/consolidateReview.ts +553 -0
  139. package/src/utils/review/fixDisplay.ts +100 -0
  140. package/src/utils/review/fixImplementation.ts +61 -0
  141. package/src/utils/review/index.ts +36 -0
  142. package/src/utils/review/interactiveProcessing.ts +294 -0
  143. package/src/utils/review/progressTracker.ts +296 -0
  144. package/src/utils/review/reviewExtraction.ts +382 -0
  145. package/src/utils/review/types.ts +46 -0
  146. package/src/utils/reviewActionHandler.ts +18 -0
  147. package/src/utils/reviewParser.ts +253 -0
  148. package/src/utils/sanitizer.ts +238 -0
  149. package/src/utils/smartFileSelector.ts +255 -0
  150. package/src/utils/templateLoader.ts +514 -0
  151. package/src/utils/treeGenerator.ts +153 -0
  152. package/tsconfig.build.json +14 -0
  153. package/tsconfig.json +59 -0
@@ -0,0 +1,150 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { LayoutProvider, useLayout } from './Layout';
3
+ import { AuthScreen } from './screens/AuthScreen';
4
+ import { ModeSelection } from './screens/ModeSelection';
5
+ import { ProgressScreen } from './screens/ProgressScreen';
6
+ import { ResultsScreen } from './screens/ResultsScreen';
7
+ import { getInk, getInkSpinner } from './inkModules';
8
+ import type { ReviewResult, ReviewStage } from '../reviewPipeline';
9
+ import { executeReview } from '../reviewPipeline';
10
+ import { loadSession, getSessionToken } from '../auth/session';
11
+ import type { SessionUser } from '../auth/types';
12
+ import { RUNTIME_CONFIG } from '../runtimeConfig';
13
+ import { MissingCrIgnoreError } from '../errors';
14
+
15
+ type ScreenState = 'auth' | 'mode' | 'progress' | 'results';
16
+
17
+ interface RuntimeAppProps {
18
+ debug?: boolean;
19
+ }
20
+
21
+ interface ProgressState {
22
+ stage: ReviewStage;
23
+ filesFound?: number;
24
+ }
25
+
26
+ const defaultProgress: ProgressState = { stage: 'collecting' };
27
+
28
+ function AppBody({ debug = false }: RuntimeAppProps) {
29
+ const { Box, Text, useApp, useInput } = getInk();
30
+ const { exit } = useApp();
31
+ const layout = useLayout();
32
+ const [screen, setScreen] = useState<ScreenState>('auth');
33
+ const [user, setUser] = useState<SessionUser | null>(null);
34
+ const [progress, setProgress] = useState<ProgressState>(defaultProgress);
35
+ const [results, setResults] = useState<ReviewResult | null>(null);
36
+ type ErrorInfo = { type: 'generic' | 'crignore'; message: string; hint?: string };
37
+ const [error, setError] = useState<ErrorInfo | null>(null);
38
+
39
+ useEffect(() => {
40
+ const sessionUser = loadSession();
41
+ const token = getSessionToken();
42
+ if (token) {
43
+ process.env.CR_AIA_PROXY_SESSION_TOKEN = token;
44
+ }
45
+ if (sessionUser) {
46
+ setUser(sessionUser);
47
+ setScreen('mode');
48
+ }
49
+ }, []);
50
+
51
+ useInput(
52
+ useCallback(
53
+ (input, key) => {
54
+ if (key.ctrl && input === 'c') {
55
+ exit();
56
+ }
57
+
58
+ if (screen === 'results' && key.return) {
59
+ setScreen('mode');
60
+ }
61
+ },
62
+ [exit, screen],
63
+ ),
64
+ );
65
+
66
+ const handleAuth = useCallback((nextUser: SessionUser) => {
67
+ setUser(nextUser);
68
+ setScreen('mode');
69
+ }, []);
70
+
71
+ const handleRunReview = useCallback(async (_mode: string) => {
72
+ setScreen('progress');
73
+ setResults(null);
74
+ setError(null);
75
+ setProgress(defaultProgress);
76
+
77
+ try {
78
+ const reviewResult = await executeReview({
79
+ model: process.env.AI_CODE_REVIEW_MODEL || RUNTIME_CONFIG.DEFAULT_MODEL,
80
+ outDir: './reports',
81
+ debug,
82
+ onStage: (stage, info) => setProgress({ stage, filesFound: info?.filesFound }),
83
+ });
84
+
85
+ setResults(reviewResult);
86
+ setScreen('results');
87
+ } catch (reviewError) {
88
+ if (reviewError instanceof MissingCrIgnoreError) {
89
+ setError({
90
+ type: 'crignore',
91
+ message: 'cr-aia needs a .crignore file in this repo.',
92
+ hint: `Create ${reviewError.crIgnorePath} (can be empty, gitignore syntax) and rerun.`,
93
+ });
94
+ } else {
95
+ const message =
96
+ reviewError instanceof Error
97
+ ? reviewError.message
98
+ : 'Failed to run review. Check proxy/auth configuration.';
99
+ setError({ type: 'generic', message });
100
+ }
101
+ setScreen('mode');
102
+ }
103
+ }, [debug]);
104
+
105
+ return (
106
+ <Box width={layout.frameWidth} flexDirection="column" gap={1}>
107
+ {screen === 'auth' && <AuthScreen onAuth={handleAuth} />}
108
+ {screen === 'mode' && user && <ModeSelection onSelect={(mode) => handleRunReview(mode)} />}
109
+ {screen === 'progress' && <ProgressScreen stage={progress.stage} filesFound={progress.filesFound} />}
110
+ {screen === 'results' && results && <ResultsScreen result={results} />}
111
+ {error && (
112
+ <Box
113
+ borderStyle="round"
114
+ borderColor={error.type === 'crignore' ? 'yellow' : 'red'}
115
+ paddingX={1}
116
+ paddingY={1}
117
+ >
118
+ <Text color={error.type === 'crignore' ? 'yellow' : 'red'}>
119
+ {error.message}
120
+ {error.hint ? `\n${error.hint}` : ''}
121
+ </Text>
122
+ </Box>
123
+ )}
124
+ <Box justifyContent="space-between">
125
+ <Text dimColor>{user ? `Logged in as ${user.username}` : 'Authenticate to start reviewing'}</Text>
126
+ <Text dimColor>Ctrl+C to exit</Text>
127
+ </Box>
128
+ </Box>
129
+ );
130
+ }
131
+
132
+ export function RuntimeApp(props: RuntimeAppProps) {
133
+ return (
134
+ <LayoutProvider>
135
+ <AppBody {...props} />
136
+ </LayoutProvider>
137
+ );
138
+ }
139
+
140
+ export function LoadingScreen() {
141
+ const { Box, Text } = getInk();
142
+ const Spinner = getInkSpinner();
143
+ return (
144
+ <Box flexDirection="column" paddingX={1} paddingY={1}>
145
+ <Text>
146
+ <Spinner type="dots" /> Preparing runtime environment...
147
+ </Text>
148
+ </Box>
149
+ );
150
+ }
@@ -0,0 +1,73 @@
1
+ type InkModule = typeof import('ink');
2
+ type SpinnerModule = typeof import('ink-spinner');
3
+ type TextInputModule = typeof import('ink-text-input');
4
+ type SelectInputModule = typeof import('ink-select-input');
5
+
6
+ const dynamicImporter = new Function('specifier', 'return import(specifier);') as (
7
+ specifier: string,
8
+ ) => Promise<unknown>;
9
+
10
+ let inkModule: InkModule | null = null;
11
+ let spinnerModule: SpinnerModule['default'] | null = null;
12
+ let textInputModule: TextInputModule['default'] | null = null;
13
+ let selectInputModule: SelectInputModule['default'] | null = null;
14
+
15
+ async function loadInk(): Promise<void> {
16
+ if (!inkModule) {
17
+ inkModule = (await dynamicImporter('ink')) as InkModule;
18
+ }
19
+ }
20
+
21
+ async function loadSpinner(): Promise<void> {
22
+ if (!spinnerModule) {
23
+ const mod = (await dynamicImporter('ink-spinner')) as SpinnerModule;
24
+ spinnerModule = mod.default;
25
+ }
26
+ }
27
+
28
+ async function loadTextInput(): Promise<void> {
29
+ if (!textInputModule) {
30
+ const mod = (await dynamicImporter('ink-text-input')) as TextInputModule;
31
+ textInputModule = mod.default;
32
+ }
33
+ }
34
+
35
+ async function loadSelectInput(): Promise<void> {
36
+ if (!selectInputModule) {
37
+ const mod = (await dynamicImporter('ink-select-input')) as SelectInputModule;
38
+ selectInputModule = mod.default;
39
+ }
40
+ }
41
+
42
+ export async function preloadInkModules(): Promise<void> {
43
+ await Promise.all([loadInk(), loadSpinner(), loadTextInput(), loadSelectInput()]);
44
+ }
45
+
46
+ export function getInk(): InkModule {
47
+ if (!inkModule) {
48
+ throw new Error('Ink module not loaded. Call preloadInkModules() before rendering UI.');
49
+ }
50
+ return inkModule;
51
+ }
52
+
53
+ export function getInkSpinner(): SpinnerModule['default'] {
54
+ if (!spinnerModule) {
55
+ throw new Error('Ink spinner module not loaded. Call preloadInkModules() before rendering UI.');
56
+ }
57
+ return spinnerModule;
58
+ }
59
+
60
+ export function getInkTextInput(): TextInputModule['default'] {
61
+ if (!textInputModule) {
62
+ throw new Error('Ink TextInput module not loaded. Call preloadInkModules() before rendering UI.');
63
+ }
64
+ return textInputModule;
65
+ }
66
+
67
+ export function getInkSelectInput(): SelectInputModule['default'] {
68
+ if (!selectInputModule) {
69
+ throw new Error('Ink SelectInput module not loaded. Call preloadInkModules() before rendering UI.');
70
+ }
71
+ return selectInputModule;
72
+ }
73
+
@@ -0,0 +1,128 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import type { SessionUser } from '../../auth/types';
3
+ import { authenticate } from '../../auth/service';
4
+ import { saveSession } from '../../auth/session';
5
+ import { useLayout } from '../Layout';
6
+ import { getInk, getInkSpinner, getInkTextInput } from '../inkModules';
7
+
8
+ interface AuthScreenProps {
9
+ onAuth: (user: SessionUser) => void;
10
+ }
11
+
12
+ export function AuthScreen({ onAuth }: AuthScreenProps) {
13
+ const { frameWidth } = useLayout();
14
+ const { Box, Text, useInput } = getInk();
15
+ const Spinner = getInkSpinner();
16
+ const TextInput = getInkTextInput();
17
+ const [username, setUsername] = useState('');
18
+ const [password, setPassword] = useState('');
19
+ const [error, setError] = useState<string | null>(null);
20
+ const [submitting, setSubmitting] = useState(false);
21
+ const [step, setStep] = useState<'username' | 'password'>('username');
22
+
23
+ const handleSubmit = useCallback(async () => {
24
+ if (submitting) return;
25
+
26
+ if (!username.trim() || !password.trim()) {
27
+ setError('Username and password are required');
28
+ return;
29
+ }
30
+
31
+ try {
32
+ setSubmitting(true);
33
+ setError(null);
34
+ const result = await authenticate(username.trim(), password.trim());
35
+ saveSession(result.token);
36
+ process.env.CR_AIA_PROXY_SESSION_TOKEN = result.token;
37
+ onAuth(result.user);
38
+ } catch (authError) {
39
+ const message = authError instanceof Error ? authError.message : 'Authentication failed';
40
+ setError(message);
41
+ setSubmitting(false);
42
+ }
43
+ }, [username, password, submitting, onAuth]);
44
+
45
+ const handleAdvance = useCallback(() => {
46
+ if (step === 'password') {
47
+ return;
48
+ }
49
+ if (!username.trim()) {
50
+ setError('Username is required');
51
+ return;
52
+ }
53
+ setError(null);
54
+ setStep('password');
55
+ }, [step, username]);
56
+
57
+ useInput((input, key) => {
58
+ if (key.return) {
59
+ if (step === 'username') {
60
+ handleAdvance();
61
+ } else {
62
+ handleSubmit();
63
+ }
64
+ }
65
+ });
66
+
67
+ useEffect(() => {
68
+ setError(null);
69
+ }, [username, password]);
70
+
71
+ return (
72
+ <Box
73
+ width={frameWidth}
74
+ flexDirection="column"
75
+ borderStyle="round"
76
+ borderColor="cyan"
77
+ paddingX={2}
78
+ paddingY={1}
79
+ gap={1}
80
+ >
81
+ <Text color="cyan" bold>
82
+ Sign in
83
+ </Text>
84
+ <Text dimColor>
85
+ {step === 'username' ? 'Type your username, press Enter.' : 'Type your password, press Enter to submit.'}
86
+ </Text>
87
+
88
+ <Box flexDirection="column" gap={1}>
89
+ <Box flexDirection="column">
90
+ <Text dimColor>Username</Text>
91
+ <TextInput
92
+ value={username}
93
+ onChange={setUsername}
94
+ placeholder="username"
95
+ focus={step === 'username' && !submitting}
96
+ />
97
+ </Box>
98
+
99
+ <Box flexDirection="column">
100
+ <Text dimColor>Password</Text>
101
+ <TextInput
102
+ value={password}
103
+ onChange={setPassword}
104
+ mask="*"
105
+ placeholder={step === 'password' ? 'password' : 'Press Enter after username'}
106
+ focus={step === 'password' && !submitting}
107
+ showCursor={step === 'password'}
108
+ />
109
+ </Box>
110
+ </Box>
111
+
112
+ {submitting && (
113
+ <Box>
114
+ <Text>
115
+ <Spinner type="dots" /> Authenticating…
116
+ </Text>
117
+ </Box>
118
+ )}
119
+
120
+ {error && (
121
+ <Box>
122
+ <Text color="red">{error}</Text>
123
+ </Box>
124
+ )}
125
+ </Box>
126
+ );
127
+ }
128
+
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import { getInk, getInkSelectInput } from '../inkModules';
3
+ import { useLayout } from '../Layout';
4
+
5
+ interface ModeSelectionProps {
6
+ onSelect: (mode: string) => void;
7
+ }
8
+
9
+ const OPTIONS = [
10
+ {
11
+ label: 'Review uncommitted changes',
12
+ value: 'uncommitted',
13
+ description: 'Scan staged and unstaged changes only',
14
+ },
15
+ ];
16
+
17
+ export function ModeSelection({ onSelect }: ModeSelectionProps) {
18
+ const { Box, Text } = getInk();
19
+ const SelectInput = getInkSelectInput();
20
+ const { frameWidth } = useLayout();
21
+
22
+ return (
23
+ <Box
24
+ width={frameWidth}
25
+ flexDirection="column"
26
+ borderStyle="round"
27
+ borderColor="green"
28
+ paddingX={2}
29
+ paddingY={1}
30
+ gap={1}
31
+ >
32
+ <Text color="green" bold>
33
+ Choose review mode
34
+ </Text>
35
+ <Text dimColor>Use arrow keys, press Enter to start.</Text>
36
+ <SelectInput
37
+ items={OPTIONS.map((option) => ({
38
+ label: option.label,
39
+ value: option.value,
40
+ }))}
41
+ onSelect={(item) => onSelect(String(item.value))}
42
+ />
43
+ <Box flexDirection="column" marginTop={1}>
44
+ {OPTIONS.map((option) => (
45
+ <Text key={option.value} dimColor>
46
+ · {option.description}
47
+ </Text>
48
+ ))}
49
+ </Box>
50
+ </Box>
51
+ );
52
+ }
@@ -0,0 +1,55 @@
1
+ import React, { useMemo } from 'react';
2
+ import { useLayout } from '../Layout';
3
+ import type { ReviewStage } from '../../reviewPipeline';
4
+ import { getInk, getInkSpinner } from '../inkModules';
5
+
6
+ interface ProgressScreenProps {
7
+ stage: ReviewStage;
8
+ filesFound?: number;
9
+ }
10
+
11
+ const STAGE_TEXT: Record<ReviewStage, { title: string; detail: string }> = {
12
+ collecting: {
13
+ title: 'Collecting files',
14
+ detail: 'Inspecting your working tree',
15
+ },
16
+ reviewing: {
17
+ title: 'Running AI review',
18
+ detail: 'Streaming code to the reviewer',
19
+ },
20
+ merging: {
21
+ title: 'Merging reports',
22
+ detail: 'Normalizing findings and cost data',
23
+ },
24
+ };
25
+
26
+ export function ProgressScreen({ stage, filesFound }: ProgressScreenProps) {
27
+ const { Box, Text } = getInk();
28
+ const Spinner = getInkSpinner();
29
+ const { frameWidth } = useLayout();
30
+
31
+ const meta = useMemo(() => STAGE_TEXT[stage], [stage]);
32
+ const fileStatus =
33
+ typeof filesFound === 'number' && filesFound >= 0 ? `${filesFound} files queued` : 'Preparing file list…';
34
+
35
+ return (
36
+ <Box
37
+ width={frameWidth}
38
+ flexDirection="column"
39
+ borderStyle="round"
40
+ borderColor="cyan"
41
+ paddingX={2}
42
+ paddingY={1}
43
+ gap={1}
44
+ >
45
+ <Box gap={1}>
46
+ <Text>
47
+ <Spinner type="line" /> {meta.title}
48
+ </Text>
49
+ </Box>
50
+ <Text dimColor>{meta.detail}</Text>
51
+ <Text dimColor>{fileStatus}</Text>
52
+ </Box>
53
+ );
54
+ }
55
+
@@ -0,0 +1,76 @@
1
+ import React, { useMemo } from 'react';
2
+ import type { ReviewResult } from '../../reviewPipeline';
3
+ import { useLayout } from '../Layout';
4
+ import { getInk } from '../inkModules';
5
+
6
+ interface ResultsScreenProps {
7
+ result: ReviewResult;
8
+ }
9
+
10
+ export function ResultsScreen({ result }: ResultsScreenProps) {
11
+ const { Box, Text } = getInk();
12
+ const { frameWidth } = useLayout();
13
+
14
+ const severityRows = useMemo(() => {
15
+ const totals = result.totals || { critical: 0, high: 0, medium: 0, low: 0 };
16
+ return [
17
+ { label: 'Critical', value: totals.critical ?? 0 },
18
+ { label: 'High', value: totals.high ?? 0 },
19
+ { label: 'Medium', value: totals.medium ?? 0 },
20
+ { label: 'Low', value: totals.low ?? 0 },
21
+ ];
22
+ }, [result.totals]);
23
+
24
+ return (
25
+ <Box
26
+ width={frameWidth}
27
+ flexDirection="column"
28
+ borderStyle="round"
29
+ borderColor="green"
30
+ paddingX={2}
31
+ paddingY={1}
32
+ gap={1}
33
+ >
34
+ <Text color="green" bold>
35
+ Review complete for {result.repo}
36
+ </Text>
37
+ <Text dimColor>Duration: {result.duration}s — Files reviewed: {result.filesReviewed}</Text>
38
+
39
+ <Box flexDirection="column" marginTop={1}>
40
+ {severityRows.map((row) => (
41
+ <Box key={row.label}>
42
+ <Text>
43
+ {row.label}: {row.value}
44
+ </Text>
45
+ </Box>
46
+ ))}
47
+ </Box>
48
+
49
+ {result.findings.length === 0 ? (
50
+ <Text dimColor>No findings reported.</Text>
51
+ ) : (
52
+ <Box flexDirection="column" marginTop={1}>
53
+ <Text bold>Sample findings</Text>
54
+ {result.findings.slice(0, 5).map((finding, index) => (
55
+ <Text key={`${finding.filePath}-${index}`} dimColor>
56
+ · {finding.filePath ?? finding.file ?? 'unknown'} {finding.priority ? `(${finding.priority})` : ''}
57
+ </Text>
58
+ ))}
59
+ {result.findings.length > 5 && (
60
+ <Text dimColor>…and {result.findings.length - 5} more in the saved report.</Text>
61
+ )}
62
+ </Box>
63
+ )}
64
+
65
+ {result.reportPath && (
66
+ <Box marginTop={1}>
67
+ <Text dimColor>Report saved to: {result.reportPath}</Text>
68
+ </Box>
69
+ )}
70
+
71
+ <Box marginTop={1}>
72
+ <Text dimColor>Press Enter to return to mode selection.</Text>
73
+ </Box>
74
+ </Box>
75
+ );
76
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @fileoverview Architectural review strategy implementation.
3
+ *
4
+ * This module implements the architectural review strategy, which analyzes the entire
5
+ * codebase structure and design patterns to provide high-level feedback.
6
+ */
7
+
8
+ import type { ApiClientConfig } from '../core/ApiClientSelector';
9
+ import { generateReview } from '../core/ReviewGenerator';
10
+ import type { FileInfo, ReviewOptions, ReviewResult /* , ReviewType */ } from '../types/review'; // ReviewType not used
11
+ import logger from '../utils/logger';
12
+ import type { ProjectDocs } from '../utils/projectDocs';
13
+ import { BaseReviewStrategy } from './ReviewStrategy';
14
+
15
+ /**
16
+ * Strategy for architectural reviews
17
+ */
18
+ export class ArchitecturalReviewStrategy extends BaseReviewStrategy {
19
+ /**
20
+ * Create a new architectural review strategy
21
+ */
22
+ constructor() {
23
+ super('architectural');
24
+ }
25
+
26
+ /**
27
+ * Execute the architectural review strategy
28
+ * @param files Files to review
29
+ * @param projectName Project name
30
+ * @param projectDocs Project documentation
31
+ * @param options Review options
32
+ * @param apiClientConfig API client configuration
33
+ * @returns Promise resolving to the review result
34
+ */
35
+ async execute(
36
+ files: FileInfo[],
37
+ projectName: string,
38
+ projectDocs: ProjectDocs | null,
39
+ options: ReviewOptions,
40
+ apiClientConfig: ApiClientConfig,
41
+ ): Promise<ReviewResult> {
42
+ logger.info('Executing architectural review strategy...');
43
+
44
+ // Generate the review using the selected API client
45
+ return generateReview(
46
+ files,
47
+ projectName,
48
+ this.reviewType,
49
+ projectDocs,
50
+ options,
51
+ apiClientConfig,
52
+ );
53
+ }
54
+ }