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.
- package/.cr-aia.yml +23 -0
- package/.crignore +0 -0
- package/dist/index.js +27 -0
- package/package.json +85 -0
- package/src/analysis/FindingsExtractor.ts +431 -0
- package/src/analysis/ai-detection/analyzers/BaseAnalyzer.ts +267 -0
- package/src/analysis/ai-detection/analyzers/DocumentationAnalyzer.ts +622 -0
- package/src/analysis/ai-detection/analyzers/GitHistoryAnalyzer.ts +430 -0
- package/src/analysis/ai-detection/core/AIDetectionEngine.ts +467 -0
- package/src/analysis/ai-detection/types/DetectionTypes.ts +406 -0
- package/src/analysis/ai-detection/utils/SubmissionConverter.ts +390 -0
- package/src/analysis/context/ReviewContext.ts +378 -0
- package/src/analysis/context/index.ts +7 -0
- package/src/analysis/index.ts +8 -0
- package/src/analysis/tokens/TokenAnalysisFormatter.ts +154 -0
- package/src/analysis/tokens/TokenAnalyzer.ts +747 -0
- package/src/analysis/tokens/index.ts +8 -0
- package/src/clients/base/abstractClient.ts +190 -0
- package/src/clients/base/httpClient.ts +160 -0
- package/src/clients/base/index.ts +12 -0
- package/src/clients/base/modelDetection.ts +107 -0
- package/src/clients/base/responseProcessor.ts +586 -0
- package/src/clients/factory/clientFactory.ts +55 -0
- package/src/clients/factory/index.ts +8 -0
- package/src/clients/implementations/index.ts +8 -0
- package/src/clients/implementations/openRouterClient.ts +411 -0
- package/src/clients/openRouterClient.ts +863 -0
- package/src/clients/openRouterClientWrapper.ts +44 -0
- package/src/clients/utils/directoryStructure.ts +52 -0
- package/src/clients/utils/index.ts +11 -0
- package/src/clients/utils/languageDetection.ts +44 -0
- package/src/clients/utils/promptFormatter.ts +105 -0
- package/src/clients/utils/promptLoader.ts +53 -0
- package/src/clients/utils/tokenCounter.ts +297 -0
- package/src/core/ApiClientSelector.ts +37 -0
- package/src/core/ConfigurationService.ts +591 -0
- package/src/core/ConsolidationService.ts +423 -0
- package/src/core/InteractiveDisplayManager.ts +81 -0
- package/src/core/OutputManager.ts +275 -0
- package/src/core/ReviewGenerator.ts +140 -0
- package/src/core/fileDiscovery.ts +237 -0
- package/src/core/handlers/EstimationHandler.ts +104 -0
- package/src/core/handlers/FileProcessingHandler.ts +204 -0
- package/src/core/handlers/OutputHandler.ts +125 -0
- package/src/core/handlers/ReviewExecutor.ts +104 -0
- package/src/core/reviewOrchestrator.ts +333 -0
- package/src/core/utils/ModelInfoUtils.ts +56 -0
- package/src/formatters/outputFormatter.ts +62 -0
- package/src/formatters/utils/IssueFormatters.ts +83 -0
- package/src/formatters/utils/JsonFormatter.ts +77 -0
- package/src/formatters/utils/MarkdownFormatters.ts +609 -0
- package/src/formatters/utils/MetadataFormatter.ts +269 -0
- package/src/formatters/utils/ModelInfoExtractor.ts +115 -0
- package/src/index.ts +27 -0
- package/src/plugins/PluginInterface.ts +50 -0
- package/src/plugins/PluginManager.ts +126 -0
- package/src/prompts/PromptManager.ts +69 -0
- package/src/prompts/cache/PromptCache.ts +50 -0
- package/src/prompts/promptText/common/variables/css-frameworks.json +33 -0
- package/src/prompts/promptText/common/variables/framework-versions.json +45 -0
- package/src/prompts/promptText/frameworks/react/comprehensive.hbs +19 -0
- package/src/prompts/promptText/languages/css/comprehensive.hbs +18 -0
- package/src/prompts/promptText/languages/generic/comprehensive.hbs +20 -0
- package/src/prompts/promptText/languages/html/comprehensive.hbs +18 -0
- package/src/prompts/promptText/languages/javascript/comprehensive.hbs +18 -0
- package/src/prompts/promptText/languages/python/comprehensive.hbs +18 -0
- package/src/prompts/promptText/languages/typescript/comprehensive.hbs +18 -0
- package/src/runtime/auth/service.ts +58 -0
- package/src/runtime/auth/session.ts +103 -0
- package/src/runtime/auth/types.ts +11 -0
- package/src/runtime/cliEntry.ts +196 -0
- package/src/runtime/errors.ts +13 -0
- package/src/runtime/fileCollector.ts +188 -0
- package/src/runtime/manifest.ts +64 -0
- package/src/runtime/openrouterProxy.ts +45 -0
- package/src/runtime/proxyConfig.ts +94 -0
- package/src/runtime/proxyEnvironment.ts +71 -0
- package/src/runtime/reportMerge.ts +102 -0
- package/src/runtime/reporting/markdownReportBuilder.ts +138 -0
- package/src/runtime/reporting/reportDataCollector.ts +234 -0
- package/src/runtime/reporting/summaryGenerator.ts +86 -0
- package/src/runtime/reviewPipeline.ts +155 -0
- package/src/runtime/runAiCodeReview.ts +153 -0
- package/src/runtime/runtimeConfig.ts +5 -0
- package/src/runtime/ui/Layout.tsx +57 -0
- package/src/runtime/ui/RuntimeApp.tsx +150 -0
- package/src/runtime/ui/inkModules.ts +73 -0
- package/src/runtime/ui/screens/AuthScreen.tsx +128 -0
- package/src/runtime/ui/screens/ModeSelection.tsx +52 -0
- package/src/runtime/ui/screens/ProgressScreen.tsx +55 -0
- package/src/runtime/ui/screens/ResultsScreen.tsx +76 -0
- package/src/strategies/ArchitecturalReviewStrategy.ts +54 -0
- package/src/strategies/CodingTestReviewStrategy.ts +920 -0
- package/src/strategies/ConsolidatedReviewStrategy.ts +59 -0
- package/src/strategies/ExtractPatternsReviewStrategy.ts +64 -0
- package/src/strategies/MultiPassReviewStrategy.ts +785 -0
- package/src/strategies/ReviewStrategy.ts +64 -0
- package/src/strategies/StrategyFactory.ts +79 -0
- package/src/strategies/index.ts +14 -0
- package/src/tokenizers/baseTokenizer.ts +61 -0
- package/src/tokenizers/gptTokenizer.ts +27 -0
- package/src/tokenizers/index.ts +8 -0
- package/src/types/apiResponses.ts +40 -0
- package/src/types/cli.ts +24 -0
- package/src/types/common.ts +39 -0
- package/src/types/configuration.ts +201 -0
- package/src/types/handlebars.d.ts +5 -0
- package/src/types/patch.d.ts +25 -0
- package/src/types/review.ts +294 -0
- package/src/types/reviewContext.d.ts +65 -0
- package/src/types/reviewSchema.ts +181 -0
- package/src/types/structuredReview.ts +167 -0
- package/src/types/tokenAnalysis.ts +56 -0
- package/src/utils/FileReader.ts +93 -0
- package/src/utils/FileWriter.ts +76 -0
- package/src/utils/PathGenerator.ts +97 -0
- package/src/utils/api/apiUtils.ts +14 -0
- package/src/utils/api/index.ts +1 -0
- package/src/utils/apiErrorHandler.ts +287 -0
- package/src/utils/ciDataCollector.ts +252 -0
- package/src/utils/codingTestConfigLoader.ts +466 -0
- package/src/utils/dependencies/aiDependencyAnalyzer.ts +454 -0
- package/src/utils/detection/frameworkDetector.ts +879 -0
- package/src/utils/detection/index.ts +10 -0
- package/src/utils/detection/projectTypeDetector.ts +518 -0
- package/src/utils/diagramGenerator.ts +206 -0
- package/src/utils/errorLogger.ts +60 -0
- package/src/utils/estimationUtils.ts +407 -0
- package/src/utils/fileFilters.ts +373 -0
- package/src/utils/fileSystem.ts +57 -0
- package/src/utils/index.ts +36 -0
- package/src/utils/logger.ts +240 -0
- package/src/utils/pathValidator.ts +98 -0
- package/src/utils/priorityFilter.ts +59 -0
- package/src/utils/projectDocs.ts +189 -0
- package/src/utils/promptPaths.ts +29 -0
- package/src/utils/promptTemplateManager.ts +157 -0
- package/src/utils/review/consolidateReview.ts +553 -0
- package/src/utils/review/fixDisplay.ts +100 -0
- package/src/utils/review/fixImplementation.ts +61 -0
- package/src/utils/review/index.ts +36 -0
- package/src/utils/review/interactiveProcessing.ts +294 -0
- package/src/utils/review/progressTracker.ts +296 -0
- package/src/utils/review/reviewExtraction.ts +382 -0
- package/src/utils/review/types.ts +46 -0
- package/src/utils/reviewActionHandler.ts +18 -0
- package/src/utils/reviewParser.ts +253 -0
- package/src/utils/sanitizer.ts +238 -0
- package/src/utils/smartFileSelector.ts +255 -0
- package/src/utils/templateLoader.ts +514 -0
- package/src/utils/treeGenerator.ts +153 -0
- package/tsconfig.build.json +14 -0
- 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
|
+
}
|