Skip to content

Commit ce1ebf2

Browse files
BrainSlugs83Copilot
andcommitted
Add CI/CD workflows, Dependabot, 100% test coverage
- CI workflow: lint + test on push/PR, Node 20+22 matrix - Publish workflow: auto-publish to npm on GitHub Release via OIDC Trusted Publishing (no tokens) - Dependabot: weekly npm dependency checks - Enforce 100% line/branch/function coverage in test script - Add 6 new tests (userPort, ping identity, null sessionStore) — 44 total - Fix ESLint config: add missing clearTimeout global - README: add CI + npm badges, /mcp reload guidance, update test counts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2e1e57a commit ce1ebf2

File tree

7 files changed

+117
-7
lines changed

7 files changed

+117
-7
lines changed

.github/dependabot.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: npm
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
open-pull-requests-limit: 5

.github/workflows/ci.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
check:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
node-version: [20, 22]
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-node@v4
18+
with:
19+
node-version: ${{ matrix.node-version }}
20+
- run: npm ci
21+
- run: npm run check

.github/workflows/publish.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Publish to npm
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
id-token: write
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- uses: actions/setup-node@v4
17+
with:
18+
node-version: 22
19+
registry-url: https://registry.npmjs.org
20+
21+
- run: npm ci
22+
23+
# Set package version from release tag (e.g. v1.6.0 → 1.6.0)
24+
- name: Set version from tag
25+
run: |
26+
VERSION="${GITHUB_REF_NAME#v}"
27+
npm version "$VERSION" --no-git-tag-version --allow-same-version
28+
29+
- run: npm run check
30+
31+
- run: npm publish --provenance --access public

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# vector-memory
22

3+
[![CI](https://github.com/BrainSlugs83/GithubCopilotCLI-VectorMemoryMCP/actions/workflows/ci.yml/badge.svg)](https://github.com/BrainSlugs83/GithubCopilotCLI-VectorMemoryMCP/actions/workflows/ci.yml)
4+
[![npm version](https://img.shields.io/npm/v/ghcp-cli-vector-memory-mcp)](https://www.npmjs.com/package/ghcp-cli-vector-memory-mcp)
5+
36
An [MCP](https://modelcontextprotocol.io/) server that adds **persistent long-term memory** to [**GitHub Copilot CLI**](https://docs.github.com/en/copilot/github-copilot-in-the-cli) via local semantic vector search. Copilot can recall past conversations, code changes, and decisions across all sessions — by meaning, not just keywords.
47

58
> **Note:** This is a community project and is not affiliated with or endorsed by GitHub. [GitHub Copilot CLI](https://docs.github.com/en/copilot/github-copilot-in-the-cli) is a product of GitHub / Microsoft.
@@ -73,9 +76,9 @@ GitHub Copilot CLI reads MCP server definitions from a JSON config file. The **u
7376

7477
> **You do not need to clone this repo or run `npm install` yourself.** The `npx -y` command automatically downloads, installs, and runs the package from the npm registry. It caches the package locally so subsequent launches are fast.
7578
76-
### Step 3: Restart Copilot CLI
79+
### Step 3: Load the server
7780

78-
Close any running Copilot CLI session and start a new one. The MCP server will launch automatically in the background.
81+
Close any running Copilot CLI session and start a new one**or** if you already have a session open, type `/mcp reload` to pick up the new config without restarting. The MCP server will launch automatically in the background.
7982

8083
> [!IMPORTANT]
8184
> **The very first launch takes a few minutes.** On first run, `npx` installs the package and its
@@ -190,7 +193,7 @@ copilot.exe ──STDIO──▶ index.js (proxy) ──HTTP──▶ vector-mem
190193

191194
```bash
192195
npm run lint # ESLint on all source files
193-
npm test # 38 unit tests (node:test, zero external deps)
196+
npm test # 44 unit tests with 100% coverage (node:test, zero external deps)
194197
npm run check # lint + test
195198
```
196199

@@ -203,7 +206,7 @@ npm test
203206
With coverage:
204207

205208
```bash
206-
node --test --experimental-test-coverage test.js
209+
npm test # coverage is enforced at 100% by default
207210
```
208211

209212
### File overview
@@ -214,7 +217,7 @@ node --test --experimental-test-coverage test.js
214217
| `vector-memory-server.js` | HTTP singleton — owns model, DB, indexing |
215218
| `embed-worker.js` | Worker thread for ONNX embedding inference |
216219
| `lib.js` | Pure logic: filtering, dedup, scoring, handler factory |
217-
| `test.js` | 38 unit tests with DI mocks |
220+
| `test.js` | 44 unit tests with DI mocks, 100% coverage enforced |
218221
| `eslint.config.js` | Lint config |
219222

220223
### Manual server management

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default [
1313
setTimeout: "readonly",
1414
setInterval: "readonly",
1515
clearInterval: "readonly",
16+
clearTimeout: "readonly",
1617
URL: "readonly",
1718
},
1819
},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
],
3939
"scripts": {
4040
"lint": "eslint index.js vector-memory-server.js embed-worker.js lib.js",
41-
"test": "node --test test.js",
41+
"test": "node --test --experimental-test-coverage --test-coverage-lines=100 --test-coverage-branches=100 --test-coverage-functions=100 --test-coverage-exclude=test.js test.js",
4242
"check": "npm run lint && npm run test"
4343
},
4444
"dependencies": {

test.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, it } from "node:test";
22
import assert from "node:assert/strict";
33
import { Readable } from "node:stream";
4-
import { filterUnindexed, dedup, postProcessResults, isOurServer, isIndexable, MIN_SCORE, createHandler } from "./lib.js";
4+
import { filterUnindexed, dedup, postProcessResults, isOurServer, isIndexable, userPort, BASE_PORT, MIN_SCORE, createHandler } from "./lib.js";
55

66
// --- Mock helpers for handler tests ---
77

@@ -50,6 +50,27 @@ function makeDeps(overrides = {}) {
5050
};
5151
}
5252

53+
describe("userPort", () => {
54+
it("returns a number within the expected range", () => {
55+
const port = userPort("testuser");
56+
assert.ok(port >= BASE_PORT, `port ${port} should be >= ${BASE_PORT}`);
57+
assert.ok(port <= BASE_PORT + 0xFFF, `port ${port} should be <= ${BASE_PORT + 0xFFF}`);
58+
});
59+
60+
it("is deterministic for the same username", () => {
61+
assert.equal(userPort("alice"), userPort("alice"));
62+
});
63+
64+
it("is case-insensitive", () => {
65+
assert.equal(userPort("Alice"), userPort("alice"));
66+
});
67+
68+
it("returns different ports for different usernames", () => {
69+
// Not guaranteed for all pairs, but statistically overwhelmingly likely
70+
assert.notEqual(userPort("alice"), userPort("bob"));
71+
});
72+
});
73+
5374
describe("filterUnindexed", () => {
5475
it("returns items not in the existing index", () => {
5576
const all = [
@@ -251,6 +272,19 @@ describe("handleRequest - /ping", () => {
251272
assert.equal(res.statusCode, 200);
252273
assert.deepEqual(res.body, { ok: true });
253274
});
275+
276+
it("includes identity when getIdentity is provided", async () => {
277+
const deps = makeDeps({
278+
getIdentity: () => ({ user: "testuser", version: "1.0.0" }),
279+
});
280+
const handler = createHandler(deps);
281+
const res = mockRes();
282+
await handler(mockReq("POST", "/ping"), res);
283+
assert.equal(res.statusCode, 200);
284+
assert.equal(res.body.ok, true);
285+
assert.equal(res.body.user, "testuser");
286+
assert.equal(res.body.version, "1.0.0");
287+
});
254288
});
255289

256290
describe("handleRequest - routing", () => {
@@ -343,6 +377,19 @@ describe("handleRequest - /search", () => {
343377
await handler(mockReq("POST", "/search", { query: "test" }), mockRes());
344378
assert.ok(deps._closedDbs.includes("vec"));
345379
});
380+
381+
it("skips indexing when sessionStore is null", async () => {
382+
let indexCalled = false;
383+
const deps = makeDeps({
384+
openSessionStore: () => null,
385+
indexContent: async () => { indexCalled = true; return 1; },
386+
});
387+
const handler = createHandler(deps);
388+
const res = mockRes();
389+
await handler(mockReq("POST", "/search", { query: "test" }), res);
390+
assert.equal(res.statusCode, 200);
391+
assert.ok(!indexCalled, "indexContent should not be called when sessionStore is null");
392+
});
346393
});
347394

348395
describe("handleRequest - /reindex", () => {

0 commit comments

Comments
 (0)