mcp-probe-kit 3.0.16 → 3.0.18
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/README.md +603 -399
- package/build/index.js +13 -1
- package/build/lib/__tests__/memory-client.unit.test.d.ts +1 -0
- package/build/lib/__tests__/memory-client.unit.test.js +83 -0
- package/build/lib/__tests__/memory-config.unit.test.d.ts +1 -0
- package/build/lib/__tests__/memory-config.unit.test.js +33 -0
- package/build/lib/cursor-history-client.d.ts +54 -0
- package/build/lib/cursor-history-client.js +240 -0
- package/build/lib/gitnexus-bridge.js +6 -8
- package/build/lib/memory-client.d.ts +61 -0
- package/build/lib/memory-client.js +293 -0
- package/build/lib/memory-config.d.ts +14 -0
- package/build/lib/memory-config.js +31 -0
- package/build/lib/memory-orchestration.d.ts +26 -0
- package/build/lib/memory-orchestration.js +65 -0
- package/build/lib/project-detector.js +6 -4
- package/build/lib/workspace-root.d.ts +12 -0
- package/build/lib/workspace-root.js +153 -0
- package/build/resources/ui-ux-data/metadata.json +1 -1
- package/build/schemas/code-analysis-tools.d.ts +1 -1
- package/build/schemas/code-analysis-tools.js +1 -1
- package/build/schemas/index.d.ts +198 -4
- package/build/schemas/index.js +2 -0
- package/build/schemas/memory-tools.d.ts +191 -0
- package/build/schemas/memory-tools.js +106 -0
- package/build/schemas/orchestration-tools.d.ts +3 -3
- package/build/schemas/orchestration-tools.js +3 -3
- package/build/schemas/ui-ux-schemas.d.ts +8 -0
- package/build/schemas/ui-ux-schemas.js +4 -0
- package/build/tools/__tests__/cursor-history.unit.test.d.ts +1 -0
- package/build/tools/__tests__/cursor-history.unit.test.js +87 -0
- package/build/tools/__tests__/memorize_asset.unit.test.d.ts +1 -0
- package/build/tools/__tests__/memorize_asset.unit.test.js +68 -0
- package/build/tools/code_insight.d.ts +20 -0
- package/build/tools/code_insight.js +15 -0
- package/build/tools/cursor_list_conversations.d.ts +7 -0
- package/build/tools/cursor_list_conversations.js +35 -0
- package/build/tools/cursor_read_conversation.d.ts +7 -0
- package/build/tools/cursor_read_conversation.js +36 -0
- package/build/tools/cursor_search_conversations.d.ts +7 -0
- package/build/tools/cursor_search_conversations.js +36 -0
- package/build/tools/index.d.ts +6 -0
- package/build/tools/index.js +7 -0
- package/build/tools/init_project_context.d.ts +20 -1
- package/build/tools/init_project_context.js +114 -99
- package/build/tools/memorize_asset.d.ts +7 -0
- package/build/tools/memorize_asset.js +66 -0
- package/build/tools/read_memory_asset.d.ts +7 -0
- package/build/tools/read_memory_asset.js +26 -0
- package/build/tools/scan_and_extract_patterns.d.ts +27 -0
- package/build/tools/scan_and_extract_patterns.js +346 -0
- package/build/tools/start_bugfix.d.ts +20 -0
- package/build/tools/start_bugfix.js +97 -69
- package/build/tools/start_feature.d.ts +20 -0
- package/build/tools/start_feature.js +61 -31
- package/build/tools/start_onboard.d.ts +20 -0
- package/build/tools/start_onboard.js +15 -0
- package/build/tools/start_ui.d.ts +20 -0
- package/build/tools/start_ui.js +66 -32
- package/docs/data/tools.js +472 -373
- package/docs/i18n/all-tools/en.json +38 -5
- package/docs/i18n/all-tools/ja.json +14 -4
- package/docs/i18n/all-tools/ko.json +13 -3
- package/docs/i18n/all-tools/zh-CN.json +38 -5
- package/docs/i18n/en.json +48 -10
- package/docs/i18n/ja.json +47 -9
- package/docs/i18n/ko.json +47 -9
- package/docs/i18n/zh-CN.json +48 -10
- package/docs/pages/all-tools.html +515 -515
- package/docs/pages/examples.html +661 -661
- package/docs/pages/getting-started.html +673 -582
- package/docs/pages/migration.html +291 -291
- package/package.json +83 -82
- package/docs/debug-i18n.html +0 -163
package/build/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { InMemoryTaskMessageQueue, InMemoryTaskStore, } from "@modelcontextproto
|
|
|
5
5
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ProgressNotificationSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport } from "./tools/index.js";
|
|
8
|
+
import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, readMemoryAsset, memorizeAsset, scanAndExtractPatterns, cursorListConversations, cursorSearchConversations, cursorReadConversation } from "./tools/index.js";
|
|
9
9
|
import { VERSION, NAME } from "./version.js";
|
|
10
10
|
import { allToolSchemas } from "./schemas/index.js";
|
|
11
11
|
import { filterTools, getToolsetFromEnv } from "./lib/toolset-manager.js";
|
|
@@ -459,6 +459,18 @@ async function executeTool(name, args, context) {
|
|
|
459
459
|
return await startProduct((args ?? {}), context);
|
|
460
460
|
case "git_work_report":
|
|
461
461
|
return await gitWorkReport(args);
|
|
462
|
+
case "read_memory_asset":
|
|
463
|
+
return await readMemoryAsset(args);
|
|
464
|
+
case "memorize_asset":
|
|
465
|
+
return await memorizeAsset(args);
|
|
466
|
+
case "scan_and_extract_patterns":
|
|
467
|
+
return await scanAndExtractPatterns(args);
|
|
468
|
+
case "cursor_list_conversations":
|
|
469
|
+
return await cursorListConversations(args);
|
|
470
|
+
case "cursor_search_conversations":
|
|
471
|
+
return await cursorSearchConversations(args);
|
|
472
|
+
case "cursor_read_conversation":
|
|
473
|
+
return await cursorReadConversation(args);
|
|
462
474
|
default:
|
|
463
475
|
throw new Error(`未知工具: ${name}`);
|
|
464
476
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { buildMemoryContentHashes, MemoryClient, normalizeContentForHash, } from '../memory-client.js';
|
|
3
|
+
const originalFetch = globalThis.fetch;
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
vi.unstubAllEnvs();
|
|
6
|
+
vi.restoreAllMocks();
|
|
7
|
+
globalThis.fetch = originalFetch;
|
|
8
|
+
});
|
|
9
|
+
describe('memory-client 去重逻辑', () => {
|
|
10
|
+
test('归一化 hash 忽略换行风格、行尾空白和多余空行', () => {
|
|
11
|
+
const a = 'export const x = 1; \r\n\r\n\r\n';
|
|
12
|
+
const b = 'export const x = 1;\n\n';
|
|
13
|
+
expect(normalizeContentForHash(a)).toBe('export const x = 1;');
|
|
14
|
+
expect(normalizeContentForHash(b)).toBe('export const x = 1;');
|
|
15
|
+
expect(buildMemoryContentHashes(a).normalizedContentHash).toBe(buildMemoryContentHashes(b).normalizedContentHash);
|
|
16
|
+
});
|
|
17
|
+
test('重复内容二次沉淀时直接复用已有资产', async () => {
|
|
18
|
+
vi.stubEnv('MEMORY_QDRANT_URL', 'http://127.0.0.1:50008');
|
|
19
|
+
vi.stubEnv('MEMORY_EMBEDDING_URL', 'http://127.0.0.1:11434/api/embeddings');
|
|
20
|
+
vi.stubEnv('MEMORY_EMBEDDING_MODEL', 'nomic-embed-text');
|
|
21
|
+
const existingAsset = {
|
|
22
|
+
id: 'existing-asset-id',
|
|
23
|
+
name: 'ExistingAsset',
|
|
24
|
+
type: 'code',
|
|
25
|
+
description: '已有资产',
|
|
26
|
+
summary: '重复内容直接复用',
|
|
27
|
+
content: 'export const x = 1;\n',
|
|
28
|
+
tags: ['memory'],
|
|
29
|
+
confidence: 0.7,
|
|
30
|
+
contentHash: 'raw-hash',
|
|
31
|
+
normalizedContentHash: buildMemoryContentHashes('export const x = 1;\n').normalizedContentHash,
|
|
32
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
33
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
34
|
+
};
|
|
35
|
+
const fetchMock = vi.fn(async (input, init) => {
|
|
36
|
+
const url = String(input);
|
|
37
|
+
if (url.endsWith('/api/embeddings')) {
|
|
38
|
+
return new Response(JSON.stringify({ embedding: [0.1, 0.2, 0.3] }), {
|
|
39
|
+
status: 200,
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (url.endsWith('/collections/mcp_probe_memory') && !init?.method) {
|
|
44
|
+
return new Response('', { status: 200 });
|
|
45
|
+
}
|
|
46
|
+
if (url.endsWith('/points/scroll')) {
|
|
47
|
+
return new Response(JSON.stringify({
|
|
48
|
+
result: {
|
|
49
|
+
points: [
|
|
50
|
+
{
|
|
51
|
+
id: existingAsset.id,
|
|
52
|
+
payload: existingAsset,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
}), {
|
|
57
|
+
status: 200,
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (url.includes('/points?wait=true')) {
|
|
62
|
+
return new Response(JSON.stringify({ status: 'ok' }), {
|
|
63
|
+
status: 200,
|
|
64
|
+
headers: { 'Content-Type': 'application/json' },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`unexpected fetch: ${url}`);
|
|
68
|
+
});
|
|
69
|
+
globalThis.fetch = fetchMock;
|
|
70
|
+
const client = new MemoryClient();
|
|
71
|
+
const result = await client.upsertAsset({
|
|
72
|
+
name: 'NewAsset',
|
|
73
|
+
type: 'code',
|
|
74
|
+
description: '新资产',
|
|
75
|
+
summary: '和已有资产内容一致',
|
|
76
|
+
content: 'export const x = 1; \r\n\r\n',
|
|
77
|
+
tags: ['memory'],
|
|
78
|
+
});
|
|
79
|
+
expect(result.id).toBe(existingAsset.id);
|
|
80
|
+
expect(result.normalizedContentHash).toBe(existingAsset.normalizedContentHash);
|
|
81
|
+
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/points?wait=true'))).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { getMemoryConfig, isMemoryEnabled, isMemoryReadEnabled } from '../memory-config.js';
|
|
3
|
+
afterEach(() => {
|
|
4
|
+
vi.unstubAllEnvs();
|
|
5
|
+
});
|
|
6
|
+
describe('memory-config 单元测试', () => {
|
|
7
|
+
test('默认未配置时记忆读写均关闭', () => {
|
|
8
|
+
vi.stubEnv('MEMORY_QDRANT_URL', '');
|
|
9
|
+
vi.stubEnv('MEMORY_EMBEDDING_URL', '');
|
|
10
|
+
vi.stubEnv('MEMORY_EMBEDDING_MODEL', '');
|
|
11
|
+
const config = getMemoryConfig();
|
|
12
|
+
expect(isMemoryEnabled(config)).toBe(false);
|
|
13
|
+
expect(isMemoryReadEnabled(config)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
test('仅配置 qdrant 时只开启只读能力', () => {
|
|
16
|
+
vi.stubEnv('MEMORY_QDRANT_URL', 'http://127.0.0.1:50008');
|
|
17
|
+
vi.stubEnv('MEMORY_EMBEDDING_URL', '');
|
|
18
|
+
vi.stubEnv('MEMORY_EMBEDDING_MODEL', '');
|
|
19
|
+
const config = getMemoryConfig();
|
|
20
|
+
expect(isMemoryReadEnabled(config)).toBe(true);
|
|
21
|
+
expect(isMemoryEnabled(config)).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
test('qdrant、embedding url 和 model 齐全时开启记忆服务', () => {
|
|
24
|
+
vi.stubEnv('MEMORY_QDRANT_URL', 'http://127.0.0.1:50008/');
|
|
25
|
+
vi.stubEnv('MEMORY_EMBEDDING_URL', 'http://127.0.0.1:11434/api/embeddings/');
|
|
26
|
+
vi.stubEnv('MEMORY_EMBEDDING_MODEL', 'nomic-embed-text');
|
|
27
|
+
const config = getMemoryConfig();
|
|
28
|
+
expect(config.qdrantUrl).toBe('http://127.0.0.1:50008');
|
|
29
|
+
expect(config.embeddingUrl).toBe('http://127.0.0.1:11434/api/embeddings');
|
|
30
|
+
expect(isMemoryReadEnabled(config)).toBe(true);
|
|
31
|
+
expect(isMemoryEnabled(config)).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface CursorConversationSummary {
|
|
2
|
+
composerId: string;
|
|
3
|
+
name: string;
|
|
4
|
+
createdAt?: number;
|
|
5
|
+
lastUpdatedAt?: number;
|
|
6
|
+
workspaceId?: string;
|
|
7
|
+
workspacePath?: string;
|
|
8
|
+
mode?: string;
|
|
9
|
+
contextUsagePercent?: number;
|
|
10
|
+
subtitle?: string;
|
|
11
|
+
isArchived?: boolean;
|
|
12
|
+
source: 'composerHeaders';
|
|
13
|
+
}
|
|
14
|
+
export interface CursorConversationMessage {
|
|
15
|
+
bubbleId: string;
|
|
16
|
+
type: number;
|
|
17
|
+
text: string;
|
|
18
|
+
createdAt?: string;
|
|
19
|
+
requestId?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface CursorConversationDetail {
|
|
22
|
+
composerId: string;
|
|
23
|
+
messages: CursorConversationMessage[];
|
|
24
|
+
}
|
|
25
|
+
export interface CursorHistorySearchResult {
|
|
26
|
+
composerId: string;
|
|
27
|
+
conversationName: string;
|
|
28
|
+
bubbleId: string;
|
|
29
|
+
type: number;
|
|
30
|
+
text: string;
|
|
31
|
+
createdAt?: string;
|
|
32
|
+
requestId?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare class CursorHistoryClient {
|
|
35
|
+
private withDatabase;
|
|
36
|
+
private loadConversationIndex;
|
|
37
|
+
listConversations(params?: {
|
|
38
|
+
titleQuery?: string;
|
|
39
|
+
workspaceQuery?: string;
|
|
40
|
+
includeArchived?: boolean;
|
|
41
|
+
limit?: number;
|
|
42
|
+
}): Promise<CursorConversationSummary[]>;
|
|
43
|
+
searchHistory(params: {
|
|
44
|
+
query: string;
|
|
45
|
+
composerId?: string;
|
|
46
|
+
limit?: number;
|
|
47
|
+
}): Promise<CursorHistorySearchResult[]>;
|
|
48
|
+
readConversation(params: {
|
|
49
|
+
composerId: string;
|
|
50
|
+
limit?: number;
|
|
51
|
+
includeEmpty?: boolean;
|
|
52
|
+
}): Promise<CursorConversationDetail>;
|
|
53
|
+
}
|
|
54
|
+
export declare function createCursorHistoryClient(): CursorHistoryClient;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import sqlite3 from 'sqlite3';
|
|
5
|
+
import { createToolError } from '../utils/error-handler.js';
|
|
6
|
+
function parseJson(value) {
|
|
7
|
+
if (!value) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(value);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function fileExists(filePath) {
|
|
18
|
+
try {
|
|
19
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function resolveCursorGlobalDbPath() {
|
|
26
|
+
const appData = process.env.APPDATA?.trim();
|
|
27
|
+
const homeDir = os.homedir();
|
|
28
|
+
const candidates = [
|
|
29
|
+
appData ? path.join(appData, 'Cursor', 'User', 'globalStorage', 'state.vscdb') : '',
|
|
30
|
+
path.join(homeDir, '.config', 'Cursor', 'User', 'globalStorage', 'state.vscdb'),
|
|
31
|
+
path.join(homeDir, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb'),
|
|
32
|
+
].filter(Boolean);
|
|
33
|
+
const hit = candidates.find(fileExists);
|
|
34
|
+
if (!hit) {
|
|
35
|
+
throw createToolError('未找到 Cursor 全局状态库 state.vscdb', undefined, {
|
|
36
|
+
candidates,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return hit;
|
|
40
|
+
}
|
|
41
|
+
function openDatabase(dbPath) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (error) => {
|
|
44
|
+
if (error) {
|
|
45
|
+
reject(createToolError(`打开 Cursor 数据库失败: ${error.message}`, error, { dbPath }));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
resolve(db);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function closeDatabase(db) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
db.close((error) => {
|
|
55
|
+
if (error) {
|
|
56
|
+
reject(error);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
resolve();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function allRows(db, sql, params = []) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
db.all(sql, params, (error, rows) => {
|
|
66
|
+
if (error) {
|
|
67
|
+
reject(error);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
resolve((rows ?? []));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function getRow(db, sql, params = []) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
db.get(sql, params, (error, row) => {
|
|
77
|
+
if (error) {
|
|
78
|
+
reject(error);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
resolve(row);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function asString(value) {
|
|
86
|
+
return typeof value === 'string' ? value : '';
|
|
87
|
+
}
|
|
88
|
+
function asNumber(value) {
|
|
89
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
90
|
+
}
|
|
91
|
+
function asBoolean(value) {
|
|
92
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
93
|
+
}
|
|
94
|
+
function extractConversationsFromHeaders(payload) {
|
|
95
|
+
const items = payload?.allComposers ?? [];
|
|
96
|
+
return items
|
|
97
|
+
.filter((item) => item?.type === 'head')
|
|
98
|
+
.map((item) => {
|
|
99
|
+
const workspace = item.workspaceIdentifier ?? {};
|
|
100
|
+
const uri = workspace.uri ?? {};
|
|
101
|
+
return {
|
|
102
|
+
composerId: asString(item.composerId),
|
|
103
|
+
name: asString(item.name),
|
|
104
|
+
createdAt: asNumber(item.createdAt),
|
|
105
|
+
lastUpdatedAt: asNumber(item.lastUpdatedAt),
|
|
106
|
+
workspaceId: asString(workspace.id) || undefined,
|
|
107
|
+
workspacePath: asString(uri.fsPath) || undefined,
|
|
108
|
+
mode: asString(item.unifiedMode) || undefined,
|
|
109
|
+
contextUsagePercent: asNumber(item.contextUsagePercent),
|
|
110
|
+
subtitle: asString(item.subtitle) || undefined,
|
|
111
|
+
isArchived: asBoolean(item.isArchived) ?? false,
|
|
112
|
+
source: 'composerHeaders',
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function extractBubbleId(key) {
|
|
117
|
+
const parts = key.split(':');
|
|
118
|
+
return parts[parts.length - 1] || key;
|
|
119
|
+
}
|
|
120
|
+
function extractComposerId(key) {
|
|
121
|
+
const parts = key.split(':');
|
|
122
|
+
return parts.length >= 3 ? parts[1] || '' : '';
|
|
123
|
+
}
|
|
124
|
+
export class CursorHistoryClient {
|
|
125
|
+
async withDatabase(runner) {
|
|
126
|
+
const dbPath = resolveCursorGlobalDbPath();
|
|
127
|
+
const db = await openDatabase(dbPath);
|
|
128
|
+
try {
|
|
129
|
+
return await runner(db);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error instanceof Error) {
|
|
133
|
+
throw createToolError(`读取 Cursor 历史失败: ${error.message}`, error, { dbPath });
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
await closeDatabase(db).catch(() => undefined);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async loadConversationIndex(db) {
|
|
142
|
+
const row = await getRow(db, "select key, value from ItemTable where key = ?", ['composer.composerHeaders']);
|
|
143
|
+
const payload = parseJson(row?.value);
|
|
144
|
+
return extractConversationsFromHeaders(payload);
|
|
145
|
+
}
|
|
146
|
+
async listConversations(params = {}) {
|
|
147
|
+
return this.withDatabase(async (db) => {
|
|
148
|
+
const titleQuery = (params.titleQuery ?? '').trim().toLowerCase();
|
|
149
|
+
const workspaceQuery = (params.workspaceQuery ?? '').trim().toLowerCase();
|
|
150
|
+
const includeArchived = params.includeArchived ?? false;
|
|
151
|
+
const limit = Math.max(1, Math.min(params.limit ?? 20, 200));
|
|
152
|
+
const conversations = await this.loadConversationIndex(db);
|
|
153
|
+
return conversations
|
|
154
|
+
.filter((item) => includeArchived || !item.isArchived)
|
|
155
|
+
.filter((item) => !titleQuery || item.name.toLowerCase().includes(titleQuery))
|
|
156
|
+
.filter((item) => !workspaceQuery || (item.workspacePath ?? '').toLowerCase().includes(workspaceQuery))
|
|
157
|
+
.sort((a, b) => (b.lastUpdatedAt ?? 0) - (a.lastUpdatedAt ?? 0))
|
|
158
|
+
.slice(0, limit);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async searchHistory(params) {
|
|
162
|
+
return this.withDatabase(async (db) => {
|
|
163
|
+
const query = (params.query ?? '').trim().toLowerCase();
|
|
164
|
+
if (!query) {
|
|
165
|
+
throw new Error('缺少 query');
|
|
166
|
+
}
|
|
167
|
+
const limit = Math.max(1, Math.min(params.limit ?? 20, 200));
|
|
168
|
+
const composerId = (params.composerId ?? '').trim();
|
|
169
|
+
const index = await this.loadConversationIndex(db);
|
|
170
|
+
const names = new Map(index.map((item) => [item.composerId, item.name]));
|
|
171
|
+
const rows = await allRows(db, 'select key, value from cursorDiskKV where key like ?', [composerId ? `bubbleId:${composerId}:%` : 'bubbleId:%']);
|
|
172
|
+
const matches = rows
|
|
173
|
+
.map((row) => {
|
|
174
|
+
const parsed = parseJson(row.value);
|
|
175
|
+
if (!parsed) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const text = asString(parsed.text);
|
|
179
|
+
const requestId = asString(parsed.requestId);
|
|
180
|
+
const haystack = `${row.key}\n${text}\n${requestId}`.toLowerCase();
|
|
181
|
+
if (!haystack.includes(query)) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const currentComposerId = extractComposerId(row.key);
|
|
185
|
+
return {
|
|
186
|
+
composerId: currentComposerId,
|
|
187
|
+
conversationName: names.get(currentComposerId) ?? '',
|
|
188
|
+
bubbleId: extractBubbleId(row.key),
|
|
189
|
+
type: asNumber(parsed.type) ?? 0,
|
|
190
|
+
text,
|
|
191
|
+
createdAt: asString(parsed.createdAt) || undefined,
|
|
192
|
+
requestId: requestId || undefined,
|
|
193
|
+
};
|
|
194
|
+
})
|
|
195
|
+
.filter((item) => item !== null)
|
|
196
|
+
.sort((a, b) => (b.createdAt ?? '').localeCompare(a.createdAt ?? ''))
|
|
197
|
+
.slice(0, limit);
|
|
198
|
+
return matches;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
async readConversation(params) {
|
|
202
|
+
return this.withDatabase(async (db) => {
|
|
203
|
+
const composerId = (params.composerId ?? '').trim();
|
|
204
|
+
if (!composerId) {
|
|
205
|
+
throw new Error('缺少 composer_id');
|
|
206
|
+
}
|
|
207
|
+
const limit = Math.max(1, Math.min(params.limit ?? 200, 2000));
|
|
208
|
+
const includeEmpty = params.includeEmpty ?? false;
|
|
209
|
+
const rows = await allRows(db, 'select key, value from cursorDiskKV where key like ?', [`bubbleId:${composerId}:%`]);
|
|
210
|
+
const messages = rows
|
|
211
|
+
.map((row) => {
|
|
212
|
+
const parsed = parseJson(row.value);
|
|
213
|
+
if (!parsed) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
const text = asString(parsed.text);
|
|
217
|
+
if (!includeEmpty && !text) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
bubbleId: extractBubbleId(row.key),
|
|
222
|
+
type: asNumber(parsed.type) ?? 0,
|
|
223
|
+
text,
|
|
224
|
+
createdAt: asString(parsed.createdAt) || undefined,
|
|
225
|
+
requestId: asString(parsed.requestId) || undefined,
|
|
226
|
+
};
|
|
227
|
+
})
|
|
228
|
+
.filter((item) => item !== null)
|
|
229
|
+
.sort((a, b) => (a.createdAt ?? '').localeCompare(b.createdAt ?? ''))
|
|
230
|
+
.slice(0, limit);
|
|
231
|
+
return {
|
|
232
|
+
composerId,
|
|
233
|
+
messages,
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export function createCursorHistoryClient() {
|
|
239
|
+
return new CursorHistoryClient();
|
|
240
|
+
}
|
|
@@ -5,6 +5,7 @@ import spawn from "cross-spawn";
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
+
import { resolveWorkspaceRoot } from "./workspace-root.js";
|
|
8
9
|
import { isAbortError, throwIfAborted, } from "./tool-execution-context.js";
|
|
9
10
|
const DEFAULT_CONNECT_TIMEOUT_MS = readIntEnv("MCP_GITNEXUS_CONNECT_TIMEOUT_MS", 12000);
|
|
10
11
|
const DEFAULT_CALL_TIMEOUT_MS = readIntEnv("MCP_GITNEXUS_TIMEOUT_MS", 20000);
|
|
@@ -82,15 +83,12 @@ function resolvePreferredRepoName(requestedRepo) {
|
|
|
82
83
|
return undefined;
|
|
83
84
|
}
|
|
84
85
|
function resolveRequestedProjectRoot(projectRoot) {
|
|
85
|
-
|
|
86
|
-
if (requested) {
|
|
87
|
-
return path.resolve(requested);
|
|
88
|
-
}
|
|
89
|
-
return path.resolve(process.cwd());
|
|
86
|
+
return resolveWorkspaceRoot(projectRoot);
|
|
90
87
|
}
|
|
91
|
-
function inferCandidateRepoNames(baseDir
|
|
88
|
+
function inferCandidateRepoNames(baseDir) {
|
|
89
|
+
const resolvedBaseDir = resolveWorkspaceRoot(baseDir);
|
|
92
90
|
const candidates = [];
|
|
93
|
-
const pkgPath = path.join(
|
|
91
|
+
const pkgPath = path.join(resolvedBaseDir, "package.json");
|
|
94
92
|
try {
|
|
95
93
|
if (fs.existsSync(pkgPath)) {
|
|
96
94
|
const parsed = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
@@ -103,7 +101,7 @@ function inferCandidateRepoNames(baseDir = process.cwd()) {
|
|
|
103
101
|
catch {
|
|
104
102
|
// ignore parse failure
|
|
105
103
|
}
|
|
106
|
-
const base = path.basename(
|
|
104
|
+
const base = path.basename(resolvedBaseDir).trim();
|
|
107
105
|
if (base) {
|
|
108
106
|
candidates.push(base);
|
|
109
107
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type MemoryConfig } from './memory-config.js';
|
|
2
|
+
export interface MemoryAsset {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
type: string;
|
|
6
|
+
description: string;
|
|
7
|
+
summary: string;
|
|
8
|
+
content: string;
|
|
9
|
+
tags: string[];
|
|
10
|
+
confidence: number;
|
|
11
|
+
sourceProject?: string;
|
|
12
|
+
sourcePath?: string;
|
|
13
|
+
usage?: string;
|
|
14
|
+
contentHash?: string;
|
|
15
|
+
normalizedContentHash?: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MemorySearchResult {
|
|
20
|
+
id: string;
|
|
21
|
+
score: number;
|
|
22
|
+
name: string;
|
|
23
|
+
type: string;
|
|
24
|
+
description: string;
|
|
25
|
+
summary: string;
|
|
26
|
+
tags: string[];
|
|
27
|
+
sourcePath?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function normalizeContentForHash(content: string): string;
|
|
30
|
+
export declare function sha256Hex(value: string): string;
|
|
31
|
+
export declare function buildMemoryContentHashes(content: string): {
|
|
32
|
+
contentHash: string;
|
|
33
|
+
normalizedContentHash: string;
|
|
34
|
+
};
|
|
35
|
+
export declare class MemoryClient {
|
|
36
|
+
private readonly config;
|
|
37
|
+
constructor(config?: MemoryConfig);
|
|
38
|
+
isEnabled(): boolean;
|
|
39
|
+
isReadEnabled(): boolean;
|
|
40
|
+
private buildHeaders;
|
|
41
|
+
private buildEmbeddingHeaders;
|
|
42
|
+
private requestJson;
|
|
43
|
+
private ensureCollection;
|
|
44
|
+
embed(text: string): Promise<number[]>;
|
|
45
|
+
private findExistingAssetByNormalizedContentHash;
|
|
46
|
+
upsertAsset(input: {
|
|
47
|
+
name: string;
|
|
48
|
+
type: string;
|
|
49
|
+
description: string;
|
|
50
|
+
summary: string;
|
|
51
|
+
content: string;
|
|
52
|
+
tags?: string[];
|
|
53
|
+
confidence?: number;
|
|
54
|
+
sourceProject?: string;
|
|
55
|
+
sourcePath?: string;
|
|
56
|
+
usage?: string;
|
|
57
|
+
}): Promise<MemoryAsset>;
|
|
58
|
+
search(query: string, limit?: number): Promise<MemorySearchResult[]>;
|
|
59
|
+
getAsset(assetId: string): Promise<MemoryAsset | null>;
|
|
60
|
+
}
|
|
61
|
+
export declare function createMemoryClient(): MemoryClient;
|