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:
@@ -0,0 +1,38 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { sliceDocument, applySelectedSuggestion, repoAndPath, statusText } from '../dist/logic.js'
|
||||
|
||||
test('slices at the cursor', () => {
|
||||
const s = sliceDocument('abc\ndef', 5, 1000, 1000)
|
||||
assert.deepEqual(s, { prefix: 'abc\nd', suffix: 'ef', prefixLineOffset: 0 })
|
||||
})
|
||||
|
||||
test('large prefixes are cut at a stable line chunk', () => {
|
||||
const doc = Array.from({ length: 2000 }, (_, i) => `line ${i}`).join('\n')
|
||||
const a = sliceDocument(doc, doc.length - 5, 5000, 100, 256)
|
||||
const b = sliceDocument(doc, doc.length - 2, 5000, 100, 256) // typed 3 more chars
|
||||
assert.equal(a.prefixLineOffset % 256, 0)
|
||||
assert.equal(a.prefixLineOffset, b.prefixLineOffset)
|
||||
assert.ok(a.prefix.startsWith(`line ${a.prefixLineOffset}\n`))
|
||||
assert.ok(a.prefix.length <= 5000 + 20 * 256)
|
||||
})
|
||||
|
||||
test('suffix is cut at a line boundary', () => {
|
||||
const s = sliceDocument('x' + '\nabcdef'.repeat(10), 1, 1000, 15)
|
||||
assert.ok(s.suffix.endsWith('\n'))
|
||||
})
|
||||
|
||||
test('selected IntelliSense item is treated as typed', () => {
|
||||
const r = applySelectedSuggestion('this.conn', 5, 9, 'connections')
|
||||
assert.deepEqual(r, { text: 'this.connections', cursorOffset: 16 })
|
||||
})
|
||||
|
||||
test('repo/path normalisation', () => {
|
||||
assert.deepEqual(repoAndPath('wrapped.cash', 'src\\a\\b.ts', '/x/src/a/b.ts'), { repo: 'wrapped.cash', path: 'src/a/b.ts' })
|
||||
assert.deepEqual(repoAndPath(undefined, '', '/tmp/scratch.ts'), { repo: 'untitled', path: 'scratch.ts' })
|
||||
})
|
||||
|
||||
test('status text', () => {
|
||||
assert.match(statusText({ enabled: true, mode: 'symbols', effectiveMode: 'simple', model: 'stage-c' }, 41.6), /symbols→simple · 42 ms/)
|
||||
assert.match(statusText(null, null), /no daemon/)
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
// End-to-end test of managed mode without VS Code: local "git server" (with Range support),
|
||||
// resumable + verified downloads, llama.cpp fetch/unpack, daemon start/reuse/replace.
|
||||
// node test/managed.e2e.mjs <release-out-dir> <scratch-dir>
|
||||
import http from 'node:http'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import assert from 'node:assert/strict'
|
||||
import { createRequire } from 'node:module'
|
||||
const core = createRequire(import.meta.url)('../dist/managed-core.js')
|
||||
|
||||
const [releaseDir, scratch] = process.argv.slice(2)
|
||||
const storage = path.join(scratch, 'storage')
|
||||
const daemonDir = path.resolve('dist/daemon')
|
||||
fs.rmSync(storage, { recursive: true, force: true })
|
||||
|
||||
let rangeRequests = 0
|
||||
const server = http.createServer((req, res) => {
|
||||
const file = path.join(releaseDir, decodeURIComponent(new URL(req.url, 'http://x').pathname))
|
||||
if (!fs.existsSync(file)) { res.writeHead(404); return res.end() }
|
||||
const size = fs.statSync(file).size
|
||||
const m = /bytes=(\d+)-/.exec(req.headers.range ?? '')
|
||||
if (m) {
|
||||
rangeRequests++
|
||||
const start = Number(m[1])
|
||||
res.writeHead(206, { 'content-length': size - start, 'content-range': `bytes ${start}-${size - 1}/${size}` })
|
||||
return fs.createReadStream(file, { start }).pipe(res)
|
||||
}
|
||||
res.writeHead(200, { 'content-length': size })
|
||||
fs.createReadStream(file).pipe(res)
|
||||
}).listen(7120)
|
||||
const manifestUrl = 'http://127.0.0.1:7120/manifest.json'
|
||||
const settings = { port: 7111, manifestUrl, model: '', modelPath: '', modelProfile: { order: 'spm', modes: ['simple'], defaultMode: 'simple' }, llamaServerPath: '', llamaServerArgs: [], recordingDir: '', idleShutdownMinutes: 30 }
|
||||
const log = []
|
||||
const progress = msg => { if (!log.includes(msg.replace(/\d+ \/ /, 'N / '))) log.push(msg.replace(/\d+ \/ /, 'N / ')) }
|
||||
const step = (name, fn) => fn().then(r => { console.log(`PASS ${name}`); return r }, e => { console.log(`FAIL ${name}: ${e.message}`); process.exitCode = 1; throw e })
|
||||
|
||||
// 1. Simulate an interrupted model download: first 100 MB already on disk.
|
||||
fs.mkdirSync(path.join(storage, 'models'), { recursive: true })
|
||||
const modelSrc = path.join(releaseDir, 'stage-c-q8_0.gguf')
|
||||
const fd = fs.openSync(modelSrc, 'r'); const buf = Buffer.alloc(100e6); fs.readSync(fd, buf, 0, buf.length, 0); fs.closeSync(fd)
|
||||
fs.writeFileSync(path.join(storage, 'models', 'stage-c-q8_0.gguf.part'), buf)
|
||||
|
||||
let t0 = Date.now()
|
||||
const prepared = await step('first run: manifest, resumed + verified model download, llama.cpp download + unpack', () => core.prepare(storage, daemonDir, '0.4.0-test', settings, progress))
|
||||
console.log(` ${((Date.now() - t0) / 1000).toFixed(1)} s; range requests: ${rangeRequests}; model verified: ${fs.existsSync(path.join(storage, 'models', 'stage-c-q8_0.gguf.verified'))}`)
|
||||
assert.ok(rangeRequests >= 1, 'download should have resumed with a Range request')
|
||||
const cfg = JSON.parse(fs.readFileSync(prepared.configPath, 'utf8'))
|
||||
assert.ok(fs.existsSync(cfg.llamaServer.binary), 'llama-server unpacked')
|
||||
|
||||
await step('daemon starts on the editor runtime', async () => assert.equal(await core.ensureRunning(storage, daemonDir, prepared, settings.port, progress), 'started'))
|
||||
const fim = await (await fetch('http://127.0.0.1:7111/v1/fim', { method: 'POST', body: JSON.stringify({ repo: 'e2e', path: 'src/a.ts', prefix: "import { z } from 'zod'\nexport const s = z.object({ a: z.string() })\nexport type S = ", suffix: '\n' }) })).json()
|
||||
await step(`completion through the managed daemon (${JSON.stringify(fim.text)})`, async () => assert.match(fim.text, /z\.infer<typeof s>/))
|
||||
|
||||
t0 = Date.now()
|
||||
const again = await step('second window: no re-download, daemon reused', async () => {
|
||||
const p = await core.prepare(storage, daemonDir, '0.4.0-test', settings, () => {})
|
||||
assert.equal(await core.ensureRunning(storage, daemonDir, p, settings.port, () => {}), 'reused')
|
||||
return p
|
||||
})
|
||||
console.log(` ${((Date.now() - t0) / 1000).toFixed(2)} s`)
|
||||
|
||||
await step('changed settings replace the managed daemon', async () => {
|
||||
const p = await core.prepare(storage, daemonDir, '0.4.0-test', { ...settings, idleShutdownMinutes: 31 }, () => {})
|
||||
assert.notEqual(p.instanceTag, again.instanceTag)
|
||||
assert.equal(await core.ensureRunning(storage, daemonDir, p, settings.port, () => {}), 'started')
|
||||
})
|
||||
|
||||
await step('checksum mismatch is rejected and the partial file removed', async () => {
|
||||
const bad = path.join(storage, 'bad.bin')
|
||||
await assert.rejects(core.ensureFile('http://127.0.0.1:7120/NOTICE', bad, '0'.repeat(64), 'NOTICE', () => {}), /checksum mismatch/)
|
||||
assert.ok(!fs.existsSync(bad) && !fs.existsSync(`${bad}.part`))
|
||||
})
|
||||
|
||||
console.log('progress messages:', log.filter(l => !l.startsWith('downloading')).join(' | '))
|
||||
await fetch('http://127.0.0.1:7111/v1/shutdown', { method: 'POST' }).catch(() => {})
|
||||
server.close()
|
||||
Reference in New Issue
Block a user