@et0and/ovid 0.0.2 → 0.0.3
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/package.json +1 -1
- package/src/auth.ts +3 -3
- package/src/labels.ts +2 -1
- package/src/main.ts +2 -2
- package/src/tree.ts +20 -15
- package/src/ui.ts +36 -5
package/package.json
CHANGED
package/src/auth.ts
CHANGED
|
@@ -74,9 +74,9 @@ async function fetchCopilotToken(githubToken: string): Promise<{ token: string;
|
|
|
74
74
|
method: "GET",
|
|
75
75
|
headers: {
|
|
76
76
|
Authorization: `token ${githubToken}`,
|
|
77
|
-
"Editor-Version": "
|
|
78
|
-
"Editor-Plugin-Version": "
|
|
79
|
-
"User-Agent": "
|
|
77
|
+
"Editor-Version": "vscode/1.95.0",
|
|
78
|
+
"Editor-Plugin-Version": "copilot/1.246.0",
|
|
79
|
+
"User-Agent": "GitHubCopilotChat/0.22.4",
|
|
80
80
|
},
|
|
81
81
|
})
|
|
82
82
|
|
package/src/labels.ts
CHANGED
|
@@ -106,7 +106,8 @@ async function chatComplete(
|
|
|
106
106
|
})
|
|
107
107
|
|
|
108
108
|
if (!resp.ok) {
|
|
109
|
-
|
|
109
|
+
const errBody = await resp.text().catch(() => "")
|
|
110
|
+
lastError = new Error(`Copilot API error: HTTP ${resp.status} ${resp.statusText} — ${errBody}`)
|
|
110
111
|
// Retry on 5xx
|
|
111
112
|
if (resp.status >= 500) {
|
|
112
113
|
await Bun.sleep(1000 * (attempt + 1))
|
package/src/main.ts
CHANGED
|
@@ -30,10 +30,10 @@ import type { CopilotConfig } from "./labels.ts"
|
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
31
|
|
|
32
32
|
const program = new Command()
|
|
33
|
-
.name("
|
|
33
|
+
.name("ovid")
|
|
34
34
|
.description("Browse a repository's files by semantic meaning")
|
|
35
35
|
.argument("[directory]", "Directory to analyse (default: current working directory)", ".")
|
|
36
|
-
.option("--completion-model <model>", "Copilot model to use for labelling", "gpt-
|
|
36
|
+
.option("--completion-model <model>", "Copilot model to use for labelling", "gpt-5-mini")
|
|
37
37
|
.option("--max-files <n>", "Maximum number of files to index", (v) => parseInt(v, 10), 2000)
|
|
38
38
|
.option("--max-file-bytes <n>", "Skip files larger than this many bytes", (v) => parseInt(v, 10), 1_000_000)
|
|
39
39
|
.option("--exclude-glob <pattern...>", "Glob patterns to exclude (repeatable)", DEFAULT_EXCLUDES)
|
package/src/tree.ts
CHANGED
|
@@ -93,25 +93,30 @@ export async function labelNodes(
|
|
|
93
93
|
const children = splitCluster(cluster)
|
|
94
94
|
|
|
95
95
|
if (children.length === 1) {
|
|
96
|
-
// Leaf cluster: label each file individually
|
|
96
|
+
// Leaf cluster: label each file individually, in batches of 5 to stay
|
|
97
|
+
// within the model's prompt token limit.
|
|
97
98
|
const entries = cluster.entries
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
99
|
+
const BATCH = 5
|
|
100
|
+
const allLabels: import("./labels.ts").Label[] = []
|
|
101
|
+
for (let i = 0; i < entries.length; i += BATCH) {
|
|
102
|
+
const batch = entries.slice(i, i + BATCH)
|
|
103
|
+
let batchLabels = await labelFiles(config, batch)
|
|
104
|
+
if (batchLabels.length < batch.length) {
|
|
105
|
+
const missing = batch.length - batchLabels.length
|
|
106
|
+
batchLabels = [
|
|
107
|
+
...batchLabels,
|
|
108
|
+
...Array.from({ length: missing }, () => ({
|
|
109
|
+
overarchingTheme: "",
|
|
110
|
+
distinguishingFeature: "",
|
|
111
|
+
label: "unlabelled",
|
|
112
|
+
})),
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
allLabels.push(...batchLabels)
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
return entries.map((entry, i) => ({
|
|
114
|
-
label: `${entry.path}: ${
|
|
119
|
+
label: `${entry.path}: ${allLabels[i]!.label}`,
|
|
115
120
|
files: [entry.path],
|
|
116
121
|
children: [],
|
|
117
122
|
}))
|
package/src/ui.ts
CHANGED
|
@@ -42,6 +42,8 @@ const THEME = {
|
|
|
42
42
|
statusColor: "#bb9af7",
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
46
|
+
|
|
45
47
|
// ---------------------------------------------------------------------------
|
|
46
48
|
// Flat node model for scroll-based rendering
|
|
47
49
|
// ---------------------------------------------------------------------------
|
|
@@ -79,7 +81,7 @@ function nodeKey(tree: Tree, depth: number): string {
|
|
|
79
81
|
// ---------------------------------------------------------------------------
|
|
80
82
|
|
|
81
83
|
export interface ProgressState {
|
|
82
|
-
phase: "reading" | "embedding" | "clustering" | "labelling" | "done"
|
|
84
|
+
phase: "reading" | "embedding" | "clustering" | "labelling" | "working" | "done"
|
|
83
85
|
done: number
|
|
84
86
|
total: number
|
|
85
87
|
message?: string
|
|
@@ -104,6 +106,8 @@ export class SemanticNavigatorUI {
|
|
|
104
106
|
|
|
105
107
|
// Progress state (shown before tree is ready)
|
|
106
108
|
private progress: ProgressState = { phase: "reading", done: 0, total: 0 }
|
|
109
|
+
private spinnerTimer: ReturnType<typeof setInterval> | null = null
|
|
110
|
+
private spinnerIndex = 0
|
|
107
111
|
|
|
108
112
|
// Row renderables (reused by rebuildRows)
|
|
109
113
|
private rowRenderables: TextRenderable[] = []
|
|
@@ -144,7 +148,7 @@ export class SemanticNavigatorUI {
|
|
|
144
148
|
})
|
|
145
149
|
this.headerText = new TextRenderable(this.renderer, {
|
|
146
150
|
id: "header-text",
|
|
147
|
-
content: "
|
|
151
|
+
content: " ovid ",
|
|
148
152
|
fg: THEME.statusColor,
|
|
149
153
|
})
|
|
150
154
|
headerBox.add(this.headerText)
|
|
@@ -277,6 +281,11 @@ export class SemanticNavigatorUI {
|
|
|
277
281
|
|
|
278
282
|
updateProgress(state: ProgressState): void {
|
|
279
283
|
this.progress = state
|
|
284
|
+
if (state.phase === "working") {
|
|
285
|
+
this.startSpinner()
|
|
286
|
+
} else {
|
|
287
|
+
this.stopSpinner()
|
|
288
|
+
}
|
|
280
289
|
this.renderProgress()
|
|
281
290
|
}
|
|
282
291
|
|
|
@@ -290,6 +299,7 @@ export class SemanticNavigatorUI {
|
|
|
290
299
|
case "embedding": phaseLabel = "Embedding"; break
|
|
291
300
|
case "clustering": phaseLabel = "Clustering"; break
|
|
292
301
|
case "labelling": phaseLabel = "Labelling"; break
|
|
302
|
+
case "working": phaseLabel = "Working"; break
|
|
293
303
|
default: phaseLabel = "Done"; break
|
|
294
304
|
}
|
|
295
305
|
|
|
@@ -297,14 +307,17 @@ export class SemanticNavigatorUI {
|
|
|
297
307
|
? `${phaseLabel}: ${done}/${total} (${pct}%)`
|
|
298
308
|
: message ?? phaseLabel
|
|
299
309
|
|
|
300
|
-
this.headerText.content = `
|
|
310
|
+
this.headerText.content = ` ovid ${statusLine}`
|
|
301
311
|
|
|
302
312
|
// Clear rows and show a single status line
|
|
303
313
|
this.clearRows()
|
|
304
314
|
const id = "status-line"
|
|
315
|
+
const spinner = phase === "working"
|
|
316
|
+
? `${SPINNER_FRAMES[this.spinnerIndex % SPINNER_FRAMES.length] ?? "•"} `
|
|
317
|
+
: ""
|
|
305
318
|
const statusText = new TextRenderable(this.renderer, {
|
|
306
319
|
id,
|
|
307
|
-
content: ` ${statusLine}…`,
|
|
320
|
+
content: ` ${spinner}${statusLine}…`,
|
|
308
321
|
fg: THEME.statusColor,
|
|
309
322
|
})
|
|
310
323
|
this.scrollBox.add(statusText)
|
|
@@ -312,11 +325,28 @@ export class SemanticNavigatorUI {
|
|
|
312
325
|
this.rowIds = [id]
|
|
313
326
|
}
|
|
314
327
|
|
|
328
|
+
private startSpinner(): void {
|
|
329
|
+
if (this.spinnerTimer !== null) return
|
|
330
|
+
this.spinnerTimer = setInterval(() => {
|
|
331
|
+
if (this.progress.phase !== "working") return
|
|
332
|
+
this.spinnerIndex = (this.spinnerIndex + 1) % SPINNER_FRAMES.length
|
|
333
|
+
this.renderProgress()
|
|
334
|
+
}, 80)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private stopSpinner(): void {
|
|
338
|
+
if (this.spinnerTimer === null) return
|
|
339
|
+
clearInterval(this.spinnerTimer)
|
|
340
|
+
this.spinnerTimer = null
|
|
341
|
+
this.spinnerIndex = 0
|
|
342
|
+
}
|
|
343
|
+
|
|
315
344
|
// ---------------------------------------------------------------------------
|
|
316
345
|
// Tree display (after tree is ready)
|
|
317
346
|
// ---------------------------------------------------------------------------
|
|
318
347
|
|
|
319
348
|
setTree(tree: Tree): void {
|
|
349
|
+
this.stopSpinner()
|
|
320
350
|
this.tree = tree
|
|
321
351
|
|
|
322
352
|
// Auto-expand root and its direct children
|
|
@@ -326,7 +356,7 @@ export class SemanticNavigatorUI {
|
|
|
326
356
|
this.renderRows()
|
|
327
357
|
|
|
328
358
|
this.headerText.content =
|
|
329
|
-
`
|
|
359
|
+
` ovid ${tree.label} (${tree.files.length} files)`
|
|
330
360
|
}
|
|
331
361
|
|
|
332
362
|
private rebuildFlatList(): void {
|
|
@@ -455,6 +485,7 @@ export class SemanticNavigatorUI {
|
|
|
455
485
|
// ---------------------------------------------------------------------------
|
|
456
486
|
|
|
457
487
|
destroy(): void {
|
|
488
|
+
this.stopSpinner()
|
|
458
489
|
this.renderer.destroy()
|
|
459
490
|
}
|
|
460
491
|
}
|