@projitive/mcp 2.0.1 → 2.0.2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projitive/mcp",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Projitive MCP Server for project and task discovery/update",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -25,14 +25,11 @@
25
25
  "output"
26
26
  ],
27
27
  "dependencies": {
28
- "@duckdb/node-api": "1.5.0-r.1",
29
28
  "@modelcontextprotocol/sdk": "^1.17.5",
30
- "sql.js": "^1.14.1",
31
29
  "zod": "^3.23.8"
32
30
  },
33
31
  "devDependencies": {
34
32
  "@types/node": "^24.3.0",
35
- "@types/sql.js": "^1.4.9",
36
33
  "@vitest/coverage-v8": "^3.2.4",
37
34
  "tsx": "^4.20.5",
38
35
  "typescript": "^5.9.2",
@@ -1,13 +1,8 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import duckdb from "@duckdb/node-api";
4
- import initSqlJs from "sql.js";
5
3
  const STORE_SCHEMA_VERSION = 3;
6
- const SQL_HEADER = Buffer.from("SQLite format 3\0", "utf8");
7
- const sqlRuntimePromise = initSqlJs();
8
4
  const storeCache = new Map();
9
5
  const storeLocks = new Map();
10
- let duckdbConnectionPromise;
11
6
  function defaultViewState(name) {
12
7
  return {
13
8
  name,
@@ -37,17 +32,6 @@ function defaultStore() {
37
32
  function nowIso() {
38
33
  return new Date().toISOString();
39
34
  }
40
- function parseJsonOr(raw, fallback) {
41
- if (typeof raw !== "string" || raw.trim().length === 0) {
42
- return fallback;
43
- }
44
- try {
45
- return JSON.parse(raw);
46
- }
47
- catch {
48
- return fallback;
49
- }
50
- }
51
35
  function normalizeTaskStatus(status) {
52
36
  if (status === "IN_PROGRESS" || status === "BLOCKED" || status === "DONE") {
53
37
  return status;
@@ -57,12 +41,6 @@ function normalizeTaskStatus(status) {
57
41
  function normalizeRoadmapStatus(status) {
58
42
  return status === "done" ? "done" : "active";
59
43
  }
60
- function isSqliteBuffer(data) {
61
- if (data.length < SQL_HEADER.length) {
62
- return false;
63
- }
64
- return data.subarray(0, SQL_HEADER.length).equals(SQL_HEADER);
65
- }
66
44
  function normalizeStore(input) {
67
45
  const base = defaultStore();
68
46
  const meta = input.meta ?? {};
@@ -129,106 +107,6 @@ function normalizeStore(input) {
129
107
  migration_history: Array.isArray(input.migration_history) ? input.migration_history : [],
130
108
  };
131
109
  }
132
- async function migrateSqliteToJson(data) {
133
- const SQL = await sqlRuntimePromise;
134
- const db = new SQL.Database(new Uint8Array(data));
135
- try {
136
- const tasksResult = db.exec(`
137
- SELECT id, title, status, owner, summary, updated_at, links_json, roadmap_refs_json, sub_state_json, blocker_json, COALESCE(record_version, 1)
138
- FROM tasks
139
- `);
140
- const roadmapsResult = db.exec(`
141
- SELECT id, title, status, time, updated_at, COALESCE(record_version, 1)
142
- FROM roadmaps
143
- `);
144
- const metaResult = db.exec(`
145
- SELECT key, value
146
- FROM meta
147
- WHERE key IN ('tasks_version', 'roadmaps_version', 'store_schema_version')
148
- `);
149
- const viewStateResult = db.exec(`
150
- SELECT name, dirty, last_source_version, last_built_at, COALESCE(record_version, 1)
151
- FROM view_state
152
- WHERE name IN ('tasks_markdown', 'roadmaps_markdown')
153
- `);
154
- const tasks = tasksResult[0]?.values?.map((row) => ({
155
- id: String(row[0]),
156
- title: String(row[1]),
157
- status: normalizeTaskStatus(String(row[2])),
158
- owner: String(row[3]),
159
- summary: String(row[4]),
160
- updatedAt: String(row[5]),
161
- links: parseJsonOr(row[6], []),
162
- roadmapRefs: parseJsonOr(row[7], []),
163
- subState: parseJsonOr(row[8], undefined),
164
- blocker: parseJsonOr(row[9], undefined),
165
- recordVersion: Number(row[10]) || 1,
166
- })) ?? [];
167
- const roadmaps = roadmapsResult[0]?.values?.map((row) => ({
168
- id: String(row[0]),
169
- title: String(row[1]),
170
- status: normalizeRoadmapStatus(String(row[2])),
171
- time: row[3] == null ? undefined : String(row[3]),
172
- updatedAt: String(row[4]),
173
- recordVersion: Number(row[5]) || 1,
174
- })) ?? [];
175
- const meta = defaultStore().meta;
176
- const metaRows = metaResult[0]?.values ?? [];
177
- for (const row of metaRows) {
178
- const key = String(row[0]);
179
- const value = Number.parseInt(String(row[1]), 10);
180
- if (!Number.isFinite(value)) {
181
- continue;
182
- }
183
- if (key === "tasks_version")
184
- meta.tasks_version = value;
185
- if (key === "roadmaps_version")
186
- meta.roadmaps_version = value;
187
- if (key === "store_schema_version")
188
- meta.store_schema_version = value;
189
- }
190
- const tasksView = defaultViewState("tasks_markdown");
191
- const roadmapsView = defaultViewState("roadmaps_markdown");
192
- const viewRows = viewStateResult[0]?.values ?? [];
193
- for (const row of viewRows) {
194
- const name = String(row[0]);
195
- const dirty = Number(row[1]) === 1;
196
- const lastSourceVersion = Number(row[2]) || 0;
197
- const lastBuiltAt = String(row[3] ?? "");
198
- const recordVersion = Number(row[4]) || 1;
199
- if (name === "tasks_markdown") {
200
- tasksView.dirty = dirty;
201
- tasksView.lastSourceVersion = lastSourceVersion;
202
- tasksView.lastBuiltAt = lastBuiltAt;
203
- tasksView.recordVersion = recordVersion;
204
- }
205
- if (name === "roadmaps_markdown") {
206
- roadmapsView.dirty = dirty;
207
- roadmapsView.lastSourceVersion = lastSourceVersion;
208
- roadmapsView.lastBuiltAt = lastBuiltAt;
209
- roadmapsView.recordVersion = recordVersion;
210
- }
211
- }
212
- return normalizeStore({
213
- schema: "projitive-json-store",
214
- tasks,
215
- roadmaps,
216
- meta: {
217
- tasks_version: meta.tasks_version,
218
- roadmaps_version: meta.roadmaps_version,
219
- store_schema_version: STORE_SCHEMA_VERSION,
220
- },
221
- view_state: {
222
- tasks_markdown: tasksView,
223
- roadmaps_markdown: roadmapsView,
224
- },
225
- migration_history: [],
226
- });
227
- }
228
- finally {
229
- db.close();
230
- }
231
- }
232
110
  async function persistStore(dbPath, store) {
233
111
  await fs.mkdir(path.dirname(dbPath), { recursive: true });
234
112
  const tempPath = `${dbPath}.tmp-${process.pid}-${Date.now()}`;
@@ -241,10 +119,6 @@ async function loadStoreFromDisk(dbPath) {
241
119
  if (!file || file.length === 0) {
242
120
  return { store: defaultStore(), shouldPersist: true };
243
121
  }
244
- if (isSqliteBuffer(file)) {
245
- const migrated = await migrateSqliteToJson(file);
246
- return { store: migrated, shouldPersist: true };
247
- }
248
122
  const text = file.toString("utf8").trim();
249
123
  if (text.length === 0) {
250
124
  return { store: defaultStore(), shouldPersist: true };
@@ -337,119 +211,6 @@ function normalizeStatusForSort(status) {
337
211
  return 1;
338
212
  return 0;
339
213
  }
340
- async function getDuckdbConnection() {
341
- if (!duckdbConnectionPromise) {
342
- duckdbConnectionPromise = (async () => {
343
- const instance = await duckdb.DuckDBInstance.create(":memory:");
344
- return instance.connect();
345
- })();
346
- }
347
- return duckdbConnectionPromise;
348
- }
349
- function normalizeStoredTaskLike(raw) {
350
- if (!raw || typeof raw !== "object") {
351
- return null;
352
- }
353
- const value = raw;
354
- const id = value.id;
355
- const title = value.title;
356
- if (typeof id !== "string" || typeof title !== "string") {
357
- return null;
358
- }
359
- const statusRaw = typeof value.status === "string" ? value.status : "TODO";
360
- const owner = typeof value.owner === "string" ? value.owner : "";
361
- const summary = typeof value.summary === "string" ? value.summary : "";
362
- const updatedAt = typeof value.updatedAt === "string"
363
- ? value.updatedAt
364
- : (typeof value.updated_at === "string" ? value.updated_at : nowIso());
365
- const links = Array.isArray(value.links) ? value.links.map((item) => String(item)) : [];
366
- const roadmapRefs = Array.isArray(value.roadmapRefs)
367
- ? value.roadmapRefs.map((item) => String(item))
368
- : (Array.isArray(value.roadmap_refs) ? value.roadmap_refs.map((item) => String(item)) : []);
369
- const recordVersionRaw = value.recordVersion ?? value.record_version;
370
- const recordVersion = Number.isFinite(Number(recordVersionRaw)) ? Number(recordVersionRaw) : 1;
371
- return {
372
- id,
373
- title,
374
- status: normalizeTaskStatus(statusRaw),
375
- owner,
376
- summary,
377
- updatedAt,
378
- links,
379
- roadmapRefs,
380
- subState: value.subState,
381
- blocker: value.blocker,
382
- recordVersion,
383
- };
384
- }
385
- function normalizeStoredRoadmapLike(raw) {
386
- if (!raw || typeof raw !== "object") {
387
- return null;
388
- }
389
- const value = raw;
390
- const id = value.id;
391
- const title = value.title;
392
- if (typeof id !== "string" || typeof title !== "string") {
393
- return null;
394
- }
395
- const statusRaw = typeof value.status === "string" ? value.status : "active";
396
- const time = typeof value.time === "string" ? value.time : undefined;
397
- const updatedAt = typeof value.updatedAt === "string"
398
- ? value.updatedAt
399
- : (typeof value.updated_at === "string" ? value.updated_at : nowIso());
400
- const recordVersionRaw = value.recordVersion ?? value.record_version;
401
- const recordVersion = Number.isFinite(Number(recordVersionRaw)) ? Number(recordVersionRaw) : 1;
402
- return {
403
- id,
404
- title,
405
- status: normalizeRoadmapStatus(statusRaw),
406
- time,
407
- updatedAt,
408
- recordVersion,
409
- };
410
- }
411
- async function runDuckdbQuery(sql) {
412
- try {
413
- const connection = await getDuckdbConnection();
414
- const result = await connection.run(sql);
415
- const rows = await result.getRowObjectsJS();
416
- if (!rows || rows.length === 0) {
417
- return undefined;
418
- }
419
- return rows;
420
- }
421
- catch {
422
- return undefined;
423
- }
424
- }
425
- async function loadTasksFromDuckdb(dbPath) {
426
- const sql = `SELECT tasks FROM read_json_auto('${dbPath.replace(/'/g, "''")}') LIMIT 1;`;
427
- const rows = await runDuckdbQuery(sql);
428
- if (!rows || rows.length === 0) {
429
- return undefined;
430
- }
431
- const rawTasks = rows[0]?.tasks;
432
- if (!Array.isArray(rawTasks)) {
433
- return undefined;
434
- }
435
- return rawTasks
436
- .map((item) => normalizeStoredTaskLike(item))
437
- .filter((item) => item != null);
438
- }
439
- async function loadRoadmapsFromDuckdb(dbPath) {
440
- const sql = `SELECT roadmaps FROM read_json_auto('${dbPath.replace(/'/g, "''")}') LIMIT 1;`;
441
- const rows = await runDuckdbQuery(sql);
442
- if (!rows || rows.length === 0) {
443
- return undefined;
444
- }
445
- const rawRoadmaps = rows[0]?.roadmaps;
446
- if (!Array.isArray(rawRoadmaps)) {
447
- return undefined;
448
- }
449
- return rawRoadmaps
450
- .map((item) => normalizeStoredRoadmapLike(item))
451
- .filter((item) => item != null);
452
- }
453
214
  export async function ensureStore(dbPath) {
454
215
  await openStore(dbPath);
455
216
  }
@@ -487,11 +248,8 @@ export async function markMarkdownViewDirty(dbPath, viewName) {
487
248
  });
488
249
  }
489
250
  export async function loadTasksFromStore(dbPath) {
490
- const tasksFromDuckdb = await loadTasksFromDuckdb(dbPath);
491
- if (!tasksFromDuckdb) {
492
- throw new Error("DuckDB task query failed");
493
- }
494
- return tasksFromDuckdb.map(toPublicTask);
251
+ const store = await openStore(dbPath);
252
+ return store.tasks.map(toPublicTask);
495
253
  }
496
254
  export async function loadTaskStatusStatsFromStore(dbPath) {
497
255
  const tasks = await loadTasksFromStore(dbPath);
@@ -568,11 +326,8 @@ export async function replaceTasksInStore(dbPath, tasks) {
568
326
  });
569
327
  }
570
328
  export async function loadRoadmapsFromStore(dbPath) {
571
- const roadmapsFromDuckdb = await loadRoadmapsFromDuckdb(dbPath);
572
- if (!roadmapsFromDuckdb) {
573
- throw new Error("DuckDB roadmap query failed");
574
- }
575
- return roadmapsFromDuckdb.map(toPublicRoadmap);
329
+ const store = await openStore(dbPath);
330
+ return store.roadmaps.map(toPublicRoadmap);
576
331
  }
577
332
  export async function loadRoadmapIdsFromStore(dbPath) {
578
333
  const roadmaps = await loadRoadmapsFromStore(dbPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projitive/mcp",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Projitive MCP Server for project and task discovery/update",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -25,14 +25,11 @@
25
25
  "output"
26
26
  ],
27
27
  "dependencies": {
28
- "@duckdb/node-api": "1.5.0-r.1",
29
28
  "@modelcontextprotocol/sdk": "^1.17.5",
30
- "sql.js": "^1.14.1",
31
29
  "zod": "^3.23.8"
32
30
  },
33
31
  "devDependencies": {
34
32
  "@types/node": "^24.3.0",
35
- "@types/sql.js": "^1.4.9",
36
33
  "@vitest/coverage-v8": "^3.2.4",
37
34
  "tsx": "^4.20.5",
38
35
  "typescript": "^5.9.2",
@@ -1,68 +0,0 @@
1
- import { MIGRATION_STEPS } from "./steps.js";
2
- function setStoreSchemaVersion(db, nextVersion) {
3
- const statement = db.prepare(`
4
- INSERT INTO meta (key, value)
5
- VALUES (?, ?)
6
- ON CONFLICT(key) DO UPDATE SET value=excluded.value
7
- `);
8
- statement.run(["store_schema_version", String(nextVersion)]);
9
- statement.free();
10
- }
11
- function writeMigrationHistory(db, migrationId, fromVersion, toVersion, checksum, status, startedAt, finishedAt, errorMessage) {
12
- const statement = db.prepare(`
13
- INSERT INTO migration_history (
14
- id,
15
- from_version,
16
- to_version,
17
- checksum,
18
- started_at,
19
- finished_at,
20
- status,
21
- error_message
22
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
23
- `);
24
- statement.run([
25
- migrationId,
26
- fromVersion,
27
- toVersion,
28
- checksum,
29
- startedAt,
30
- finishedAt,
31
- status,
32
- errorMessage,
33
- ]);
34
- statement.free();
35
- }
36
- export function runPendingMigrations(db, currentVersion, targetVersion) {
37
- let versionCursor = currentVersion;
38
- if (versionCursor >= targetVersion) {
39
- return;
40
- }
41
- const steps = MIGRATION_STEPS
42
- .filter((item) => item.fromVersion >= currentVersion && item.toVersion <= targetVersion)
43
- .sort((a, b) => a.fromVersion - b.fromVersion);
44
- for (const step of steps) {
45
- if (step.fromVersion !== versionCursor) {
46
- throw new Error(`Migration chain broken at version ${versionCursor}. Missing step ${versionCursor} -> ${step.toVersion}.`);
47
- }
48
- const startedAt = new Date().toISOString();
49
- try {
50
- db.exec("BEGIN TRANSACTION;");
51
- step.up(db);
52
- setStoreSchemaVersion(db, step.toVersion);
53
- const finishedAt = new Date().toISOString();
54
- writeMigrationHistory(db, step.id, step.fromVersion, step.toVersion, step.checksum, "SUCCESS", startedAt, finishedAt, null);
55
- db.exec("COMMIT;");
56
- versionCursor = step.toVersion;
57
- }
58
- catch (error) {
59
- db.exec("ROLLBACK;");
60
- const finishedAt = new Date().toISOString();
61
- writeMigrationHistory(db, step.id, step.fromVersion, step.toVersion, step.checksum, "FAILED", startedAt, finishedAt, error instanceof Error ? error.message : String(error));
62
- throw error;
63
- }
64
- }
65
- if (versionCursor !== targetVersion) {
66
- throw new Error(`Migration target not reached. expected=${targetVersion}, actual=${versionCursor}`);
67
- }
68
- }
@@ -1,55 +0,0 @@
1
- function hasColumn(db, tableName, columnName) {
2
- const result = db.exec(`PRAGMA table_info(${tableName});`);
3
- if (result.length === 0) {
4
- return false;
5
- }
6
- const rows = result[0].values;
7
- return rows.some((row) => String(row[1]) === columnName);
8
- }
9
- function ensureRecordVersionColumns(db) {
10
- const tables = ["tasks", "roadmaps", "meta", "view_state"];
11
- for (const tableName of tables) {
12
- if (!hasColumn(db, tableName, "record_version")) {
13
- db.exec(`ALTER TABLE ${tableName} ADD COLUMN record_version INTEGER NOT NULL DEFAULT 1;`);
14
- }
15
- }
16
- }
17
- function ensurePerformanceIndexes(db) {
18
- db.exec(`
19
- CREATE INDEX IF NOT EXISTS idx_tasks_status_updated
20
- ON tasks(status, updated_at DESC);
21
- CREATE INDEX IF NOT EXISTS idx_tasks_updated
22
- ON tasks(updated_at DESC);
23
- CREATE INDEX IF NOT EXISTS idx_roadmaps_updated
24
- ON roadmaps(updated_at DESC);
25
- `);
26
- }
27
- export const MIGRATION_STEPS = [
28
- {
29
- id: "20260313_baseline_v1",
30
- fromVersion: 0,
31
- toVersion: 1,
32
- checksum: "baseline-v1",
33
- up: () => {
34
- // Baseline marker for pre-versioned stores.
35
- },
36
- },
37
- {
38
- id: "20260313_add_record_version_v2",
39
- fromVersion: 1,
40
- toVersion: 2,
41
- checksum: "add-record-version-v2",
42
- up: (db) => {
43
- ensureRecordVersionColumns(db);
44
- },
45
- },
46
- {
47
- id: "20260313_add_indexes_v3",
48
- fromVersion: 2,
49
- toVersion: 3,
50
- checksum: "add-indexes-v3",
51
- up: (db) => {
52
- ensurePerformanceIndexes(db);
53
- },
54
- },
55
- ];
@@ -1 +0,0 @@
1
- export {};