Restructure into a monorepo: models, VS Code extension, daemon, shared

- models/: GGUFs (Git LFS), manifest.json, model card, Apache-2.0 LICENSE + NOTICE
- extensions/vscode/: the Local Autocomplete extension (v0.5.0, publisher harvmaster)
- daemon/: acd completion daemon (bundled into the extension; also runs standalone)
- shared/: prompt layout + symbol extraction shared with the training pipeline
- scripts/: make-model-release.mjs, publish-extension-release.mjs (Gitea releases)
- README with quick start, settings, architecture, build and release instructions;
  MIT license for code; CHANGELOG

The default model manifest URL moves to models/manifest.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 15:37:19 +00:00
co-authored by Claude Opus 5.5
parent f8dcf1b7df
commit fa8f775779
52 changed files with 309630 additions and 207 deletions
+65
View File
@@ -0,0 +1,65 @@
/**
* Pure request-building logic (no vscode import), unit-tested in test/.
*/
export interface DocumentSlice {
prefix: string
suffix: string
/** Absolute line number where `prefix` starts (non-zero only when truncated). */
prefixLineOffset: number
}
/**
* Splits the document at the cursor. Very large prefixes are cut at a line
* whose number is a multiple of `lineChunk`, so the cut point (and the daemon's
* KV cache) stays stable while typing.
*/
export function sliceDocument(text: string, offset: number, maxPrefixChars: number, maxSuffixChars: number, lineChunk = 256): DocumentSlice {
let prefix = text.slice(0, offset)
let prefixLineOffset = 0
if (prefix.length > maxPrefixChars) {
const lineStarts = [0]
for (let i = prefix.indexOf('\n'); i !== -1; i = prefix.indexOf('\n', i + 1)) lineStarts.push(i + 1)
let line = 0
while (line + lineChunk < lineStarts.length && prefix.length - lineStarts[line] > maxPrefixChars) line += lineChunk
prefix = prefix.slice(lineStarts[line])
prefixLineOffset = line
}
let suffix = text.slice(offset, offset + maxSuffixChars)
if (offset + maxSuffixChars < text.length) suffix = suffix.slice(0, suffix.lastIndexOf('\n') + 1)
return { prefix, suffix, prefixLineOffset }
}
/**
* When VS Code's suggest widget is open, an inline completion must extend the
* selected suggestion. We pretend the selected text was already typed: the
* prefix ends at the replace range's start plus the selected text.
*/
export function applySelectedSuggestion(text: string, rangeStartOffset: number, cursorOffset: number, selectedText: string): { text: string; cursorOffset: number } {
return {
text: text.slice(0, rangeStartOffset) + selectedText + text.slice(cursorOffset),
cursorOffset: rangeStartOffset + selectedText.length,
}
}
/** Repo name and '/'-separated relative path, as the model saw them in training. */
export function repoAndPath(workspaceFolderName: string | undefined, relativePath: string, fileName: string): { repo: string; path: string } {
if (!workspaceFolderName) return { repo: 'untitled', path: fileName.split(/[\\/]/).pop() || 'untitled' }
return { repo: workspaceFolderName, path: relativePath.replace(/\\/g, '/') }
}
export type ContextMode = 'symbols' | 'simple' | 'raw'
export const MODE_DESCRIPTIONS: Record<ContextMode, string> = {
symbols: 'repo header + available symbols from the TypeScript language service',
simple: 'repo name + file path header only (fast, no language service)',
raw: 'no header: plain suffix-first FIM (for models trained without it)',
}
export function statusText(cfg: { enabled: boolean; mode: ContextMode; effectiveMode?: ContextMode; model: string; llama?: { loading: boolean; running: boolean } } | null, lastMs: number | null): string {
if (!cfg) return '$(debug-disconnect) AC: no daemon'
if (!cfg.enabled) return '$(circle-slash) AC: off'
if (cfg.llama?.loading) return '$(loading~spin) AC: loading'
const mode = cfg.effectiveMode && cfg.effectiveMode !== cfg.mode ? `${cfg.mode}→${cfg.effectiveMode}` : cfg.mode
return `$(sparkle) AC: ${mode}${lastMs !== null ? ` · ${Math.round(lastMs)} ms` : ''}`
}