@repolensai/cli 0.1.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.
@@ -0,0 +1,18 @@
1
+ export interface HistoryReportMetadata {
2
+ kind: string;
3
+ generatedAt: string | null;
4
+ sinceRef: string | null;
5
+ scopePath: string | null;
6
+ pathKind: string | null;
7
+ summary: string | null;
8
+ whyChanged: string | null;
9
+ relatedFiles: string[];
10
+ commitThemes: string[];
11
+ topOwners: string[];
12
+ readingOrder: string[];
13
+ query?: string | null;
14
+ traceMatches?: string[];
15
+ }
16
+
17
+ export function buildHistoryReportMetadata(kind: string, payload: Record<string, unknown>): HistoryReportMetadata;
18
+ export function buildHistoryMetadataText(metadata: HistoryReportMetadata): string;
@@ -0,0 +1,87 @@
1
+ export function buildHistoryReportMetadata(kind, payload) {
2
+ const base = {
3
+ kind,
4
+ generatedAt: readString(payload?.generatedAt),
5
+ sinceRef: readString(payload?.sinceRef),
6
+ scopePath: readString(payload?.targetPath),
7
+ pathKind: readString(payload?.pathKind),
8
+ summary: readString(payload?.summary) ?? buildDefaultSummary(kind, payload),
9
+ whyChanged: readString(payload?.whyChanged),
10
+ relatedFiles: readStringArray(payload?.relatedFiles),
11
+ commitThemes: readCommitThemes(payload?.commitThemes).map((theme) => theme.label),
12
+ topOwners: readOwners(payload?.owners).slice(0, 3).map((owner) => owner.author),
13
+ readingOrder: readReadingOrder(payload?.readingOrder).slice(0, 5).map((entry) => entry.path),
14
+ };
15
+
16
+ if (kind === "trace") {
17
+ return {
18
+ ...base,
19
+ query: readString(payload?.query),
20
+ traceMatches: readTraceMatches(payload?.matches).slice(0, 5).map((match) => match.filePath),
21
+ };
22
+ }
23
+
24
+ return base;
25
+ }
26
+
27
+ export function buildHistoryMetadataText(metadata) {
28
+ const sections = [
29
+ metadata.kind ? `Kind: ${metadata.kind}` : null,
30
+ metadata.query ? `Query: ${metadata.query}` : null,
31
+ metadata.scopePath ? `Scope: ${metadata.scopePath}${metadata.pathKind ? ` (${metadata.pathKind})` : ""}` : null,
32
+ metadata.summary ? `Summary: ${metadata.summary}` : null,
33
+ metadata.whyChanged ? `Why changed: ${metadata.whyChanged}` : null,
34
+ metadata.commitThemes?.length ? `Themes: ${metadata.commitThemes.join(", ")}` : null,
35
+ metadata.topOwners?.length ? `Owners: ${metadata.topOwners.join(", ")}` : null,
36
+ metadata.readingOrder?.length ? `Reading order: ${metadata.readingOrder.join(" -> ")}` : null,
37
+ metadata.traceMatches?.length ? `Matches: ${metadata.traceMatches.join(", ")}` : null,
38
+ ].filter(Boolean);
39
+
40
+ return sections.join("\n");
41
+ }
42
+
43
+ function buildDefaultSummary(kind, payload) {
44
+ switch (kind) {
45
+ case "trace":
46
+ return readString(payload?.query) ? `Likely matches for ${payload.query}.` : "Likely file matches.";
47
+ case "hotspots":
48
+ return readHotspotFiles(payload?.files).length > 0
49
+ ? `${readHotspotFiles(payload.files).length} hotspot files were ranked.`
50
+ : "Hotspot file ranking.";
51
+ case "ownership":
52
+ return readString(payload?.targetPath) ? `Ownership summary for ${payload.targetPath}.` : "Ownership summary.";
53
+ case "start-here":
54
+ return readString(payload?.targetPath) ? `Start-here summary for ${payload.targetPath}.` : "Start-here summary.";
55
+ case "evolution":
56
+ default:
57
+ return readString(payload?.targetPath) ? `Evolution summary for ${payload.targetPath}.` : "Evolution summary.";
58
+ }
59
+ }
60
+
61
+ function readString(value) {
62
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
63
+ }
64
+
65
+ function readStringArray(value) {
66
+ return Array.isArray(value) ? value.map((entry) => (typeof entry === "string" ? entry : "")).filter(Boolean) : [];
67
+ }
68
+
69
+ function readOwners(value) {
70
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.author === "string") : [];
71
+ }
72
+
73
+ function readReadingOrder(value) {
74
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.path === "string") : [];
75
+ }
76
+
77
+ function readCommitThemes(value) {
78
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.label === "string") : [];
79
+ }
80
+
81
+ function readTraceMatches(value) {
82
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.filePath === "string") : [];
83
+ }
84
+
85
+ function readHotspotFiles(value) {
86
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.filePath === "string") : [];
87
+ }
@@ -0,0 +1,6 @@
1
+ export type HistoryReportKind = "evolution" | "hotspots" | "ownership" | "start-here" | "trace";
2
+
3
+ export function buildHistoryReportMarkdown(kind: HistoryReportKind, payload: Record<string, unknown>): string;
4
+ export function buildHistoryReportTitle(kind: HistoryReportKind, payload: Record<string, unknown>): string;
5
+ export function buildHistoryReportSummary(kind: HistoryReportKind, payload: Record<string, unknown>): string;
6
+ export function collectHistoryReportRelatedFiles(kind: HistoryReportKind, payload: Record<string, unknown>): string[];
@@ -0,0 +1,410 @@
1
+ export function buildHistoryReportMarkdown(kind, payload) {
2
+ switch (kind) {
3
+ case "evolution":
4
+ return buildEvolutionMarkdown(payload);
5
+ case "hotspots":
6
+ return buildHotspotsMarkdown(payload);
7
+ case "ownership":
8
+ return buildOwnershipMarkdown(payload);
9
+ case "start-here":
10
+ return buildStartHereMarkdown(payload);
11
+ case "trace":
12
+ return buildTraceMarkdown(payload);
13
+ default:
14
+ return "# RepoLens History Report\n";
15
+ }
16
+ }
17
+
18
+ export function buildHistoryReportTitle(kind, payload) {
19
+ switch (kind) {
20
+ case "evolution":
21
+ return `Evolution: ${stringOrFallback(payload?.targetPath, "Unknown path")}`;
22
+ case "hotspots":
23
+ return `Hotspots since ${stringOrFallback(payload?.sinceRef, "recent history")}`;
24
+ case "ownership":
25
+ return `Ownership: ${stringOrFallback(payload?.targetPath, "Unknown path")}`;
26
+ case "start-here":
27
+ return `Start Here: ${stringOrFallback(payload?.targetPath, "Unknown path")}`;
28
+ case "trace":
29
+ return `Trace: ${stringOrFallback(payload?.query, "Unknown query")}`;
30
+ default:
31
+ return "History Report";
32
+ }
33
+ }
34
+
35
+ export function buildHistoryReportSummary(kind, payload) {
36
+ switch (kind) {
37
+ case "evolution":
38
+ return `${numberOrZero(payload?.commitCount)} commits across ${arrayLength(payload?.authors)} authors for ${stringOrFallback(payload?.targetPath, "this path")}.`;
39
+ case "hotspots":
40
+ return `${arrayLength(payload?.files)} hotspot files ranked from recent churn${payload?.sinceRef ? ` since ${payload.sinceRef}` : ""}.`;
41
+ case "ownership":
42
+ return `${arrayLength(payload?.owners)} owners summarized for ${stringOrFallback(payload?.targetPath, "this path")}.`;
43
+ case "start-here":
44
+ return `${numberOrZero(payload?.trackedFileCount)} tracked files and ${arrayLength(payload?.owners)} owners summarized for ${stringOrFallback(payload?.targetPath, "this area")}.`;
45
+ case "trace":
46
+ return `${arrayLength(payload?.matches)} likely matches for ${stringOrFallback(payload?.query, "this query")}.`;
47
+ default:
48
+ return "History-aware repo report.";
49
+ }
50
+ }
51
+
52
+ export function collectHistoryReportRelatedFiles(kind, payload) {
53
+ switch (kind) {
54
+ case "evolution":
55
+ case "ownership":
56
+ return dedupeStrings([
57
+ readString(payload?.targetPath),
58
+ ...readRenameHistory(payload?.renameHistory).flatMap((entry) => [entry.fromPath, entry.toPath]),
59
+ ...readFileStats(payload?.fileStats).map((entry) => entry.filePath),
60
+ ]);
61
+ case "start-here":
62
+ return dedupeStrings([
63
+ readString(payload?.targetPath),
64
+ ...readHotspotFiles(payload?.hotspotFiles).map((entry) => entry.filePath),
65
+ ...readDirectoryStats(payload?.directoryStats).map((entry) => entry.directoryPath),
66
+ ...readReadingOrder(payload?.readingOrder).map((entry) => entry.path),
67
+ ...readRenameHistory(payload?.renameHistory).flatMap((entry) => [entry.fromPath, entry.toPath]),
68
+ ]);
69
+ case "hotspots":
70
+ return dedupeStrings(readHotspotFiles(payload?.files).map((entry) => entry.filePath));
71
+ case "trace":
72
+ return dedupeStrings(readTraceMatches(payload?.matches).map((entry) => entry.filePath));
73
+ default:
74
+ return [];
75
+ }
76
+ }
77
+
78
+ function buildEvolutionMarkdown(payload) {
79
+ const sections = [
80
+ "# RepoLens Evolution Report",
81
+ "",
82
+ `- Repo root: \`${stringOrFallback(payload?.repoRoot, "unknown")}\``,
83
+ `- Branch: \`${stringOrFallback(payload?.branch, "unknown")}\``,
84
+ `- Target path: \`${stringOrFallback(payload?.targetPath, "unknown")}\``,
85
+ `- Scope: ${stringOrFallback(payload?.pathKind, "unknown")}`,
86
+ `- Since: \`${stringOrFallback(payload?.sinceRef, "full available history")}\``,
87
+ `- Tracked files in scope: ${numberOrZero(payload?.trackedFileCount)}`,
88
+ `- Generated at: ${stringOrFallback(payload?.generatedAt, "unknown")}`,
89
+ "",
90
+ "## Why This Area Changed",
91
+ stringOrFallback(payload?.whyChanged, "No why-changed summary was derived."),
92
+ "",
93
+ "## Commit Themes",
94
+ readCommitThemes(payload?.commitThemes).length > 0
95
+ ? readCommitThemes(payload?.commitThemes)
96
+ .map((theme) => `- ${theme.label}: ${theme.commits} commits${theme.examples.length > 0 ? ` (${theme.examples.join("; ")})` : ""}`)
97
+ .join("\n")
98
+ : "- No recurring commit themes found.",
99
+ "",
100
+ "## Recent Commits",
101
+ readCommits(payload?.commits).length > 0
102
+ ? readCommits(payload?.commits)
103
+ .map((commit) => `- ${commit.date} \`${shortSha(commit.sha)}\` ${commit.author} — ${commit.subject}`)
104
+ .join("\n")
105
+ : "- No history found for this path.",
106
+ "",
107
+ "## Authors",
108
+ readAuthors(payload?.authors).length > 0
109
+ ? readAuthors(payload?.authors).map((author) => `- ${author.author}: ${author.commits} commits`).join("\n")
110
+ : "- No authors found.",
111
+ "",
112
+ "## Touched Files",
113
+ readFileStats(payload?.fileStats).length > 0
114
+ ? readFileStats(payload?.fileStats)
115
+ .map((file) => `- \`${file.filePath}\` — ${file.touchedCommits} commits, +${file.additions}/-${file.deletions}`)
116
+ .join("\n")
117
+ : "- No file stats found.",
118
+ "",
119
+ "## Subdirectories",
120
+ readDirectoryStats(payload?.directoryStats).length > 0
121
+ ? readDirectoryStats(payload?.directoryStats)
122
+ .map((directory) => `- \`${directory.directoryPath}\` — ${directory.fileCount} files, ${directory.touchedCommits} commits, score ${directory.score}`)
123
+ .join("\n")
124
+ : "- No subdirectory summaries found.",
125
+ "",
126
+ "## Rename / Move History",
127
+ readRenameHistory(payload?.renameHistory).length > 0
128
+ ? readRenameHistory(payload?.renameHistory)
129
+ .map((entry) => `- ${shortSha(entry.commit)}: \`${entry.fromPath}\` -> \`${entry.toPath}\``)
130
+ .join("\n")
131
+ : "- No rename or move history found.",
132
+ ];
133
+
134
+ return `${sections.join("\n")}\n`;
135
+ }
136
+
137
+ function buildHotspotsMarkdown(payload) {
138
+ const sections = [
139
+ "# RepoLens Hotspots Report",
140
+ "",
141
+ `- Repo root: \`${stringOrFallback(payload?.repoRoot, "unknown")}\``,
142
+ `- Branch: \`${stringOrFallback(payload?.branch, "unknown")}\``,
143
+ `- Since: \`${stringOrFallback(payload?.sinceRef, "recent history")}\``,
144
+ `- Generated at: ${stringOrFallback(payload?.generatedAt, "unknown")}`,
145
+ "",
146
+ "## Hotspots",
147
+ readHotspotFiles(payload?.files).length > 0
148
+ ? readHotspotFiles(payload?.files)
149
+ .map((file, index) => `${index + 1}. \`${file.filePath}\` — score ${file.score}, ${file.touchedCommits} commits, +${file.additions}/-${file.deletions}`)
150
+ .join("\n")
151
+ : "- No hotspot files found.",
152
+ ];
153
+
154
+ return `${sections.join("\n")}\n`;
155
+ }
156
+
157
+ function buildOwnershipMarkdown(payload) {
158
+ const sections = [
159
+ "# RepoLens Ownership Report",
160
+ "",
161
+ `- Repo root: \`${stringOrFallback(payload?.repoRoot, "unknown")}\``,
162
+ `- Branch: \`${stringOrFallback(payload?.branch, "unknown")}\``,
163
+ `- Target path: \`${stringOrFallback(payload?.targetPath, "unknown")}\``,
164
+ `- Scope: ${stringOrFallback(payload?.pathKind, "unknown")}`,
165
+ `- Since: \`${stringOrFallback(payload?.sinceRef, "full available history")}\``,
166
+ `- Tracked files in scope: ${numberOrZero(payload?.trackedFileCount)}`,
167
+ `- Generated at: ${stringOrFallback(payload?.generatedAt, "unknown")}`,
168
+ "",
169
+ "## Owners",
170
+ readOwners(payload?.owners).length > 0
171
+ ? readOwners(payload?.owners)
172
+ .map((owner) => `- ${owner.author}: ${owner.commits} commits, last touched ${owner.lastTouchedAt} — ${owner.lastSubject}`)
173
+ .join("\n")
174
+ : "- No ownership history found.",
175
+ "",
176
+ "## Current Line Ownership",
177
+ readBlameOwners(payload?.blameOwners).length > 0
178
+ ? readBlameOwners(payload?.blameOwners)
179
+ .map((owner) => `- ${owner.author}: ${owner.lines} current lines across ${owner.commits} commits`)
180
+ .join("\n")
181
+ : "- No blame-based ownership available for this path.",
182
+ "",
183
+ "## File Ownership Map",
184
+ readFileOwners(payload?.fileOwners).length > 0
185
+ ? readFileOwners(payload?.fileOwners)
186
+ .map((owner) => `- \`${owner.filePath}\` — ${owner.primaryOwner} (${owner.ownerCommits} commits, ${owner.touchedCommits} touches)`)
187
+ .join("\n")
188
+ : "- No file-level ownership map found.",
189
+ "",
190
+ "## Rename / Move History",
191
+ readRenameHistory(payload?.renameHistory).length > 0
192
+ ? readRenameHistory(payload?.renameHistory)
193
+ .map((entry) => `- ${shortSha(entry.commit)}: \`${entry.fromPath}\` -> \`${entry.toPath}\``)
194
+ .join("\n")
195
+ : "- No rename or move history found.",
196
+ ];
197
+
198
+ return `${sections.join("\n")}\n`;
199
+ }
200
+
201
+ function buildStartHereMarkdown(payload) {
202
+ const sections = [
203
+ "# RepoLens Start Here Report",
204
+ "",
205
+ `- Repo root: \`${stringOrFallback(payload?.repoRoot, "unknown")}\``,
206
+ `- Branch: \`${stringOrFallback(payload?.branch, "unknown")}\``,
207
+ `- Target path: \`${stringOrFallback(payload?.targetPath, "unknown")}\``,
208
+ `- Scope: ${stringOrFallback(payload?.pathKind, "unknown")}`,
209
+ `- Since: \`${stringOrFallback(payload?.sinceRef, "full available history")}\``,
210
+ `- Tracked files in scope: ${numberOrZero(payload?.trackedFileCount)}`,
211
+ `- Generated at: ${stringOrFallback(payload?.generatedAt, "unknown")}`,
212
+ "",
213
+ "## Why Start Here",
214
+ stringOrFallback(payload?.summary, "Start with the highest-signal files and owners in this area."),
215
+ "",
216
+ "## Why This Area Changed",
217
+ stringOrFallback(payload?.whyChanged, "No why-changed summary was derived."),
218
+ "",
219
+ "## Commit Themes",
220
+ readCommitThemes(payload?.commitThemes).length > 0
221
+ ? readCommitThemes(payload?.commitThemes)
222
+ .map((theme) => `- ${theme.label}: ${theme.commits} commits${theme.examples.length > 0 ? ` (${theme.examples.join("; ")})` : ""}`)
223
+ .join("\n")
224
+ : "- No recurring commit themes found.",
225
+ "",
226
+ "## Reading Order",
227
+ readReadingOrder(payload?.readingOrder).length > 0
228
+ ? readReadingOrder(payload?.readingOrder)
229
+ .map((entry, index) => `${index + 1}. \`${entry.path}\` — ${entry.reason}`)
230
+ .join("\n")
231
+ : "- No reading order was derived.",
232
+ "",
233
+ "## Hot Files",
234
+ readHotspotFiles(payload?.hotspotFiles).length > 0
235
+ ? readHotspotFiles(payload?.hotspotFiles)
236
+ .map((file) => `- \`${file.filePath}\` — score ${file.score}, ${file.touchedCommits} commits, +${file.additions}/-${file.deletions}`)
237
+ .join("\n")
238
+ : "- No hotspot files found in this scope.",
239
+ "",
240
+ "## Subdirectories",
241
+ readDirectoryStats(payload?.directoryStats).length > 0
242
+ ? readDirectoryStats(payload?.directoryStats)
243
+ .map((directory) => `- \`${directory.directoryPath}\` — ${directory.fileCount} files, ${directory.touchedCommits} commits, score ${directory.score}`)
244
+ .join("\n")
245
+ : "- No subdirectory summaries found.",
246
+ "",
247
+ "## Key Owners",
248
+ readOwners(payload?.owners).length > 0
249
+ ? readOwners(payload?.owners)
250
+ .map((owner) => `- ${owner.author}: ${owner.commits} commits, last touched ${owner.lastTouchedAt} — ${owner.lastSubject}`)
251
+ .join("\n")
252
+ : "- No ownership history found.",
253
+ "",
254
+ "## File Ownership Map",
255
+ readFileOwners(payload?.fileOwners).length > 0
256
+ ? readFileOwners(payload?.fileOwners)
257
+ .map((owner) => `- \`${owner.filePath}\` — ${owner.primaryOwner} (${owner.ownerCommits} commits, ${owner.touchedCommits} touches)`)
258
+ .join("\n")
259
+ : "- No file-level ownership map found.",
260
+ "",
261
+ "## Recent Commits",
262
+ readCommits(payload?.recentCommits).length > 0
263
+ ? readCommits(payload?.recentCommits)
264
+ .map((commit) => `- ${commit.date} \`${shortSha(commit.sha)}\` ${commit.author} — ${commit.subject}`)
265
+ .join("\n")
266
+ : "- No recent commits found.",
267
+ "",
268
+ "## Rename / Move History",
269
+ readRenameHistory(payload?.renameHistory).length > 0
270
+ ? readRenameHistory(payload?.renameHistory)
271
+ .map((entry) => `- ${shortSha(entry.commit)}: \`${entry.fromPath}\` -> \`${entry.toPath}\``)
272
+ .join("\n")
273
+ : "- No rename or move history found.",
274
+ ];
275
+
276
+ return `${sections.join("\n")}\n`;
277
+ }
278
+
279
+ function buildTraceMarkdown(payload) {
280
+ const sections = [
281
+ "# RepoLens Trace Report",
282
+ "",
283
+ `- Repo root: \`${stringOrFallback(payload?.repoRoot, "unknown")}\``,
284
+ `- Branch: \`${stringOrFallback(payload?.branch, "unknown")}\``,
285
+ `- Query: \`${stringOrFallback(payload?.query, "unknown")}\``,
286
+ `- Since: \`${stringOrFallback(payload?.sinceRef, "full available history")}\``,
287
+ `- Generated at: ${stringOrFallback(payload?.generatedAt, "unknown")}`,
288
+ "",
289
+ "## Matches",
290
+ ];
291
+
292
+ const matches = readTraceMatches(payload?.matches);
293
+ if (matches.length === 0) {
294
+ sections.push("- No matching files found.");
295
+ return `${sections.join("\n")}\n`;
296
+ }
297
+
298
+ for (const match of matches) {
299
+ sections.push(`- \`${match.filePath}\` (score ${numberOrZero(match.score)}, primary ${stringOrFallback(match.matchType, "path")} match)`);
300
+ const scoreParts = [
301
+ numberOrZero(match.scoreBreakdown?.definition) > 0 ? `definition +${match.scoreBreakdown.definition}` : null,
302
+ numberOrZero(match.scoreBreakdown?.symbol) > 0 ? `symbol +${match.scoreBreakdown.symbol}` : null,
303
+ numberOrZero(match.scoreBreakdown?.content) > 0 ? `content +${match.scoreBreakdown.content}` : null,
304
+ numberOrZero(match.scoreBreakdown?.path) > 0 ? `path +${match.scoreBreakdown.path}` : null,
305
+ numberOrZero(match.scoreBreakdown?.hotspot) > 0 ? `hotspot +${match.scoreBreakdown.hotspot}` : null,
306
+ numberOrZero(match.scoreBreakdown?.history) > 0 ? `history +${match.scoreBreakdown.history}` : null,
307
+ numberOrZero(match.scoreBreakdown?.ownership) > 0 ? `ownership +${match.scoreBreakdown.ownership}` : null,
308
+ numberOrZero(match.scoreBreakdown?.blame) > 0 ? `blame +${match.scoreBreakdown.blame}` : null,
309
+ numberOrZero(match.scoreBreakdown?.rename) > 0 ? `rename +${match.scoreBreakdown.rename}` : null,
310
+ ].filter(Boolean);
311
+
312
+ sections.push(` Match signals: ${readStringArray(match.matchSignals).join(", ") || stringOrFallback(match.matchType, "path")}`);
313
+ sections.push(` Score: ${scoreParts.join(", ") || "base match"}`);
314
+ if (readMatchedLines(match.matchedLines).length > 0) {
315
+ sections.push(` Matches: ${readMatchedLines(match.matchedLines).slice(0, 2).map((entry) => `${entry.lineNumber}: ${entry.preview}`).join("; ")}`);
316
+ }
317
+ if (readOwners(match.ownership?.owners).length > 0) {
318
+ sections.push(` Owners: ${readOwners(match.ownership.owners).slice(0, 2).map((owner) => `${owner.author} (${owner.commits})`).join(", ")}`);
319
+ }
320
+ if (readCommits(match.evolution?.commits).length > 0) {
321
+ sections.push(` Recent: ${readCommits(match.evolution.commits).slice(0, 3).map((commit) => `${commit.date} ${shortSha(commit.sha)} ${commit.subject}`).join("; ")}`);
322
+ }
323
+ if (readRenameHistory(match.evolution?.renameHistory).length > 0) {
324
+ const latestRename = readRenameHistory(match.evolution.renameHistory)[0];
325
+ sections.push(` Rename: ${latestRename.fromPath} -> ${latestRename.toPath}`);
326
+ }
327
+ }
328
+
329
+ return `${sections.join("\n")}\n`;
330
+ }
331
+
332
+ function readCommits(value) {
333
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.sha === "string") : [];
334
+ }
335
+
336
+ function readAuthors(value) {
337
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.author === "string") : [];
338
+ }
339
+
340
+ function readFileStats(value) {
341
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.filePath === "string") : [];
342
+ }
343
+
344
+ function readOwners(value) {
345
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.author === "string") : [];
346
+ }
347
+
348
+ function readBlameOwners(value) {
349
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.author === "string") : [];
350
+ }
351
+
352
+ function readCommitThemes(value) {
353
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.label === "string") : [];
354
+ }
355
+
356
+ function readFileOwners(value) {
357
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.filePath === "string" && typeof entry.primaryOwner === "string") : [];
358
+ }
359
+
360
+ function readRenameHistory(value) {
361
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.fromPath === "string" && typeof entry.toPath === "string") : [];
362
+ }
363
+
364
+ function readDirectoryStats(value) {
365
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.directoryPath === "string") : [];
366
+ }
367
+
368
+ function readHotspotFiles(value) {
369
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.filePath === "string") : [];
370
+ }
371
+
372
+ function readTraceMatches(value) {
373
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.filePath === "string") : [];
374
+ }
375
+
376
+ function readMatchedLines(value) {
377
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.filePath === "string" && Number.isFinite(entry.lineNumber)) : [];
378
+ }
379
+
380
+ function readReadingOrder(value) {
381
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry.path === "string" && typeof entry.reason === "string") : [];
382
+ }
383
+
384
+ function readStringArray(value) {
385
+ return Array.isArray(value) ? value.map((entry) => (typeof entry === "string" ? entry : "")).filter(Boolean) : [];
386
+ }
387
+
388
+ function readString(value) {
389
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
390
+ }
391
+
392
+ function dedupeStrings(values) {
393
+ return Array.from(new Set(values.filter(Boolean)));
394
+ }
395
+
396
+ function arrayLength(value) {
397
+ return Array.isArray(value) ? value.length : 0;
398
+ }
399
+
400
+ function numberOrZero(value) {
401
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
402
+ }
403
+
404
+ function stringOrFallback(value, fallback) {
405
+ return typeof value === "string" && value.length > 0 ? value : fallback;
406
+ }
407
+
408
+ function shortSha(value) {
409
+ return typeof value === "string" ? value.slice(0, 7) : "unknown";
410
+ }
@@ -0,0 +1,15 @@
1
+ export function parseRepoLensProtocolUrl(rawUrl: string): {
2
+ command: string;
3
+ boardId: string | null;
4
+ source: string | null;
5
+ };
6
+
7
+ export function isSafeRepoLensCommand(command: string): boolean;
8
+
9
+ export function buildTerminalLaunchScript(input: {
10
+ command: string;
11
+ cliEntrypoint: string;
12
+ nodePath: string;
13
+ repoRoot?: string | null;
14
+ source?: string | null;
15
+ }): string;