@ridit/lens 0.3.2 → 0.3.4
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/LENS.md +41 -0
- package/README.md +2 -2
- package/dist/index.mjs +5438 -3829
- package/package.json +1 -2
- package/src/commands/commit.tsx +10 -2
- package/src/commands/watch.tsx +56 -0
- package/src/components/repo/LensFileMenu.tsx +2 -9
- package/src/components/repo/RepoAnalysis.tsx +241 -50
- package/src/components/timeline/TimelineRunner.tsx +0 -7
- package/src/components/watch/WatchRunner.tsx +929 -0
- package/src/index.tsx +144 -110
- package/src/types/repo.ts +15 -3
- package/src/utils/ai.ts +108 -20
- package/src/utils/lensfile.ts +83 -18
- package/src/utils/watch.ts +307 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ridit/lens",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Know Your Codebase.",
|
|
5
5
|
"author": "Ridit Jangra <riditjangra09@gmail.com> (https://ridit.space)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@ridit/lens-sdk": "0.1.6",
|
|
23
|
-
"add": "^2.0.6",
|
|
24
23
|
"asciichart": "^1.5.25",
|
|
25
24
|
"bun": "^1.3.11",
|
|
26
25
|
"commander": "^14.0.3",
|
package/src/commands/commit.tsx
CHANGED
|
@@ -144,7 +144,7 @@ Rules:
|
|
|
144
144
|
- Skip bullets that just restate the subject line or describe trivial version bumps
|
|
145
145
|
- Be specific — mention file names, feature names, component names
|
|
146
146
|
- No markdown, no backticks, no code blocks
|
|
147
|
-
- Output ONLY the commit message, nothing else
|
|
147
|
+
- Output ONLY the commit message, nothing else — no preamble, no explanation, no thinking
|
|
148
148
|
|
|
149
149
|
Examples of good short commits:
|
|
150
150
|
chore: bump version to 0.1.6
|
|
@@ -158,6 +158,13 @@ feat(chat): add persistent memory across sessions
|
|
|
158
158
|
- inject memory summary into system prompt on load
|
|
159
159
|
- expose /memory commands for manual management`;
|
|
160
160
|
|
|
161
|
+
function stripThinking(raw: string): string {
|
|
162
|
+
return raw
|
|
163
|
+
.replace(/<thinking>[\s\S]*?<\/thinking>/g, "")
|
|
164
|
+
.replace(/^[\s\n]+/, "")
|
|
165
|
+
.trim();
|
|
166
|
+
}
|
|
167
|
+
|
|
161
168
|
async function generateCommitMessage(
|
|
162
169
|
provider: Provider,
|
|
163
170
|
diff: string,
|
|
@@ -170,7 +177,8 @@ async function generateCommitMessage(
|
|
|
170
177
|
},
|
|
171
178
|
];
|
|
172
179
|
const raw = await callChat(provider, SYSTEM_PROMPT, msgs);
|
|
173
|
-
|
|
180
|
+
if (typeof raw !== "string") return "chore: update files";
|
|
181
|
+
return stripThinking(raw) || "chore: update files";
|
|
174
182
|
}
|
|
175
183
|
|
|
176
184
|
function trunc(s: string, n: number) {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import figures from "figures";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { WatchRunner } from "../components/watch/WatchRunner";
|
|
7
|
+
import { RED } from "../colors";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
cmd: string;
|
|
11
|
+
path: string;
|
|
12
|
+
clean: boolean;
|
|
13
|
+
fixAll: boolean;
|
|
14
|
+
autoRestart: boolean;
|
|
15
|
+
prompt?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function WatchCommand({
|
|
19
|
+
cmd,
|
|
20
|
+
path: inputPath,
|
|
21
|
+
clean,
|
|
22
|
+
fixAll,
|
|
23
|
+
autoRestart,
|
|
24
|
+
prompt,
|
|
25
|
+
}: Props) {
|
|
26
|
+
const repoPath = path.resolve(inputPath);
|
|
27
|
+
|
|
28
|
+
if (!cmd.trim()) {
|
|
29
|
+
return (
|
|
30
|
+
<Box marginTop={1}>
|
|
31
|
+
<Text color={RED}>{figures.cross} Usage: lens watch "bun dev"</Text>
|
|
32
|
+
</Box>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!existsSync(repoPath)) {
|
|
37
|
+
return (
|
|
38
|
+
<Box marginTop={1}>
|
|
39
|
+
<Text color={RED}>
|
|
40
|
+
{figures.cross} Path not found: {repoPath}
|
|
41
|
+
</Text>
|
|
42
|
+
</Box>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<WatchRunner
|
|
48
|
+
cmd={cmd}
|
|
49
|
+
repoPath={repoPath}
|
|
50
|
+
clean={clean}
|
|
51
|
+
fixAll={fixAll}
|
|
52
|
+
autoRestart={autoRestart}
|
|
53
|
+
extraPrompt={prompt}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -31,18 +31,11 @@ const buildOptions = (lf: LensFile): MenuOption[] => {
|
|
|
31
31
|
description: "Run a fresh AI analysis",
|
|
32
32
|
},
|
|
33
33
|
];
|
|
34
|
-
if (lf.suggestions.length > 0
|
|
34
|
+
if (lf.suggestions.length > 0) {
|
|
35
35
|
opts.push({
|
|
36
36
|
id: "fix-issues",
|
|
37
37
|
label: "Fix issues",
|
|
38
|
-
description: `${lf.suggestions.length
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
if (lf.securityIssues.length > 0) {
|
|
42
|
-
opts.push({
|
|
43
|
-
id: "security",
|
|
44
|
-
label: "Review security issues",
|
|
45
|
-
description: `${lf.securityIssues.length} issue(s) found`,
|
|
38
|
+
description: `${lf.suggestions.length} issues found`,
|
|
46
39
|
});
|
|
47
40
|
}
|
|
48
41
|
opts.push({
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
2
|
+
import { Box, Text, Static, useInput } from "ink";
|
|
3
3
|
import Spinner from "ink-spinner";
|
|
4
4
|
import figures from "figures";
|
|
5
|
-
import { useState } from "react";
|
|
5
|
+
import { useState, useRef } from "react";
|
|
6
6
|
import { writeFileSync } from "fs";
|
|
7
7
|
import path from "path";
|
|
8
8
|
import { ACCENT } from "../../colors";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
requestFileList,
|
|
11
|
+
analyzeRepo,
|
|
12
|
+
extractToolingPatch,
|
|
13
|
+
} from "../../utils/ai";
|
|
10
14
|
import { ProviderPicker } from "../provider/ProviderPicker";
|
|
11
15
|
import { PreviewRunner } from "./PreviewRunner";
|
|
12
16
|
import { IssueFixer } from "./IssueFixer";
|
|
13
|
-
import { writeLensFile } from "../../utils/lensfile";
|
|
17
|
+
import { writeLensFile, patchLensFile } from "../../utils/lensfile";
|
|
18
|
+
import { callChat } from "../../utils/chat";
|
|
19
|
+
import { StaticMessage } from "../chat/ChatMessage";
|
|
20
|
+
import { InputBox, TypewriterText, ShortcutBar } from "../chat/ChatOverlays";
|
|
14
21
|
import type { Provider } from "../../types/config";
|
|
15
22
|
import type { AnalysisResult, ImportantFile } from "../../types/repo";
|
|
23
|
+
import type { Message } from "../../types/chat";
|
|
16
24
|
import { useThinkingPhrase } from "../../utils/thinking";
|
|
17
25
|
|
|
18
26
|
type AnalysisStage =
|
|
@@ -24,12 +32,17 @@ type AnalysisStage =
|
|
|
24
32
|
| { type: "written"; filePath: string }
|
|
25
33
|
| { type: "previewing" }
|
|
26
34
|
| { type: "fixing"; result: AnalysisResult }
|
|
35
|
+
| { type: "asking"; result: AnalysisResult }
|
|
27
36
|
| { type: "error"; message: string };
|
|
28
37
|
|
|
29
38
|
const OUTPUT_FILES = ["CLAUDE.md", "copilot-instructions.md"] as const;
|
|
30
39
|
type OutputFile = (typeof OUTPUT_FILES)[number];
|
|
31
40
|
|
|
32
41
|
function buildMarkdown(repoUrl: string, result: AnalysisResult): string {
|
|
42
|
+
const toolingLines = Object.entries(result.tooling ?? {})
|
|
43
|
+
.map(([k, v]) => `- **${k}**: ${v}`)
|
|
44
|
+
.join("\n");
|
|
45
|
+
|
|
33
46
|
return `# Repository Analysis
|
|
34
47
|
|
|
35
48
|
> ${repoUrl}
|
|
@@ -37,28 +50,56 @@ function buildMarkdown(repoUrl: string, result: AnalysisResult): string {
|
|
|
37
50
|
## Overview
|
|
38
51
|
${result.overview}
|
|
39
52
|
|
|
53
|
+
## Architecture
|
|
54
|
+
${result.architecture ?? ""}
|
|
55
|
+
|
|
56
|
+
## Tooling
|
|
57
|
+
${toolingLines || "- Not determined"}
|
|
58
|
+
|
|
40
59
|
## Important Folders
|
|
41
60
|
${result.importantFolders.map((f) => `- ${f}`).join("\n")}
|
|
42
61
|
|
|
43
|
-
##
|
|
44
|
-
${
|
|
45
|
-
result.missingConfigs.length > 0
|
|
46
|
-
? result.missingConfigs.map((f) => `- ${f}`).join("\n")
|
|
47
|
-
: "- None detected"
|
|
48
|
-
}
|
|
62
|
+
## Key Files
|
|
63
|
+
${(result.keyFiles ?? []).map((f) => `- ${f}`).join("\n")}
|
|
49
64
|
|
|
50
|
-
##
|
|
51
|
-
${
|
|
52
|
-
result.securityIssues.length > 0
|
|
53
|
-
? result.securityIssues.map((s) => `- ⚠️ ${s}`).join("\n")
|
|
54
|
-
: "- None detected"
|
|
55
|
-
}
|
|
65
|
+
## Patterns & Idioms
|
|
66
|
+
${(result.patterns ?? []).map((p) => `- ${p}`).join("\n")}
|
|
56
67
|
|
|
57
68
|
## Suggestions
|
|
58
69
|
${result.suggestions.map((s) => `- ${s}`).join("\n")}
|
|
59
70
|
`;
|
|
60
71
|
}
|
|
61
72
|
|
|
73
|
+
function buildQASystemPrompt(repoUrl: string, result: AnalysisResult): string {
|
|
74
|
+
const toolingLines = Object.entries(result.tooling ?? {})
|
|
75
|
+
.map(([k, v]) => `- ${k}: ${v}`)
|
|
76
|
+
.join("\n");
|
|
77
|
+
|
|
78
|
+
return `You are a codebase assistant for the repository at ${repoUrl}.
|
|
79
|
+
|
|
80
|
+
Here is what you know about this codebase:
|
|
81
|
+
|
|
82
|
+
Overview:
|
|
83
|
+
${result.overview}
|
|
84
|
+
|
|
85
|
+
Architecture:
|
|
86
|
+
${result.architecture ?? "Not determined"}
|
|
87
|
+
|
|
88
|
+
Tooling:
|
|
89
|
+
${toolingLines || "Not determined"}
|
|
90
|
+
|
|
91
|
+
Important Folders:
|
|
92
|
+
${result.importantFolders.map((f) => `- ${f}`).join("\n")}
|
|
93
|
+
|
|
94
|
+
Key Files:
|
|
95
|
+
${(result.keyFiles ?? []).map((f) => `- ${f}`).join("\n")}
|
|
96
|
+
|
|
97
|
+
Patterns & Idioms:
|
|
98
|
+
${(result.patterns ?? []).map((p) => `- ${p}`).join("\n")}
|
|
99
|
+
|
|
100
|
+
Answer questions about this codebase concisely and accurately. If you're unsure about something not covered in the analysis, say so clearly rather than guessing.`;
|
|
101
|
+
}
|
|
102
|
+
|
|
62
103
|
function AskingFilesStep() {
|
|
63
104
|
const phrase = useThinkingPhrase(true, "model");
|
|
64
105
|
return (
|
|
@@ -83,6 +124,121 @@ function AnalyzingStep() {
|
|
|
83
124
|
);
|
|
84
125
|
}
|
|
85
126
|
|
|
127
|
+
// ─── CodebaseQA ──────────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
type QAStage = "idle" | "thinking";
|
|
130
|
+
|
|
131
|
+
function CodebaseQA({
|
|
132
|
+
repoUrl,
|
|
133
|
+
result,
|
|
134
|
+
provider,
|
|
135
|
+
onExit,
|
|
136
|
+
}: {
|
|
137
|
+
repoUrl: string;
|
|
138
|
+
result: AnalysisResult;
|
|
139
|
+
provider: Provider;
|
|
140
|
+
onExit: () => void;
|
|
141
|
+
}) {
|
|
142
|
+
const [committed, setCommitted] = useState<Message[]>([]);
|
|
143
|
+
const [allMessages, setAllMessages] = useState<Message[]>([]);
|
|
144
|
+
const [inputValue, setInputValue] = useState("");
|
|
145
|
+
const [inputKey, setInputKey] = useState(0);
|
|
146
|
+
const [qaStage, setQaStage] = useState<QAStage>("idle");
|
|
147
|
+
const abortRef = useRef<AbortController | null>(null);
|
|
148
|
+
const systemPrompt = buildQASystemPrompt(repoUrl, result);
|
|
149
|
+
const thinkingPhrase = useThinkingPhrase(qaStage === "thinking");
|
|
150
|
+
|
|
151
|
+
useInput((_, key) => {
|
|
152
|
+
if (key.escape) {
|
|
153
|
+
if (qaStage === "thinking") {
|
|
154
|
+
abortRef.current?.abort();
|
|
155
|
+
abortRef.current = null;
|
|
156
|
+
setQaStage("idle");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
onExit();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const sendQuestion = (text: string) => {
|
|
164
|
+
const trimmed = text.trim();
|
|
165
|
+
if (!trimmed) return;
|
|
166
|
+
|
|
167
|
+
const userMsg: Message = { role: "user", type: "text", content: trimmed };
|
|
168
|
+
const nextAll = [...allMessages, userMsg];
|
|
169
|
+
setCommitted((prev) => [...prev, userMsg]);
|
|
170
|
+
setAllMessages(nextAll);
|
|
171
|
+
setQaStage("thinking");
|
|
172
|
+
|
|
173
|
+
const abort = new AbortController();
|
|
174
|
+
abortRef.current = abort;
|
|
175
|
+
|
|
176
|
+
callChat(provider, systemPrompt, nextAll, abort.signal)
|
|
177
|
+
.then((answer) => {
|
|
178
|
+
const assistantMsg: Message = {
|
|
179
|
+
role: "assistant",
|
|
180
|
+
type: "text",
|
|
181
|
+
content: answer,
|
|
182
|
+
};
|
|
183
|
+
setCommitted((prev) => [...prev, assistantMsg]);
|
|
184
|
+
setAllMessages([...nextAll, assistantMsg]);
|
|
185
|
+
setQaStage("idle");
|
|
186
|
+
})
|
|
187
|
+
.catch((err: unknown) => {
|
|
188
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
189
|
+
setQaStage("idle");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const errMsg: Message = {
|
|
193
|
+
role: "assistant",
|
|
194
|
+
type: "text",
|
|
195
|
+
content: `Error: ${err instanceof Error ? err.message : "Request failed"}`,
|
|
196
|
+
};
|
|
197
|
+
setCommitted((prev) => [...prev, errMsg]);
|
|
198
|
+
setAllMessages([...nextAll, errMsg]);
|
|
199
|
+
setQaStage("idle");
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<Box flexDirection="column">
|
|
205
|
+
<Static items={committed}>
|
|
206
|
+
{(msg, i) => <StaticMessage key={i} msg={msg} />}
|
|
207
|
+
</Static>
|
|
208
|
+
|
|
209
|
+
{qaStage === "thinking" && (
|
|
210
|
+
<Box gap={1}>
|
|
211
|
+
<Text color={ACCENT}>●</Text>
|
|
212
|
+
<TypewriterText text={thinkingPhrase} />
|
|
213
|
+
<Text color="gray" dimColor>
|
|
214
|
+
· esc cancel
|
|
215
|
+
</Text>
|
|
216
|
+
</Box>
|
|
217
|
+
)}
|
|
218
|
+
|
|
219
|
+
{qaStage === "idle" && (
|
|
220
|
+
<Box flexDirection="column">
|
|
221
|
+
<InputBox
|
|
222
|
+
value={inputValue}
|
|
223
|
+
onChange={setInputValue}
|
|
224
|
+
onSubmit={(val) => {
|
|
225
|
+
if (val.trim()) sendQuestion(val.trim());
|
|
226
|
+
setInputValue("");
|
|
227
|
+
setInputKey((k) => k + 1);
|
|
228
|
+
}}
|
|
229
|
+
inputKey={inputKey}
|
|
230
|
+
/>
|
|
231
|
+
<Text color="gray" dimColor>
|
|
232
|
+
enter send · esc back
|
|
233
|
+
</Text>
|
|
234
|
+
</Box>
|
|
235
|
+
)}
|
|
236
|
+
</Box>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── RepoAnalysis ─────────────────────────────────────────────────────────────
|
|
241
|
+
|
|
86
242
|
export const RepoAnalysis = ({
|
|
87
243
|
repoUrl,
|
|
88
244
|
repoPath,
|
|
@@ -103,18 +259,31 @@ export const RepoAnalysis = ({
|
|
|
103
259
|
? { type: "done", result: preloadedResult }
|
|
104
260
|
: { type: "picking-provider" },
|
|
105
261
|
);
|
|
106
|
-
const [selectedOutput, setSelectedOutput] = useState<0 | 1 | 2 | 3>(0);
|
|
262
|
+
const [selectedOutput, setSelectedOutput] = useState<0 | 1 | 2 | 3 | 4>(0);
|
|
107
263
|
const [requestedFiles, setRequestedFiles] = useState<ImportantFile[]>([]);
|
|
108
264
|
const [provider, setProvider] = useState<Provider | null>(null);
|
|
109
265
|
|
|
110
|
-
const OPTIONS = [
|
|
266
|
+
const OPTIONS = [
|
|
267
|
+
...OUTPUT_FILES,
|
|
268
|
+
"Preview repo",
|
|
269
|
+
"Fix issues",
|
|
270
|
+
"Ask questions",
|
|
271
|
+
] as const;
|
|
111
272
|
|
|
112
273
|
const handleProviderDone = (p: Provider) => {
|
|
113
274
|
setProvider(p);
|
|
114
275
|
setStage({ type: "requesting-files" });
|
|
276
|
+
|
|
115
277
|
requestFileList(repoUrl, repoPath, fileTree, p)
|
|
116
278
|
.then((files) => {
|
|
117
279
|
setRequestedFiles(files);
|
|
280
|
+
|
|
281
|
+
extractToolingPatch(repoUrl, files.length > 0 ? files : initialFiles, p)
|
|
282
|
+
.then((patch) => {
|
|
283
|
+
if (patch) patchLensFile(repoPath, patch);
|
|
284
|
+
})
|
|
285
|
+
.catch(() => {});
|
|
286
|
+
|
|
118
287
|
setStage({ type: "analyzing" });
|
|
119
288
|
return analyzeRepo(repoUrl, files.length > 0 ? files : initialFiles, p);
|
|
120
289
|
})
|
|
@@ -133,10 +302,10 @@ export const RepoAnalysis = ({
|
|
|
133
302
|
useInput((_, key) => {
|
|
134
303
|
if (stage.type !== "done") return;
|
|
135
304
|
if (key.leftArrow)
|
|
136
|
-
setSelectedOutput((i) => Math.max(0, i - 1) as 0 | 1 | 2 | 3);
|
|
305
|
+
setSelectedOutput((i) => Math.max(0, i - 1) as 0 | 1 | 2 | 3 | 4);
|
|
137
306
|
if (key.rightArrow)
|
|
138
307
|
setSelectedOutput(
|
|
139
|
-
(i) => Math.min(OPTIONS.length - 1, i + 1) as 0 | 1 | 2 | 3,
|
|
308
|
+
(i) => Math.min(OPTIONS.length - 1, i + 1) as 0 | 1 | 2 | 3 | 4,
|
|
140
309
|
);
|
|
141
310
|
if (key.return) {
|
|
142
311
|
if (selectedOutput === 2) {
|
|
@@ -147,6 +316,10 @@ export const RepoAnalysis = ({
|
|
|
147
316
|
setStage({ type: "fixing", result: stage.result });
|
|
148
317
|
return;
|
|
149
318
|
}
|
|
319
|
+
if (selectedOutput === 4) {
|
|
320
|
+
setStage({ type: "asking", result: stage.result });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
150
323
|
const fileName = OUTPUT_FILES[selectedOutput] as OutputFile;
|
|
151
324
|
setStage({ type: "writing" });
|
|
152
325
|
try {
|
|
@@ -205,9 +378,7 @@ export const RepoAnalysis = ({
|
|
|
205
378
|
if (stage.type === "written") {
|
|
206
379
|
setTimeout(() => {
|
|
207
380
|
if (onExit) onExit();
|
|
208
|
-
else
|
|
209
|
-
process.exit(0);
|
|
210
|
-
}
|
|
381
|
+
else process.exit(0);
|
|
211
382
|
}, 100);
|
|
212
383
|
return (
|
|
213
384
|
<Text color="green">
|
|
@@ -228,9 +399,7 @@ export const RepoAnalysis = ({
|
|
|
228
399
|
onExit={() => {
|
|
229
400
|
setTimeout(() => {
|
|
230
401
|
if (onExit) onExit();
|
|
231
|
-
else
|
|
232
|
-
process.exit(0);
|
|
233
|
-
}
|
|
402
|
+
else process.exit(0);
|
|
234
403
|
}, 100);
|
|
235
404
|
}}
|
|
236
405
|
/>
|
|
@@ -250,6 +419,17 @@ export const RepoAnalysis = ({
|
|
|
250
419
|
);
|
|
251
420
|
}
|
|
252
421
|
|
|
422
|
+
if (stage.type === "asking") {
|
|
423
|
+
return (
|
|
424
|
+
<CodebaseQA
|
|
425
|
+
repoUrl={repoUrl}
|
|
426
|
+
result={stage.result}
|
|
427
|
+
provider={provider!}
|
|
428
|
+
onExit={() => setStage({ type: "done", result: stage.result })}
|
|
429
|
+
/>
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
253
433
|
if (stage.type === "error") {
|
|
254
434
|
return (
|
|
255
435
|
<Text color="red">
|
|
@@ -269,6 +449,25 @@ export const RepoAnalysis = ({
|
|
|
269
449
|
<Text color="white">{result.overview}</Text>
|
|
270
450
|
</Box>
|
|
271
451
|
|
|
452
|
+
<Box flexDirection="column">
|
|
453
|
+
<Text bold color="cyan">
|
|
454
|
+
{figures.pointerSmall} Architecture
|
|
455
|
+
</Text>
|
|
456
|
+
<Text color="white">{result.architecture}</Text>
|
|
457
|
+
</Box>
|
|
458
|
+
|
|
459
|
+
<Box flexDirection="column">
|
|
460
|
+
<Text bold color="cyan">
|
|
461
|
+
{figures.pointerSmall} Tooling
|
|
462
|
+
</Text>
|
|
463
|
+
{Object.entries(result.tooling ?? {}).map(([k, v]) => (
|
|
464
|
+
<Text key={k} color="white">
|
|
465
|
+
{" "}
|
|
466
|
+
{figures.bullet} <Text bold>{k}</Text>: {v}
|
|
467
|
+
</Text>
|
|
468
|
+
))}
|
|
469
|
+
</Box>
|
|
470
|
+
|
|
272
471
|
<Box flexDirection="column">
|
|
273
472
|
<Text bold color="cyan">
|
|
274
473
|
{figures.pointerSmall} Important Folders
|
|
@@ -282,35 +481,27 @@ export const RepoAnalysis = ({
|
|
|
282
481
|
</Box>
|
|
283
482
|
|
|
284
483
|
<Box flexDirection="column">
|
|
285
|
-
<Text bold color="
|
|
286
|
-
{figures.
|
|
484
|
+
<Text bold color="cyan">
|
|
485
|
+
{figures.pointerSmall} Key Files
|
|
287
486
|
</Text>
|
|
288
|
-
{result.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
))
|
|
295
|
-
) : (
|
|
296
|
-
<Text color="gray"> None detected</Text>
|
|
297
|
-
)}
|
|
487
|
+
{(result.keyFiles ?? []).map((f) => (
|
|
488
|
+
<Text key={f} color="white">
|
|
489
|
+
{" "}
|
|
490
|
+
{figures.bullet} {f}
|
|
491
|
+
</Text>
|
|
492
|
+
))}
|
|
298
493
|
</Box>
|
|
299
494
|
|
|
300
495
|
<Box flexDirection="column">
|
|
301
|
-
<Text bold color="
|
|
302
|
-
{figures.
|
|
496
|
+
<Text bold color="cyan">
|
|
497
|
+
{figures.pointerSmall} Patterns & Idioms
|
|
303
498
|
</Text>
|
|
304
|
-
{result.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
))
|
|
311
|
-
) : (
|
|
312
|
-
<Text color="gray"> None detected</Text>
|
|
313
|
-
)}
|
|
499
|
+
{(result.patterns ?? []).map((p) => (
|
|
500
|
+
<Text key={p} color="white">
|
|
501
|
+
{" "}
|
|
502
|
+
{figures.bullet} {p}
|
|
503
|
+
</Text>
|
|
504
|
+
))}
|
|
314
505
|
</Box>
|
|
315
506
|
|
|
316
507
|
<Box flexDirection="column">
|
|
@@ -1179,7 +1179,6 @@ export function TimelineRunner({
|
|
|
1179
1179
|
|
|
1180
1180
|
return (
|
|
1181
1181
|
<Box flexDirection="column">
|
|
1182
|
-
{/* header */}
|
|
1183
1182
|
<Box gap={2} marginBottom={1}>
|
|
1184
1183
|
<Text color={ACCENT} bold>
|
|
1185
1184
|
◈ TIMELINE
|
|
@@ -1195,7 +1194,6 @@ export function TimelineRunner({
|
|
|
1195
1194
|
)}
|
|
1196
1195
|
</Box>
|
|
1197
1196
|
|
|
1198
|
-
{/* status messages */}
|
|
1199
1197
|
<Static items={statusMsgs}>
|
|
1200
1198
|
{(msg) => (
|
|
1201
1199
|
<Box key={msg.id} paddingX={1} gap={1}>
|
|
@@ -1205,7 +1203,6 @@ export function TimelineRunner({
|
|
|
1205
1203
|
)}
|
|
1206
1204
|
</Static>
|
|
1207
1205
|
|
|
1208
|
-
{/* search bar */}
|
|
1209
1206
|
{isSearching && (
|
|
1210
1207
|
<Box gap={1} marginBottom={1}>
|
|
1211
1208
|
<Text color={ACCENT}>{"/"}</Text>
|
|
@@ -1218,7 +1215,6 @@ export function TimelineRunner({
|
|
|
1218
1215
|
</Box>
|
|
1219
1216
|
)}
|
|
1220
1217
|
|
|
1221
|
-
{/* commit list */}
|
|
1222
1218
|
{visible.map((commit, i) => {
|
|
1223
1219
|
const absIdx = scrollOffset + i;
|
|
1224
1220
|
const isSel = absIdx === selectedIdx;
|
|
@@ -1251,7 +1247,6 @@ export function TimelineRunner({
|
|
|
1251
1247
|
</Box>
|
|
1252
1248
|
)}
|
|
1253
1249
|
|
|
1254
|
-
{/* revert overlay */}
|
|
1255
1250
|
{isReverting && mode.type === "revert" && (
|
|
1256
1251
|
<RevertConfirm
|
|
1257
1252
|
commit={mode.commit}
|
|
@@ -1268,7 +1263,6 @@ export function TimelineRunner({
|
|
|
1268
1263
|
/>
|
|
1269
1264
|
)}
|
|
1270
1265
|
|
|
1271
|
-
{/* ask panel */}
|
|
1272
1266
|
{isAsking && provider && (
|
|
1273
1267
|
<AskPanel
|
|
1274
1268
|
commits={commits}
|
|
@@ -1281,7 +1275,6 @@ export function TimelineRunner({
|
|
|
1281
1275
|
/>
|
|
1282
1276
|
)}
|
|
1283
1277
|
|
|
1284
|
-
{/* shortcut bar */}
|
|
1285
1278
|
<Box marginTop={1}>
|
|
1286
1279
|
<Text color="gray" dimColor>
|
|
1287
1280
|
{shortcutHint}
|