@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@et0and/ovid",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Browse a repository's files by semantic meaning",
5
5
  "type": "module",
6
6
  "bin": {
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": "semantic-navigator/1.0.0",
78
- "Editor-Plugin-Version": "semantic-navigator/1.0.0",
79
- "User-Agent": "semantic-navigator",
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
- lastError = new Error(`Copilot API error: HTTP ${resp.status} ${resp.statusText}`)
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("semantic-navigator")
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-4o-mini")
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
- let labels = await labelFiles(config, entries)
99
-
100
- // Guard: align label count with entry count (Copilot may return fewer)
101
- if (labels.length < entries.length) {
102
- const missing = entries.length - labels.length
103
- labels = [
104
- ...labels,
105
- ...Array.from({ length: missing }, () => ({
106
- overarchingTheme: "",
107
- distinguishingFeature: "",
108
- label: "unlabelled",
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}: ${labels[i]!.label}`,
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: " semantic-navigator ",
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 = ` semantic-navigator ${statusLine}`
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
- ` semantic-navigator ${tree.label} (${tree.files.length} files)`
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
  }