aifastdb-devplan 1.2.6 → 1.3.8
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/dist/dev-plan-document-store.d.ts +18 -0
- package/dist/dev-plan-document-store.d.ts.map +1 -1
- package/dist/dev-plan-document-store.js +80 -3
- package/dist/dev-plan-document-store.js.map +1 -1
- package/dist/dev-plan-factory.d.ts +4 -0
- package/dist/dev-plan-factory.d.ts.map +1 -1
- package/dist/dev-plan-factory.js +6 -0
- package/dist/dev-plan-factory.js.map +1 -1
- package/dist/dev-plan-graph-store.d.ts +85 -1
- package/dist/dev-plan-graph-store.d.ts.map +1 -1
- package/dist/dev-plan-graph-store.js +506 -18
- package/dist/dev-plan-graph-store.js.map +1 -1
- package/dist/dev-plan-interface.d.ts +38 -1
- package/dist/dev-plan-interface.d.ts.map +1 -1
- package/dist/dev-plan-migrate.d.ts +1 -1
- package/dist/dev-plan-migrate.d.ts.map +1 -1
- package/dist/dev-plan-migrate.js +2 -1
- package/dist/dev-plan-migrate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/index.js +168 -3
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/visualize/server.js +3 -1
- package/dist/visualize/server.js.map +1 -1
- package/dist/visualize/template.d.ts.map +1 -1
- package/dist/visualize/template.js +777 -38
- package/dist/visualize/template.js.map +1 -1
- package/package.json +2 -2
|
@@ -15,9 +15,43 @@
|
|
|
15
15
|
* - Entity 类型: devplan-project, devplan-doc, devplan-main-task, devplan-sub-task, devplan-module
|
|
16
16
|
* - Relation 类型: has_document, has_main_task, has_sub_task, module_has_task, module_has_doc
|
|
17
17
|
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
18
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
52
|
exports.DevPlanGraphStore = void 0;
|
|
20
53
|
const aifastdb_1 = require("aifastdb");
|
|
54
|
+
const path = __importStar(require("path"));
|
|
21
55
|
// ============================================================================
|
|
22
56
|
// Constants
|
|
23
57
|
// ============================================================================
|
|
@@ -37,6 +71,7 @@ const RT = {
|
|
|
37
71
|
MODULE_HAS_TASK: 'module_has_task',
|
|
38
72
|
MODULE_HAS_DOC: 'module_has_doc',
|
|
39
73
|
TASK_HAS_DOC: 'task_has_doc',
|
|
74
|
+
DOC_HAS_CHILD: 'doc_has_child',
|
|
40
75
|
};
|
|
41
76
|
// ============================================================================
|
|
42
77
|
// Helper
|
|
@@ -67,19 +102,85 @@ class DevPlanGraphStore {
|
|
|
67
102
|
constructor(projectName, config) {
|
|
68
103
|
/** 缓存的项目根实体 ID */
|
|
69
104
|
this.projectEntityId = null;
|
|
105
|
+
/** VibeSynapse 实例(用于 Embedding 生成),仅启用语义搜索时可用 */
|
|
106
|
+
this.synapse = null;
|
|
107
|
+
/** 语义搜索是否成功初始化 */
|
|
108
|
+
this.semanticSearchReady = false;
|
|
70
109
|
this.projectName = projectName;
|
|
71
|
-
|
|
110
|
+
// 构建 SocialGraphV2 配置
|
|
111
|
+
const graphConfig = {
|
|
72
112
|
path: config.graphPath,
|
|
73
113
|
shardCount: config.shardCount || 4,
|
|
74
114
|
walEnabled: true,
|
|
75
115
|
mode: 'balanced',
|
|
76
116
|
shardNames: ['entities', 'relations', 'index', 'meta'],
|
|
77
|
-
}
|
|
78
|
-
//
|
|
117
|
+
};
|
|
118
|
+
// 如果启用语义搜索,配置 SocialGraphV2 的向量搜索
|
|
119
|
+
const dimension = config.embeddingDimension || 384;
|
|
120
|
+
if (config.enableSemanticSearch) {
|
|
121
|
+
graphConfig.vectorSearch = {
|
|
122
|
+
dimension,
|
|
123
|
+
m: 16,
|
|
124
|
+
efConstruction: 200,
|
|
125
|
+
efSearch: 50,
|
|
126
|
+
maxElements: 100000,
|
|
127
|
+
shardCount: 1,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
this.graph = new aifastdb_1.SocialGraphV2(graphConfig);
|
|
131
|
+
// 恢复 WAL 数据(包括向量 WAL)
|
|
79
132
|
this.graph.recover();
|
|
133
|
+
// 初始化 VibeSynapse(用于 Embedding 生成)
|
|
134
|
+
if (config.enableSemanticSearch) {
|
|
135
|
+
this.initSynapse(config.graphPath, dimension);
|
|
136
|
+
}
|
|
80
137
|
// 确保项目根实体存在
|
|
81
138
|
this.ensureProjectEntity();
|
|
82
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* 初始化 VibeSynapse Embedding 引擎
|
|
142
|
+
*
|
|
143
|
+
* 使用 Candle MiniLM (384维) 作为默认模型,支持零配置离线使用。
|
|
144
|
+
* 初始化失败时降级为纯字面搜索(graceful degradation)。
|
|
145
|
+
*/
|
|
146
|
+
initSynapse(graphPath, dimension) {
|
|
147
|
+
try {
|
|
148
|
+
const synapsePath = path.resolve(graphPath, '..', 'synapse-data');
|
|
149
|
+
this.synapse = new aifastdb_1.VibeSynapse({
|
|
150
|
+
storage: synapsePath,
|
|
151
|
+
dimension,
|
|
152
|
+
perception: {
|
|
153
|
+
engineType: 'candle',
|
|
154
|
+
modelId: 'sentence-transformers/all-MiniLM-L6-v2',
|
|
155
|
+
autoDownload: true,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
// 验证 perception engine 是否真正可用
|
|
159
|
+
if (!this.synapse.hasPerception) {
|
|
160
|
+
console.warn('[DevPlan] VibeSynapse created but perception engine not available. ' +
|
|
161
|
+
'Candle MiniLM may not be installed. Falling back to literal search.');
|
|
162
|
+
this.synapse = null;
|
|
163
|
+
this.semanticSearchReady = false;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// 测试 embed 是否可用(dry run)
|
|
167
|
+
try {
|
|
168
|
+
this.synapse.embed('test');
|
|
169
|
+
this.semanticSearchReady = true;
|
|
170
|
+
console.log('[DevPlan] Semantic search initialized (Candle MiniLM)');
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
console.warn('[DevPlan] VibeSynapse embed() dry-run failed. Falling back to literal search.');
|
|
174
|
+
this.synapse = null;
|
|
175
|
+
this.semanticSearchReady = false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
console.warn(`[DevPlan] Failed to initialize VibeSynapse for semantic search: ${e instanceof Error ? e.message : String(e)}. Falling back to literal search.`);
|
|
180
|
+
this.synapse = null;
|
|
181
|
+
this.semanticSearchReady = false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
83
184
|
// ==========================================================================
|
|
84
185
|
// Project Entity
|
|
85
186
|
// ==========================================================================
|
|
@@ -147,6 +248,19 @@ class DevPlanGraphStore {
|
|
|
147
248
|
// ==========================================================================
|
|
148
249
|
entityToDevPlanDoc(e) {
|
|
149
250
|
const p = e.properties;
|
|
251
|
+
// 获取 parentDoc:从属性读取
|
|
252
|
+
const parentDoc = p.parentDoc || undefined;
|
|
253
|
+
// 获取 childDocs:通过 DOC_HAS_CHILD 出向关系查询
|
|
254
|
+
const childDocRels = this.getOutRelations(e.id, RT.DOC_HAS_CHILD);
|
|
255
|
+
const childDocs = childDocRels.length > 0
|
|
256
|
+
? childDocRels.map((rel) => {
|
|
257
|
+
const childEntity = this.graph.getEntity(rel.target);
|
|
258
|
+
if (!childEntity)
|
|
259
|
+
return undefined;
|
|
260
|
+
const cp = childEntity.properties;
|
|
261
|
+
return sectionKey(cp.section, cp.subSection || undefined);
|
|
262
|
+
}).filter((k) => k !== undefined)
|
|
263
|
+
: undefined;
|
|
150
264
|
return {
|
|
151
265
|
id: e.id,
|
|
152
266
|
projectName: this.projectName,
|
|
@@ -158,6 +272,8 @@ class DevPlanGraphStore {
|
|
|
158
272
|
relatedSections: p.relatedSections || [],
|
|
159
273
|
moduleId: p.moduleId || undefined,
|
|
160
274
|
relatedTaskIds: p.relatedTaskIds || [],
|
|
275
|
+
parentDoc,
|
|
276
|
+
childDocs,
|
|
161
277
|
createdAt: p.createdAt || e.created_at,
|
|
162
278
|
updatedAt: p.updatedAt || e.updated_at,
|
|
163
279
|
};
|
|
@@ -177,6 +293,7 @@ class DevPlanGraphStore {
|
|
|
177
293
|
totalSubtasks: p.totalSubtasks || 0,
|
|
178
294
|
completedSubtasks: p.completedSubtasks || 0,
|
|
179
295
|
status: p.status || 'pending',
|
|
296
|
+
order: p.order != null ? p.order : undefined,
|
|
180
297
|
createdAt: p.createdAt || e.created_at,
|
|
181
298
|
updatedAt: p.updatedAt || e.updated_at,
|
|
182
299
|
completedAt: p.completedAt || null,
|
|
@@ -194,6 +311,7 @@ class DevPlanGraphStore {
|
|
|
194
311
|
relatedFiles: p.relatedFiles || [],
|
|
195
312
|
description: p.description || undefined,
|
|
196
313
|
status: p.status || 'pending',
|
|
314
|
+
order: p.order != null ? p.order : undefined,
|
|
197
315
|
createdAt: p.createdAt || e.created_at,
|
|
198
316
|
updatedAt: p.updatedAt || e.updated_at,
|
|
199
317
|
completedAt: p.completedAt || null,
|
|
@@ -238,6 +356,8 @@ class DevPlanGraphStore {
|
|
|
238
356
|
const now = Date.now();
|
|
239
357
|
const version = input.version || '1.0.0';
|
|
240
358
|
const finalModuleId = input.moduleId || existing?.moduleId;
|
|
359
|
+
// 确定最终的 parentDoc 值(显式传入 > 已有值)
|
|
360
|
+
const finalParentDoc = input.parentDoc !== undefined ? input.parentDoc : existing?.parentDoc;
|
|
241
361
|
if (existing) {
|
|
242
362
|
// 更新已有文档
|
|
243
363
|
const finalRelatedTaskIds = input.relatedTaskIds || existing.relatedTaskIds || [];
|
|
@@ -250,6 +370,7 @@ class DevPlanGraphStore {
|
|
|
250
370
|
relatedSections: input.relatedSections || [],
|
|
251
371
|
relatedTaskIds: finalRelatedTaskIds,
|
|
252
372
|
moduleId: finalModuleId || null,
|
|
373
|
+
parentDoc: finalParentDoc || null,
|
|
253
374
|
updatedAt: now,
|
|
254
375
|
},
|
|
255
376
|
});
|
|
@@ -257,6 +378,8 @@ class DevPlanGraphStore {
|
|
|
257
378
|
if (finalModuleId && finalModuleId !== existing.moduleId) {
|
|
258
379
|
this.updateModuleDocRelation(existing.id, existing.moduleId, finalModuleId);
|
|
259
380
|
}
|
|
381
|
+
// 更新 parentDoc 关系(DOC_HAS_CHILD)
|
|
382
|
+
this.updateParentDocRelation(existing.id, existing.parentDoc, finalParentDoc);
|
|
260
383
|
// 更新 task -> doc 关系
|
|
261
384
|
if (finalRelatedTaskIds.length) {
|
|
262
385
|
// 删除旧的 TASK_HAS_DOC 入向关系(指向本文档的)
|
|
@@ -272,6 +395,8 @@ class DevPlanGraphStore {
|
|
|
272
395
|
}
|
|
273
396
|
}
|
|
274
397
|
}
|
|
398
|
+
// 语义搜索:自动为更新的文档生成 Embedding 并索引
|
|
399
|
+
this.autoIndexDocument(existing.id, input.title, input.content);
|
|
275
400
|
this.graph.flush();
|
|
276
401
|
return existing.id;
|
|
277
402
|
}
|
|
@@ -286,11 +411,23 @@ class DevPlanGraphStore {
|
|
|
286
411
|
relatedSections: input.relatedSections || [],
|
|
287
412
|
relatedTaskIds: input.relatedTaskIds || [],
|
|
288
413
|
moduleId: finalModuleId || null,
|
|
414
|
+
parentDoc: finalParentDoc || null,
|
|
289
415
|
createdAt: now,
|
|
290
416
|
updatedAt: now,
|
|
291
417
|
});
|
|
292
|
-
//
|
|
293
|
-
|
|
418
|
+
// 子文档不直接连接项目节点,仅通过 doc_has_child 连接父文档
|
|
419
|
+
if (finalParentDoc) {
|
|
420
|
+
// 有 parentDoc → 创建 DOC_HAS_CHILD 关系(parent -> child),不创建 project -> doc
|
|
421
|
+
const [parentSection, parentSubSection] = finalParentDoc.split('|');
|
|
422
|
+
const parentEntity = this.findDocEntityBySection(parentSection, parentSubSection || undefined);
|
|
423
|
+
if (parentEntity) {
|
|
424
|
+
this.graph.putRelation(parentEntity.id, entity.id, RT.DOC_HAS_CHILD);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
// 无 parentDoc → 创建 project -> doc 关系(顶级文档)
|
|
429
|
+
this.graph.putRelation(this.getProjectId(), entity.id, RT.HAS_DOCUMENT);
|
|
430
|
+
}
|
|
294
431
|
// 如果有模块关联,创建 module -> doc 关系
|
|
295
432
|
if (finalModuleId) {
|
|
296
433
|
const modEntity = this.findEntityByProp(ET.MODULE, 'moduleId', finalModuleId);
|
|
@@ -307,6 +444,8 @@ class DevPlanGraphStore {
|
|
|
307
444
|
}
|
|
308
445
|
}
|
|
309
446
|
}
|
|
447
|
+
// 语义搜索:自动为新文档生成 Embedding 并索引
|
|
448
|
+
this.autoIndexDocument(entity.id, input.title, input.content);
|
|
310
449
|
this.graph.flush();
|
|
311
450
|
return entity.id;
|
|
312
451
|
}
|
|
@@ -341,16 +480,135 @@ class DevPlanGraphStore {
|
|
|
341
480
|
});
|
|
342
481
|
}
|
|
343
482
|
searchSections(query, limit = 10) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
483
|
+
// 默认使用 hybrid 模式(语义+字面),无语义搜索时回退字面
|
|
484
|
+
const mode = this.semanticSearchReady ? 'hybrid' : 'literal';
|
|
485
|
+
return this.searchSectionsAdvanced(query, { mode, limit }).map(({ score, ...doc }) => doc);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* 高级搜索:支持 literal / semantic / hybrid 三种模式
|
|
489
|
+
*
|
|
490
|
+
* - literal: 纯字面匹配(标题+内容包含查询词)
|
|
491
|
+
* - semantic: 纯语义搜索(embed(query) → searchEntitiesByVector)
|
|
492
|
+
* - hybrid: 字面+语义 RRF 融合排序
|
|
493
|
+
*
|
|
494
|
+
* 当 VibeSynapse 不可用时,semantic/hybrid 模式自动降级为 literal。
|
|
495
|
+
*/
|
|
496
|
+
searchSectionsAdvanced(query, options) {
|
|
497
|
+
const mode = options?.mode || 'hybrid';
|
|
498
|
+
const limit = options?.limit || 10;
|
|
499
|
+
const minScore = options?.minScore || 0;
|
|
500
|
+
// ---- Literal Search ----
|
|
501
|
+
const literalResults = this.literalSearch(query);
|
|
502
|
+
// ---- If no semantic search or literal-only mode ----
|
|
503
|
+
if (mode === 'literal' || !this.semanticSearchReady || !this.synapse) {
|
|
504
|
+
return literalResults.slice(0, limit).map((doc) => ({ ...doc, score: undefined }));
|
|
505
|
+
}
|
|
506
|
+
// ---- Semantic Search ----
|
|
507
|
+
let semanticHits = [];
|
|
508
|
+
try {
|
|
509
|
+
const embedding = this.synapse.embed(query);
|
|
510
|
+
semanticHits = this.graph.searchEntitiesByVector(embedding, limit * 2, ET.DOC);
|
|
511
|
+
}
|
|
512
|
+
catch (e) {
|
|
513
|
+
console.warn(`[DevPlan] Semantic search failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
514
|
+
// 降级为字面搜索
|
|
515
|
+
return literalResults.slice(0, limit).map((doc) => ({ ...doc, score: undefined }));
|
|
516
|
+
}
|
|
517
|
+
if (mode === 'semantic') {
|
|
518
|
+
// 纯语义模式:直接返回语义搜索结果
|
|
519
|
+
const docs = [];
|
|
520
|
+
for (const hit of semanticHits) {
|
|
521
|
+
if (minScore > 0 && hit.score < minScore)
|
|
522
|
+
continue;
|
|
523
|
+
const entity = this.graph.getEntity(hit.entityId);
|
|
524
|
+
if (entity && entity.properties?.projectName === this.projectName) {
|
|
525
|
+
docs.push({ ...this.entityToDevPlanDoc(entity), score: hit.score });
|
|
526
|
+
}
|
|
527
|
+
if (docs.length >= limit)
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
return docs;
|
|
531
|
+
}
|
|
532
|
+
// ---- Hybrid Mode: RRF Fusion ----
|
|
533
|
+
return this.rrfFusion(semanticHits, literalResults, limit, minScore);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* 重建所有文档的向量索引
|
|
537
|
+
*
|
|
538
|
+
* 适用于:首次启用语义搜索、模型切换、索引损坏修复。
|
|
539
|
+
*/
|
|
540
|
+
rebuildIndex() {
|
|
541
|
+
const startTime = Date.now();
|
|
542
|
+
const docs = this.listSections();
|
|
543
|
+
let indexed = 0;
|
|
544
|
+
let failed = 0;
|
|
545
|
+
const failedDocIds = [];
|
|
546
|
+
if (!this.semanticSearchReady || !this.synapse) {
|
|
547
|
+
return {
|
|
548
|
+
total: docs.length,
|
|
549
|
+
indexed: 0,
|
|
550
|
+
failed: docs.length,
|
|
551
|
+
durationMs: Date.now() - startTime,
|
|
552
|
+
failedDocIds: docs.map((d) => d.id),
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
for (const doc of docs) {
|
|
556
|
+
try {
|
|
557
|
+
const text = `${doc.title}\n${doc.content}`;
|
|
558
|
+
const embedding = this.synapse.embed(text);
|
|
559
|
+
this.graph.indexEntity(doc.id, embedding);
|
|
560
|
+
indexed++;
|
|
561
|
+
}
|
|
562
|
+
catch (e) {
|
|
563
|
+
failed++;
|
|
564
|
+
failedDocIds.push(doc.id);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
this.graph.flush();
|
|
568
|
+
return {
|
|
569
|
+
total: docs.length,
|
|
570
|
+
indexed,
|
|
571
|
+
failed,
|
|
572
|
+
durationMs: Date.now() - startTime,
|
|
573
|
+
failedDocIds: failedDocIds.length > 0 ? failedDocIds : undefined,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* 检查语义搜索是否可用
|
|
578
|
+
*/
|
|
579
|
+
isSemanticSearchEnabled() {
|
|
580
|
+
return this.semanticSearchReady;
|
|
349
581
|
}
|
|
350
582
|
deleteSection(section, subSection) {
|
|
351
583
|
const existing = this.getSection(section, subSection);
|
|
352
584
|
if (!existing)
|
|
353
585
|
return false;
|
|
586
|
+
// 断开 DOC_HAS_CHILD 入向关系(从父文档指向本文档的)
|
|
587
|
+
const parentRels = this.getInRelations(existing.id, RT.DOC_HAS_CHILD);
|
|
588
|
+
for (const rel of parentRels) {
|
|
589
|
+
this.graph.deleteRelation(rel.id);
|
|
590
|
+
}
|
|
591
|
+
// 断开 DOC_HAS_CHILD 出向关系(本文档指向子文档的),子文档的 parentDoc 属性清空
|
|
592
|
+
const childRels = this.getOutRelations(existing.id, RT.DOC_HAS_CHILD);
|
|
593
|
+
for (const rel of childRels) {
|
|
594
|
+
this.graph.deleteRelation(rel.id);
|
|
595
|
+
// 清除子文档的 parentDoc 属性
|
|
596
|
+
const childEntity = this.graph.getEntity(rel.target);
|
|
597
|
+
if (childEntity) {
|
|
598
|
+
this.graph.updateEntity(childEntity.id, {
|
|
599
|
+
properties: { parentDoc: null },
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// 语义搜索:删除文档对应的向量索引
|
|
604
|
+
if (this.semanticSearchReady) {
|
|
605
|
+
try {
|
|
606
|
+
this.graph.removeEntityVector(existing.id);
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
// 向量可能不存在,忽略错误
|
|
610
|
+
}
|
|
611
|
+
}
|
|
354
612
|
this.graph.deleteEntity(existing.id);
|
|
355
613
|
this.graph.flush();
|
|
356
614
|
return true;
|
|
@@ -364,6 +622,8 @@ class DevPlanGraphStore {
|
|
|
364
622
|
throw new Error(`Main task "${input.taskId}" already exists for project "${this.projectName}"`);
|
|
365
623
|
}
|
|
366
624
|
const now = Date.now();
|
|
625
|
+
// 如果未指定 order,自动分配为当前最大 order + 1
|
|
626
|
+
const order = input.order != null ? input.order : this.getNextMainTaskOrder();
|
|
367
627
|
const entity = this.graph.addEntity(input.title, ET.MAIN_TASK, {
|
|
368
628
|
projectName: this.projectName,
|
|
369
629
|
taskId: input.taskId,
|
|
@@ -376,6 +636,7 @@ class DevPlanGraphStore {
|
|
|
376
636
|
totalSubtasks: 0,
|
|
377
637
|
completedSubtasks: 0,
|
|
378
638
|
status: 'pending',
|
|
639
|
+
order,
|
|
379
640
|
createdAt: now,
|
|
380
641
|
updatedAt: now,
|
|
381
642
|
completedAt: null,
|
|
@@ -426,6 +687,7 @@ class DevPlanGraphStore {
|
|
|
426
687
|
const now = Date.now();
|
|
427
688
|
const completedAt = finalStatus === 'completed' ? (existing.completedAt || now) : null;
|
|
428
689
|
const finalModuleId = input.moduleId || existing.moduleId;
|
|
690
|
+
const finalOrder = input.order != null ? input.order : existing.order;
|
|
429
691
|
this.graph.updateEntity(existing.id, {
|
|
430
692
|
name: input.title,
|
|
431
693
|
properties: {
|
|
@@ -436,6 +698,7 @@ class DevPlanGraphStore {
|
|
|
436
698
|
relatedSections: input.relatedSections || existing.relatedSections || [],
|
|
437
699
|
moduleId: finalModuleId || null,
|
|
438
700
|
status: finalStatus,
|
|
701
|
+
order: finalOrder,
|
|
439
702
|
updatedAt: now,
|
|
440
703
|
completedAt,
|
|
441
704
|
},
|
|
@@ -480,7 +743,8 @@ class DevPlanGraphStore {
|
|
|
480
743
|
if (filter?.moduleId) {
|
|
481
744
|
entities = entities.filter((e) => e.properties.moduleId === filter.moduleId);
|
|
482
745
|
}
|
|
483
|
-
|
|
746
|
+
const tasks = entities.map((e) => this.entityToMainTask(e));
|
|
747
|
+
return this.sortByOrder(tasks);
|
|
484
748
|
}
|
|
485
749
|
updateMainTaskStatus(taskId, status) {
|
|
486
750
|
const mainTask = this.getMainTask(taskId);
|
|
@@ -512,6 +776,8 @@ class DevPlanGraphStore {
|
|
|
512
776
|
throw new Error(`Parent main task "${input.parentTaskId}" not found for project "${this.projectName}"`);
|
|
513
777
|
}
|
|
514
778
|
const now = Date.now();
|
|
779
|
+
// 如果未指定 order,自动分配为当前父任务下最大 order + 1
|
|
780
|
+
const order = input.order != null ? input.order : this.getNextSubTaskOrder(input.parentTaskId);
|
|
515
781
|
const entity = this.graph.addEntity(input.title, ET.SUB_TASK, {
|
|
516
782
|
projectName: this.projectName,
|
|
517
783
|
taskId: input.taskId,
|
|
@@ -521,6 +787,7 @@ class DevPlanGraphStore {
|
|
|
521
787
|
relatedFiles: input.relatedFiles || [],
|
|
522
788
|
description: input.description || '',
|
|
523
789
|
status: 'pending',
|
|
790
|
+
order,
|
|
524
791
|
createdAt: now,
|
|
525
792
|
updatedAt: now,
|
|
526
793
|
completedAt: null,
|
|
@@ -542,6 +809,7 @@ class DevPlanGraphStore {
|
|
|
542
809
|
throw new Error(`Parent main task "${input.parentTaskId}" not found for project "${this.projectName}"`);
|
|
543
810
|
}
|
|
544
811
|
const now = Date.now();
|
|
812
|
+
const order = input.order != null ? input.order : this.getNextSubTaskOrder(input.parentTaskId);
|
|
545
813
|
const entity = this.graph.addEntity(input.title, ET.SUB_TASK, {
|
|
546
814
|
projectName: this.projectName,
|
|
547
815
|
taskId: input.taskId,
|
|
@@ -551,6 +819,7 @@ class DevPlanGraphStore {
|
|
|
551
819
|
relatedFiles: input.relatedFiles || [],
|
|
552
820
|
description: input.description || '',
|
|
553
821
|
status: targetStatus,
|
|
822
|
+
order,
|
|
554
823
|
createdAt: now,
|
|
555
824
|
updatedAt: now,
|
|
556
825
|
completedAt: targetStatus === 'completed' ? now : null,
|
|
@@ -571,10 +840,12 @@ class DevPlanGraphStore {
|
|
|
571
840
|
}
|
|
572
841
|
}
|
|
573
842
|
// 检查是否有实质变化
|
|
843
|
+
const newOrder = input.order != null ? input.order : existing.order;
|
|
574
844
|
if (existing.title === input.title &&
|
|
575
845
|
existing.description === (input.description || '') &&
|
|
576
846
|
existing.status === finalStatus &&
|
|
577
|
-
existing.estimatedHours === (input.estimatedHours || 0)
|
|
847
|
+
existing.estimatedHours === (input.estimatedHours || 0) &&
|
|
848
|
+
existing.order === newOrder) {
|
|
578
849
|
return existing;
|
|
579
850
|
}
|
|
580
851
|
const now = Date.now();
|
|
@@ -587,6 +858,7 @@ class DevPlanGraphStore {
|
|
|
587
858
|
relatedFiles: input.relatedFiles || existing.relatedFiles || [],
|
|
588
859
|
description: input.description || existing.description || '',
|
|
589
860
|
status: finalStatus,
|
|
861
|
+
order: newOrder,
|
|
590
862
|
updatedAt: now,
|
|
591
863
|
completedAt,
|
|
592
864
|
},
|
|
@@ -605,7 +877,8 @@ class DevPlanGraphStore {
|
|
|
605
877
|
if (filter?.status) {
|
|
606
878
|
entities = entities.filter((e) => e.properties.status === filter.status);
|
|
607
879
|
}
|
|
608
|
-
|
|
880
|
+
const tasks = entities.map((e) => this.entityToSubTask(e));
|
|
881
|
+
return this.sortByOrder(tasks);
|
|
609
882
|
}
|
|
610
883
|
updateSubTaskStatus(taskId, status, options) {
|
|
611
884
|
const subTask = this.getSubTask(taskId);
|
|
@@ -693,6 +966,7 @@ class DevPlanGraphStore {
|
|
|
693
966
|
title: mt.title,
|
|
694
967
|
priority: mt.priority,
|
|
695
968
|
status: mt.status,
|
|
969
|
+
order: mt.order,
|
|
696
970
|
total: subs.length,
|
|
697
971
|
completed: subCompleted,
|
|
698
972
|
percent: subs.length > 0 ? Math.round((subCompleted / subs.length) * 100) : 0,
|
|
@@ -908,6 +1182,45 @@ class DevPlanGraphStore {
|
|
|
908
1182
|
return tasks;
|
|
909
1183
|
}
|
|
910
1184
|
// ==========================================================================
|
|
1185
|
+
// Document Hierarchy (文档层级关系)
|
|
1186
|
+
// ==========================================================================
|
|
1187
|
+
/**
|
|
1188
|
+
* 获取文档的直接子文档列表(通过 DOC_HAS_CHILD 出向关系)
|
|
1189
|
+
*/
|
|
1190
|
+
getChildDocs(section, subSection) {
|
|
1191
|
+
const docEntity = this.findDocEntityBySection(section, subSection);
|
|
1192
|
+
if (!docEntity)
|
|
1193
|
+
return [];
|
|
1194
|
+
const rels = this.getOutRelations(docEntity.id, RT.DOC_HAS_CHILD);
|
|
1195
|
+
const children = [];
|
|
1196
|
+
for (const rel of rels) {
|
|
1197
|
+
const childEntity = this.graph.getEntity(rel.target);
|
|
1198
|
+
if (childEntity) {
|
|
1199
|
+
children.push(this.entityToDevPlanDoc(childEntity));
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return children;
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* 获取文档树(递归,含所有后代文档)
|
|
1206
|
+
*/
|
|
1207
|
+
getDocTree(section, subSection) {
|
|
1208
|
+
const doc = this.getSection(section, subSection);
|
|
1209
|
+
if (!doc)
|
|
1210
|
+
return null;
|
|
1211
|
+
return this.buildDocTree(doc);
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* 递归构建文档树
|
|
1215
|
+
*/
|
|
1216
|
+
buildDocTree(doc) {
|
|
1217
|
+
const children = this.getChildDocs(doc.section, doc.subSection);
|
|
1218
|
+
return {
|
|
1219
|
+
doc,
|
|
1220
|
+
children: children.map((child) => this.buildDocTree(child)),
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
// ==========================================================================
|
|
911
1224
|
// Utility
|
|
912
1225
|
// ==========================================================================
|
|
913
1226
|
sync() {
|
|
@@ -1042,13 +1355,32 @@ class DevPlanGraphStore {
|
|
|
1042
1355
|
section: doc.section,
|
|
1043
1356
|
subSection: doc.subSection,
|
|
1044
1357
|
version: doc.version,
|
|
1358
|
+
parentDoc: doc.parentDoc || null,
|
|
1359
|
+
childDocs: doc.childDocs || [],
|
|
1045
1360
|
},
|
|
1046
1361
|
});
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1362
|
+
// 子文档不连接项目节点,仅通过 doc_has_child 连接父文档
|
|
1363
|
+
if (!doc.parentDoc) {
|
|
1364
|
+
edges.push({
|
|
1365
|
+
from: this.getProjectId(),
|
|
1366
|
+
to: doc.id,
|
|
1367
|
+
label: RT.HAS_DOCUMENT,
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
// doc_has_child 关系(文档 → 子文档)
|
|
1371
|
+
if (doc.childDocs?.length) {
|
|
1372
|
+
const docEntity = this.findDocEntityBySection(doc.section, doc.subSection);
|
|
1373
|
+
if (docEntity) {
|
|
1374
|
+
const childRels = this.getOutRelations(docEntity.id, RT.DOC_HAS_CHILD);
|
|
1375
|
+
for (const rel of childRels) {
|
|
1376
|
+
edges.push({
|
|
1377
|
+
from: doc.id,
|
|
1378
|
+
to: rel.target,
|
|
1379
|
+
label: RT.DOC_HAS_CHILD,
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1052
1384
|
}
|
|
1053
1385
|
}
|
|
1054
1386
|
// 模块节点
|
|
@@ -1124,6 +1456,46 @@ class DevPlanGraphStore {
|
|
|
1124
1456
|
// ==========================================================================
|
|
1125
1457
|
// Private Helpers
|
|
1126
1458
|
// ==========================================================================
|
|
1459
|
+
/**
|
|
1460
|
+
* 获取下一个主任务的 order 值(当前最大 order + 1)
|
|
1461
|
+
*/
|
|
1462
|
+
getNextMainTaskOrder() {
|
|
1463
|
+
const entities = this.findEntitiesByType(ET.MAIN_TASK);
|
|
1464
|
+
let maxOrder = 0;
|
|
1465
|
+
for (const e of entities) {
|
|
1466
|
+
const o = e.properties.order;
|
|
1467
|
+
if (typeof o === 'number' && o > maxOrder) {
|
|
1468
|
+
maxOrder = o;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
return maxOrder + 1;
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* 获取下一个子任务的 order 值(当前父任务下最大 order + 1)
|
|
1475
|
+
*/
|
|
1476
|
+
getNextSubTaskOrder(parentTaskId) {
|
|
1477
|
+
const entities = this.findEntitiesByType(ET.SUB_TASK).filter((e) => e.properties.parentTaskId === parentTaskId);
|
|
1478
|
+
let maxOrder = 0;
|
|
1479
|
+
for (const e of entities) {
|
|
1480
|
+
const o = e.properties.order;
|
|
1481
|
+
if (typeof o === 'number' && o > maxOrder) {
|
|
1482
|
+
maxOrder = o;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
return maxOrder + 1;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* 按 order 字段排序(order 为空的排到最后,order 相同则按 createdAt 排)
|
|
1489
|
+
*/
|
|
1490
|
+
sortByOrder(items) {
|
|
1491
|
+
return items.sort((a, b) => {
|
|
1492
|
+
const oa = a.order != null ? a.order : Number.MAX_SAFE_INTEGER;
|
|
1493
|
+
const ob = b.order != null ? b.order : Number.MAX_SAFE_INTEGER;
|
|
1494
|
+
if (oa !== ob)
|
|
1495
|
+
return oa - ob;
|
|
1496
|
+
return a.createdAt - b.createdAt;
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1127
1499
|
refreshMainTaskCounts(mainTaskId) {
|
|
1128
1500
|
const mainTask = this.getMainTask(mainTaskId);
|
|
1129
1501
|
if (!mainTask)
|
|
@@ -1194,6 +1566,45 @@ class DevPlanGraphStore {
|
|
|
1194
1566
|
}
|
|
1195
1567
|
}
|
|
1196
1568
|
}
|
|
1569
|
+
/**
|
|
1570
|
+
* 更新文档的父文档关系(DOC_HAS_CHILD)
|
|
1571
|
+
*
|
|
1572
|
+
* 移除旧的父文档关系,建立新的父文档关系。
|
|
1573
|
+
*/
|
|
1574
|
+
updateParentDocRelation(docEntityId, oldParentDoc, newParentDoc) {
|
|
1575
|
+
const projectId = this.getProjectId();
|
|
1576
|
+
// 移除旧的父文档关系(入向 DOC_HAS_CHILD)
|
|
1577
|
+
if (oldParentDoc) {
|
|
1578
|
+
const [oldSection, oldSub] = oldParentDoc.split('|');
|
|
1579
|
+
const oldParentEntity = this.findDocEntityBySection(oldSection, oldSub || undefined);
|
|
1580
|
+
if (oldParentEntity) {
|
|
1581
|
+
const rel = this.graph.getRelationBetween(oldParentEntity.id, docEntityId);
|
|
1582
|
+
if (rel)
|
|
1583
|
+
this.graph.deleteRelation(rel.id);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
// 建立新的父文档关系
|
|
1587
|
+
if (newParentDoc) {
|
|
1588
|
+
const [newSection, newSub] = newParentDoc.split('|');
|
|
1589
|
+
const newParentEntity = this.findDocEntityBySection(newSection, newSub || undefined);
|
|
1590
|
+
if (newParentEntity) {
|
|
1591
|
+
this.graph.putRelation(newParentEntity.id, docEntityId, RT.DOC_HAS_CHILD);
|
|
1592
|
+
}
|
|
1593
|
+
// 从顶级变为子文档 → 移除 project -> doc 的 has_document 关系
|
|
1594
|
+
if (!oldParentDoc) {
|
|
1595
|
+
const hasDocRels = this.getInRelations(docEntityId, RT.HAS_DOCUMENT);
|
|
1596
|
+
for (const rel of hasDocRels) {
|
|
1597
|
+
if (rel.source === projectId) {
|
|
1598
|
+
this.graph.deleteRelation(rel.id);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
else if (oldParentDoc && !newParentDoc) {
|
|
1604
|
+
// 从子文档变为顶级 → 添加 project -> doc 关系
|
|
1605
|
+
this.graph.putRelation(projectId, docEntityId, RT.HAS_DOCUMENT);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1197
1608
|
getCurrentGitCommit() {
|
|
1198
1609
|
try {
|
|
1199
1610
|
const { execSync } = require('child_process');
|
|
@@ -1226,6 +1637,83 @@ class DevPlanGraphStore {
|
|
|
1226
1637
|
const empty = total - filled;
|
|
1227
1638
|
return `[${'█'.repeat(filled)}${'░'.repeat(empty)}]`;
|
|
1228
1639
|
}
|
|
1640
|
+
// ==========================================================================
|
|
1641
|
+
// Semantic Search Helpers
|
|
1642
|
+
// ==========================================================================
|
|
1643
|
+
/**
|
|
1644
|
+
* 自动为文档生成 Embedding 并索引到 SocialGraphV2 向量搜索层
|
|
1645
|
+
*
|
|
1646
|
+
* 将 title + content 拼接后生成 Embedding,以 entity.id 为 key 存入 HNSW 索引。
|
|
1647
|
+
* 失败时仅输出警告,不影响文档保存。
|
|
1648
|
+
*/
|
|
1649
|
+
autoIndexDocument(entityId, title, content) {
|
|
1650
|
+
if (!this.semanticSearchReady || !this.synapse)
|
|
1651
|
+
return;
|
|
1652
|
+
try {
|
|
1653
|
+
const text = `${title}\n${content}`;
|
|
1654
|
+
const embedding = this.synapse.embed(text);
|
|
1655
|
+
this.graph.indexEntity(entityId, embedding);
|
|
1656
|
+
}
|
|
1657
|
+
catch (e) {
|
|
1658
|
+
console.warn(`[DevPlan] Failed to index document ${entityId}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* 字面搜索(标题/内容包含查询词)
|
|
1663
|
+
*/
|
|
1664
|
+
literalSearch(query) {
|
|
1665
|
+
const queryLower = query.toLowerCase();
|
|
1666
|
+
return this.listSections().filter((doc) => doc.content.toLowerCase().includes(queryLower) ||
|
|
1667
|
+
doc.title.toLowerCase().includes(queryLower));
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* RRF (Reciprocal Rank Fusion) 融合排序
|
|
1671
|
+
*
|
|
1672
|
+
* 将语义搜索和字面搜索的结果通过 RRF 公式融合:
|
|
1673
|
+
* score(d) = Σ 1/(k + rank_i(d))
|
|
1674
|
+
* 其中 k=60 是标准 RRF 常数。
|
|
1675
|
+
*/
|
|
1676
|
+
rrfFusion(semanticHits, literalResults, limit, minScore) {
|
|
1677
|
+
const RRF_K = 60;
|
|
1678
|
+
const rrfScores = new Map();
|
|
1679
|
+
const docMap = new Map();
|
|
1680
|
+
// 语义搜索结果贡献
|
|
1681
|
+
for (let i = 0; i < semanticHits.length; i++) {
|
|
1682
|
+
const hit = semanticHits[i];
|
|
1683
|
+
const rrf = 1 / (RRF_K + i + 1);
|
|
1684
|
+
rrfScores.set(hit.entityId, (rrfScores.get(hit.entityId) || 0) + rrf);
|
|
1685
|
+
}
|
|
1686
|
+
// 字面搜索结果贡献
|
|
1687
|
+
for (let i = 0; i < literalResults.length; i++) {
|
|
1688
|
+
const doc = literalResults[i];
|
|
1689
|
+
const rrf = 1 / (RRF_K + i + 1);
|
|
1690
|
+
rrfScores.set(doc.id, (rrfScores.get(doc.id) || 0) + rrf);
|
|
1691
|
+
docMap.set(doc.id, doc);
|
|
1692
|
+
}
|
|
1693
|
+
// 按 RRF 评分排序
|
|
1694
|
+
const sorted = Array.from(rrfScores.entries())
|
|
1695
|
+
.sort((a, b) => b[1] - a[1]);
|
|
1696
|
+
// 组装结果
|
|
1697
|
+
const results = [];
|
|
1698
|
+
for (const [id, score] of sorted) {
|
|
1699
|
+
if (minScore > 0 && score < minScore)
|
|
1700
|
+
continue;
|
|
1701
|
+
if (results.length >= limit)
|
|
1702
|
+
break;
|
|
1703
|
+
// 优先从 docMap 获取(字面搜索已解析过的),否则从图中获取
|
|
1704
|
+
let doc = docMap.get(id);
|
|
1705
|
+
if (!doc) {
|
|
1706
|
+
const entity = this.graph.getEntity(id);
|
|
1707
|
+
if (entity && entity.properties?.projectName === this.projectName) {
|
|
1708
|
+
doc = this.entityToDevPlanDoc(entity);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
if (doc) {
|
|
1712
|
+
results.push({ ...doc, score });
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
return results;
|
|
1716
|
+
}
|
|
1229
1717
|
}
|
|
1230
1718
|
exports.DevPlanGraphStore = DevPlanGraphStore;
|
|
1231
1719
|
//# sourceMappingURL=dev-plan-graph-store.js.map
|