noteconnection 1.8.0 → 1.9.1
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 +257 -1803
- package/dist/src/agent_workspace.contract.parity.test.js +105 -421
- package/dist/src/agent_workspace.frontend.test.js +410 -11
- package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
- package/dist/src/appimage.patchelf.test.js +113 -0
- package/dist/src/appimage.portability.test.js +156 -0
- package/dist/src/appimage.update.test.js +127 -0
- package/dist/src/backend/FileLoader.js +18 -4
- package/dist/src/backend/FileLoader.test.js +125 -0
- package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
- package/dist/src/backend/GraphBuilder.js +66 -18
- package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
- package/dist/src/backend/GraphMetrics.js +0 -3
- package/dist/src/backend/ResourceIdentity.js +155 -0
- package/dist/src/backend/ResourceIdentity.test.js +113 -0
- package/dist/src/backend/algorithms/CycleDetection.js +0 -1
- package/dist/src/backend/algorithms/HybridEngine.js +0 -3
- package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
- package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
- package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
- package/dist/src/backend/build_dag.js +0 -1
- package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
- package/dist/src/capacitor.runtime.contract.test.js +11 -2
- package/dist/src/core/Graph.js +138 -42
- package/dist/src/core/Graph.test.js +74 -0
- package/dist/src/core/NoteConnection.js +2 -2
- package/dist/src/core/PathBridge.js +265 -2
- package/dist/src/core/ResourceReference.js +17 -0
- package/dist/src/desktop.godot.pack.test.js +142 -0
- package/dist/src/desktop.runtime.graph.test.js +66 -0
- package/dist/src/export/WorkspaceExportBundle.js +44 -0
- package/dist/src/export/WorkspaceExportBundle.test.js +61 -0
- package/dist/src/foundation.release.evidence.contract.test.js +127 -9
- package/dist/src/foundation.sqlite.runtime.contract.test.js +6 -0
- package/dist/src/frontend/README.md +257 -1803
- package/dist/src/frontend/agent_workspace.js +665 -7
- package/dist/src/frontend/index.html +31 -2
- package/dist/src/frontend/knowledge_projection_contract.js +230 -0
- package/dist/src/frontend/knowledge_projection_store.js +214 -0
- package/dist/src/frontend/libs/path_core.js +158 -42
- package/dist/src/frontend/locales/en.json +32 -0
- package/dist/src/frontend/locales/zh.json +32 -0
- package/dist/src/frontend/mobile_budget_runtime.js +33 -0
- package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
- package/dist/src/frontend/mobile_identity_contract.js +221 -0
- package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
- package/dist/src/frontend/path.html +6 -2
- package/dist/src/frontend/simulationWorker.js +1 -1
- package/dist/src/frontend/source_manager.js +110 -4
- package/dist/src/frontend/storage_provider.js +661 -71
- package/dist/src/frontend/styles.css +107 -13
- package/dist/src/frontend/workspace_panes.js +111 -7
- package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
- package/dist/src/graph.answer.operator.contract.test.js +72 -0
- package/dist/src/index.js +6 -2
- package/dist/src/index.knowledge-root.test.js +118 -0
- package/dist/src/indexing/IndexLifecycle.js +22 -0
- package/dist/src/indexing/IndexLifecycle.test.js +24 -0
- package/dist/src/knowledge.api.contract.test.js +4 -2
- package/dist/src/knowledge_projection.contract.test.js +134 -0
- package/dist/src/knowledge_projection_store.test.js +200 -0
- package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
- package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
- package/dist/src/learning/KnowledgeLearningPlatform.js +4687 -3892
- package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +418 -0
- package/dist/src/learning/KnowledgeLearningPlatform.test.js +243 -6
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +25 -1
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +13 -5
- package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
- package/dist/src/learning/PublicAnswerContract.test.js +85 -0
- package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
- package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
- package/dist/src/learning/agentConversationExecution.js +211 -0
- package/dist/src/learning/agentConversationExecution.test.js +209 -0
- package/dist/src/learning/agentConversationSerialization.js +266 -0
- package/dist/src/learning/agentConversationSerialization.test.js +165 -0
- package/dist/src/learning/agentResponseBudget.js +179 -0
- package/dist/src/learning/agentResponseBudget.test.js +97 -0
- package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
- package/dist/src/learning/answerReleaseReview.js +1191 -250
- package/dist/src/learning/answerReleaseReview.test.js +1293 -7
- package/dist/src/learning/answerTaskPlan.js +473 -0
- package/dist/src/learning/conversationComposer.js +627 -386
- package/dist/src/learning/conversationComposer.test.js +1403 -0
- package/dist/src/learning/evidenceContextAssembler.js +312 -120
- package/dist/src/learning/graphAnswerCoverage.js +37 -0
- package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
- package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
- package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
- package/dist/src/learning/graphAnswerFacts.js +94 -0
- package/dist/src/learning/graphAnswerFacts.test.js +39 -0
- package/dist/src/learning/graphAnswerPlan.js +908 -0
- package/dist/src/learning/graphAnswerPlan.test.js +907 -0
- package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
- package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
- package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
- package/dist/src/learning/graphClaimMatcher.js +104 -0
- package/dist/src/learning/graphConditionedContext.js +109 -0
- package/dist/src/learning/graphConditionedContext.test.js +99 -0
- package/dist/src/learning/graphContextAssembler.js +3 -13
- package/dist/src/learning/graphContextAssembler.test.js +2 -2
- package/dist/src/learning/graphExpansionPolicy.js +22 -0
- package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
- package/dist/src/learning/identityMetadata.test.js +26 -0
- package/dist/src/learning/index.js +1 -0
- package/dist/src/learning/mobileAnswerProjection.js +104 -0
- package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
- package/dist/src/learning/queryBackend.js +34 -10
- package/dist/src/learning/queryBackend.test.js +21 -0
- package/dist/src/learning/ragContextPack.js +115 -16
- package/dist/src/learning/ragContextPack.test.js +151 -0
- package/dist/src/learning/ragEvidenceQuality.js +96 -0
- package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
- package/dist/src/learning/ragPublicText.js +67 -4
- package/dist/src/learning/ragPublicText.test.js +57 -0
- package/dist/src/learning/ragSufficiencyJudge.js +11 -1
- package/dist/src/learning/ragSufficiencyProviderJudge.js +16 -26
- package/dist/src/learning/ragSufficiencyProviderJudge.test.js +51 -0
- package/dist/src/learning/requestNormalization.js +106 -0
- package/dist/src/learning/requestNormalization.test.js +101 -0
- package/dist/src/learning/store.js +116 -12
- package/dist/src/learning/store.test.js +299 -0
- package/dist/src/learning/vectorAccelerationAdapter.js +22 -6
- package/dist/src/learning/vectorAccelerationAdapter.test.js +30 -0
- package/dist/src/learning/workspaceHydration.js +168 -0
- package/dist/src/learning/workspaceHydration.test.js +30 -0
- package/dist/src/license.policy.contract.test.js +9 -4
- package/dist/src/markdown/MarkdownGateway.js +4 -0
- package/dist/src/markdown/MarkdownGateway.test.js +59 -0
- package/dist/src/middleware/SseResponseWriter.js +122 -0
- package/dist/src/middleware/SseResponseWriter.test.js +54 -0
- package/dist/src/middleware/auth.js +28 -7
- package/dist/src/middleware/auth.test.js +33 -0
- package/dist/src/middleware/index.js +2 -1
- package/dist/src/mobile.android.evidence.contract.test.js +146 -0
- package/dist/src/mobile.android.signing.contract.test.js +66 -0
- package/dist/src/mobile.artifact.contract.test.js +144 -0
- package/dist/src/mobile.budget.contract.test.js +90 -0
- package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
- package/dist/src/mobile.graph.parity.contract.test.js +207 -0
- package/dist/src/mobile.identity.contract.test.js +66 -0
- package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
- package/dist/src/mobile.identity.corpus.test.js +40 -0
- package/dist/src/mobile.native.recovery.contract.test.js +64 -0
- package/dist/src/mobile.pipeline.test.js +11 -1
- package/dist/src/mobile.profile.contract.test.js +225 -0
- package/dist/src/mobile.semantic.parity.test.js +92 -0
- package/dist/src/mobile_exact_analyzer.test.js +113 -0
- package/dist/src/notemd/ContentGenerator.js +4 -9
- package/dist/src/notemd/FileProcessor.js +4 -9
- package/dist/src/notemd/NotemdService.js +12 -17
- package/dist/src/notemd/Translator.js +4 -9
- package/dist/src/notemd/progressReporter.js +9 -0
- package/dist/src/notemd.server.integration.test.js +119 -4
- package/dist/src/notemd.workflow.pipeline.test.js +1 -1
- package/dist/src/pathbridge.handshake.contract.test.js +29 -0
- package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
- package/dist/src/pathcore.bundle.contract.test.js +8 -0
- package/dist/src/platform/ExportProfile.js +28 -0
- package/dist/src/platform/MobileBudget.js +11 -0
- package/dist/src/platform/PlatformCapabilities.js +14 -0
- package/dist/src/platform/PlatformCapabilities.test.js +6 -0
- package/dist/src/query_backend.external_http.integration.test.js +2 -2
- package/dist/src/release.godot.mirror.contract.test.js +28 -4
- package/dist/src/resources/ResourceRegistry.js +40 -0
- package/dist/src/resources/ResourceRegistry.test.js +36 -0
- package/dist/src/routes/index.js +0 -4
- package/dist/src/routes/knowledge.js +46 -33
- package/dist/src/routes/knowledgePayload.js +185 -0
- package/dist/src/routes/knowledgePayload.test.js +53 -0
- package/dist/src/routes/markdown.js +8 -0
- package/dist/src/routes/notemd.js +990 -234
- package/dist/src/routes/registry.contract.test.js +5 -2
- package/dist/src/routes/registry.shadow.contract.test.js +22 -0
- package/dist/src/routes/render.js +14 -51
- package/dist/src/runtime.capabilities.test.js +18 -2
- package/dist/src/server.js +566 -1090
- package/dist/src/server.migration.test.js +417 -15
- package/dist/src/sidecar.build.lock.contract.test.js +201 -0
- package/dist/src/sidecar.freshness.contract.test.js +184 -0
- package/dist/src/sidecar.replacement.boundary.contract.test.js +6 -3
- package/dist/src/sidecar.supply.readiness.contract.test.js +11 -0
- package/dist/src/simulation.worker.offline.test.js +104 -0
- package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
- package/dist/src/storage.provider.contract.test.js +152 -1
- package/dist/src/tauri.frontend.build.contract.test.js +8 -0
- package/dist/src/tauri.window.evidence.execution.test.js +68 -0
- package/dist/src/utils/RuntimePaths.js +6 -0
- package/dist/src/utils/RuntimePaths.test.js +27 -0
- package/dist/src/windows.installer.build.test.js +109 -0
- package/dist/src/windows.installer.payload.test.js +76 -0
- package/dist/src/windows.installer.runtime.test.js +69 -0
- package/dist/src/workspace/WorkspaceRegistry.js +13 -0
- package/package.json +32 -10
- package/dist/src/routes/data.js +0 -267
- package/dist/src/routes/diagnostics.js +0 -51
|
@@ -29,12 +29,10 @@ class HybridEngine {
|
|
|
29
29
|
if (checkedPairs.has(pairKey))
|
|
30
30
|
return;
|
|
31
31
|
checkedPairs.add(pairKey);
|
|
32
|
-
// Get Reverse Metrics
|
|
33
32
|
const rowB = matrix.get(nodeB);
|
|
34
33
|
const metricsBtoA = rowB ? rowB.get(nodeA) : null;
|
|
35
34
|
if (!metricsBtoA)
|
|
36
35
|
return;
|
|
37
|
-
// 1. Check Vector Similarity
|
|
38
36
|
const vecA = vectorSpace.getVector(nodeA);
|
|
39
37
|
const vecB = vectorSpace.getVector(nodeB);
|
|
40
38
|
if (!vecA || !vecB)
|
|
@@ -43,7 +41,6 @@ class HybridEngine {
|
|
|
43
41
|
const similarity = vectorSpace.cosineSimilarity(vecA, vecB);
|
|
44
42
|
if (similarity < vectorThreshold)
|
|
45
43
|
return;
|
|
46
|
-
// 2. Check Asymmetry
|
|
47
44
|
// P(B|A) = metricsAtoB.conditionalProb
|
|
48
45
|
// P(A|B) = metricsBtoA.conditionalProb
|
|
49
46
|
const p_A_given_B = metricsBtoA.conditionalProb;
|
|
@@ -136,7 +136,6 @@ class LayoutEngine {
|
|
|
136
136
|
execArgv: isTsNode ? ['-r', require.resolve('ts-node/register')] : undefined
|
|
137
137
|
});
|
|
138
138
|
worker.on('message', (positions) => {
|
|
139
|
-
// Update graph nodes with computed positions
|
|
140
139
|
positions.forEach((pos, id) => {
|
|
141
140
|
const node = graph.getNode(id);
|
|
142
141
|
if (node) {
|
|
@@ -45,7 +45,6 @@ class StatisticalAnalyzer {
|
|
|
45
45
|
*/
|
|
46
46
|
static async analyzeAsync(files, terms) {
|
|
47
47
|
const termDocCounts = new Map();
|
|
48
|
-
// Initialize counts
|
|
49
48
|
terms.forEach(term => termDocCounts.set(term, 0));
|
|
50
49
|
// 1. Parallel Term Extraction
|
|
51
50
|
// 1. 并行术语提取
|
|
@@ -155,14 +154,11 @@ class StatisticalAnalyzer {
|
|
|
155
154
|
// Temporary storage for intersection counts: Source -> Target -> Count
|
|
156
155
|
// 临时存储交集计数:源 -> 目标 -> 计数
|
|
157
156
|
const intersectionCounts = new Map();
|
|
158
|
-
// Iterate over files to find co-occurring pairs
|
|
159
|
-
// 遍历文件以查找共现对
|
|
160
157
|
for (const termsInFile of Object.values(fileTermsMap)) {
|
|
161
158
|
// Ensure unique terms per file to avoid self-loops or double counting within same file
|
|
162
159
|
const uniqueTerms = Array.from(new Set(termsInFile));
|
|
163
160
|
for (let i = 0; i < uniqueTerms.length; i++) {
|
|
164
161
|
const source = uniqueTerms[i];
|
|
165
|
-
// Initialize row if needed
|
|
166
162
|
if (!intersectionCounts.has(source)) {
|
|
167
163
|
intersectionCounts.set(source, new Map());
|
|
168
164
|
}
|
|
@@ -105,12 +105,10 @@ class TopologicalSort {
|
|
|
105
105
|
const uRank = ranks.get(uId);
|
|
106
106
|
const neighbors = graph.getNeighbors(uId);
|
|
107
107
|
for (const vId of neighbors) {
|
|
108
|
-
// Update rank of v
|
|
109
108
|
const currentVRank = ranks.get(vId) || 0;
|
|
110
109
|
if (uRank + 1 > currentVRank) {
|
|
111
110
|
ranks.set(vId, uRank + 1);
|
|
112
111
|
}
|
|
113
|
-
// Decrement in-degree
|
|
114
112
|
const d = inDegrees.get(vId) - 1;
|
|
115
113
|
inDegrees.set(vId, d);
|
|
116
114
|
if (d === 0) {
|
|
@@ -42,11 +42,14 @@ describe('capacitor bridge serialization contract', () => {
|
|
|
42
42
|
const source = fs.readFileSync(storageProviderPath, 'utf8');
|
|
43
43
|
expect(source).toContain('CAPACITOR_BRIDGE_MAX_CHUNK_BYTES');
|
|
44
44
|
expect(source).toContain('CAPACITOR_GRAPH_SERIALIZATION_MAX_BYTES');
|
|
45
|
+
expect(source).toContain('getMobileRuntimeBudget().maxProjectionBytes');
|
|
46
|
+
expect(source).toContain('Math.min(\n CAPACITOR_GRAPH_SERIALIZATION_MAX_BYTES,');
|
|
45
47
|
expect(source).toContain('splitCapacitorPayloadIntoChunks');
|
|
46
48
|
expect(source).toContain('writeCapacitorChunkSequenceToDirectory');
|
|
47
49
|
expect(source).toContain('appendFile');
|
|
48
50
|
expect(source).toContain('createCapacitorGraphDataJsonChunks');
|
|
49
51
|
expect(source).toContain("serializationMode: 'chunked-bridge-json-stream'");
|
|
52
|
+
expect(source).toContain('maxPayloadBytes: graphSerializationMaxBytes');
|
|
50
53
|
expect(source).not.toContain('JSON.stringify(graphData, null, 2)');
|
|
51
54
|
});
|
|
52
55
|
});
|
|
@@ -69,7 +69,16 @@ describe('capacitor runtime parity closure contract', () => {
|
|
|
69
69
|
});
|
|
70
70
|
test('storage provider keeps capacitor local build fallback path', () => {
|
|
71
71
|
const source = fs.readFileSync(storageProviderPath, 'utf8');
|
|
72
|
-
expect(source).toContain('
|
|
72
|
+
expect(source).toContain('getMobileRuntimeBudget');
|
|
73
|
+
expect(source).toContain('measureUtf8Bytes');
|
|
74
|
+
expect(source).toContain('maxBytes: getMobileRuntimeBudget().maxDocumentBytes');
|
|
75
|
+
expect(source).toContain('maxBytes: getMobileRuntimeBudget().maxProjectionBytes');
|
|
76
|
+
expect(source).toContain('maxDocuments');
|
|
77
|
+
expect(source).toContain('maxBytes: budget.maxDocumentBytes');
|
|
78
|
+
expect(source).toContain('maxTotalInputBytes');
|
|
79
|
+
expect(source).toContain('maxDepth');
|
|
80
|
+
expect(source).toContain('const entryDepth = currentDepth + 1;');
|
|
81
|
+
expect(source).toContain("MOBILE_BUDGET_EXCEEDED");
|
|
73
82
|
expect(source).toContain('collectCapacitorMarkdownFiles');
|
|
74
83
|
expect(source).toContain('buildCapacitorGraphData');
|
|
75
84
|
expect(source).toContain('buildCapacitorGraphDataWithWorkerFallback');
|
|
@@ -82,7 +91,7 @@ describe('capacitor runtime parity closure contract', () => {
|
|
|
82
91
|
expect(source).toContain('supportsMobileWasmCompute');
|
|
83
92
|
expect(source).toContain('mobileWasmReason');
|
|
84
93
|
expect(source).toContain('capacitorBuildGraph');
|
|
85
|
-
expect(source).toContain("return await capacitorReadText(normalized
|
|
94
|
+
expect(source).toContain("return await capacitorReadText(normalized, {");
|
|
86
95
|
});
|
|
87
96
|
test('source panel no longer hard-blocks capacitor load action before cache/build flow', () => {
|
|
88
97
|
const source = fs.readFileSync(sourceManagerPath, 'utf8');
|
package/dist/src/core/Graph.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Graph = void 0;
|
|
4
|
+
const ResourceReference_1 = require("./ResourceReference");
|
|
4
5
|
/**
|
|
5
6
|
* Directed Graph implementation for managing notes and dependencies.
|
|
6
7
|
* 用于管理笔记和依赖关系的有向图实现。
|
|
@@ -10,6 +11,7 @@ class Graph {
|
|
|
10
11
|
this.nodes = new Map();
|
|
11
12
|
this.adjacencyList = new Map();
|
|
12
13
|
this.reverseAdjacencyList = new Map();
|
|
14
|
+
this.aliases = new Map();
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* Adds a node to the graph.
|
|
@@ -17,11 +19,90 @@ class Graph {
|
|
|
17
19
|
* @param node The node to add | 要添加的节点
|
|
18
20
|
*/
|
|
19
21
|
addNode(node) {
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
this.adjacencyList.set(node.id, []);
|
|
23
|
-
this.reverseAdjacencyList.set(node.id, []);
|
|
22
|
+
if (this.nodes.has(node.id)) {
|
|
23
|
+
return;
|
|
24
24
|
}
|
|
25
|
+
const aliases = [node.id, node.sourceUri, ...(node.identityAliases ?? [])]
|
|
26
|
+
.filter((alias) => typeof alias === 'string' && alias.length > 0);
|
|
27
|
+
const normalizedAliases = new Set(aliases.map(alias => (0, ResourceReference_1.normalizeResourceReference)(alias)));
|
|
28
|
+
normalizedAliases.forEach(alias => {
|
|
29
|
+
const existingNodeId = this.aliases.get(alias);
|
|
30
|
+
if (existingNodeId && existingNodeId !== node.id) {
|
|
31
|
+
throw new Error(`Resource identity alias collision: "${alias}" is claimed by both "${existingNodeId}" and "${node.id}"`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
this.nodes.set(node.id, { ...node, inDegree: 0, outDegree: 0 });
|
|
35
|
+
this.adjacencyList.set(node.id, []);
|
|
36
|
+
this.reverseAdjacencyList.set(node.id, []);
|
|
37
|
+
normalizedAliases.forEach(alias => {
|
|
38
|
+
this.aliases.set(alias, node.id);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Rehydrates a graph from either a current or legacy JSON snapshot.
|
|
43
|
+
*
|
|
44
|
+
* The snapshot is validated before mutating the returned graph. Edges must
|
|
45
|
+
* reference declared nodes so a corrupt or partially-written snapshot cannot
|
|
46
|
+
* silently create synthetic nodes and change graph semantics during replay.
|
|
47
|
+
*/
|
|
48
|
+
static fromJSON(data) {
|
|
49
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
50
|
+
throw new Error('Graph snapshot must be an object.');
|
|
51
|
+
}
|
|
52
|
+
const snapshot = data;
|
|
53
|
+
if (!Array.isArray(snapshot.nodes) || !Array.isArray(snapshot.edges)) {
|
|
54
|
+
throw new Error('Graph snapshot must contain nodes and edges arrays.');
|
|
55
|
+
}
|
|
56
|
+
const graph = new Graph();
|
|
57
|
+
const nodeIds = new Set();
|
|
58
|
+
snapshot.nodes.forEach((rawNode, index) => {
|
|
59
|
+
if (!rawNode || typeof rawNode !== 'object' || Array.isArray(rawNode)) {
|
|
60
|
+
throw new Error(`Graph snapshot node at index ${index} must be an object.`);
|
|
61
|
+
}
|
|
62
|
+
const node = rawNode;
|
|
63
|
+
if (typeof node.id !== 'string' || node.id.trim().length === 0) {
|
|
64
|
+
throw new Error(`Graph snapshot node at index ${index} requires a non-empty id.`);
|
|
65
|
+
}
|
|
66
|
+
if (nodeIds.has(node.id)) {
|
|
67
|
+
throw new Error(`Graph snapshot contains duplicate node id: ${node.id}.`);
|
|
68
|
+
}
|
|
69
|
+
nodeIds.add(node.id);
|
|
70
|
+
graph.addNode({
|
|
71
|
+
...node,
|
|
72
|
+
id: node.id,
|
|
73
|
+
label: typeof node.label === 'string' && node.label.length > 0 ? node.label : node.id,
|
|
74
|
+
inDegree: 0,
|
|
75
|
+
outDegree: 0,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
snapshot.edges.forEach((rawEdge, index) => {
|
|
79
|
+
if (!rawEdge || typeof rawEdge !== 'object' || Array.isArray(rawEdge)) {
|
|
80
|
+
throw new Error(`Graph snapshot edge at index ${index} must be an object.`);
|
|
81
|
+
}
|
|
82
|
+
const edge = rawEdge;
|
|
83
|
+
if (typeof edge.source !== 'string' || typeof edge.target !== 'string') {
|
|
84
|
+
throw new Error(`Graph snapshot edge at index ${index} requires source and target ids.`);
|
|
85
|
+
}
|
|
86
|
+
if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) {
|
|
87
|
+
throw new Error(`Graph snapshot edge at index ${index} references an undeclared node: ${edge.source} -> ${edge.target}.`);
|
|
88
|
+
}
|
|
89
|
+
graph.addEdge(edge.source, edge.target, edge.type, edge.weight);
|
|
90
|
+
});
|
|
91
|
+
return graph;
|
|
92
|
+
}
|
|
93
|
+
/** Restore this instance atomically from a legacy or current JSON snapshot. */
|
|
94
|
+
restore(data) {
|
|
95
|
+
const restored = Graph.fromJSON(data);
|
|
96
|
+
this.nodes = restored.nodes;
|
|
97
|
+
this.adjacencyList = restored.adjacencyList;
|
|
98
|
+
this.reverseAdjacencyList = restored.reverseAdjacencyList;
|
|
99
|
+
this.aliases = restored.aliases;
|
|
100
|
+
}
|
|
101
|
+
resolveNodeId(reference) {
|
|
102
|
+
if (this.nodes.has(reference)) {
|
|
103
|
+
return reference;
|
|
104
|
+
}
|
|
105
|
+
return this.aliases.get((0, ResourceReference_1.normalizeResourceReference)(reference));
|
|
25
106
|
}
|
|
26
107
|
/**
|
|
27
108
|
* Retrieves a node by its ID.
|
|
@@ -29,16 +110,17 @@ class Graph {
|
|
|
29
110
|
* @param id The node ID | 节点 ID
|
|
30
111
|
* @returns The node or undefined if not found | 节点,如果未找到则返回 undefined
|
|
31
112
|
*/
|
|
32
|
-
getNode(
|
|
33
|
-
|
|
113
|
+
getNode(reference) {
|
|
114
|
+
const nodeId = this.resolveNodeId(reference);
|
|
115
|
+
return nodeId ? this.nodes.get(nodeId) : undefined;
|
|
34
116
|
}
|
|
35
117
|
/**
|
|
36
118
|
* Checks if a node exists in the graph.
|
|
37
119
|
* 检查图中是否存在该节点。
|
|
38
120
|
* @param id The node ID | 节点 ID
|
|
39
121
|
*/
|
|
40
|
-
hasNode(
|
|
41
|
-
return this.
|
|
122
|
+
hasNode(reference) {
|
|
123
|
+
return this.resolveNodeId(reference) !== undefined;
|
|
42
124
|
}
|
|
43
125
|
/**
|
|
44
126
|
* Adds a directed edge between two nodes.
|
|
@@ -49,30 +131,32 @@ class Graph {
|
|
|
49
131
|
* @param weight Edge weight (confidence) | 边权重 (置信度)
|
|
50
132
|
*/
|
|
51
133
|
addEdge(source, target, type = 'dependency', weight = 1) {
|
|
52
|
-
|
|
53
|
-
|
|
134
|
+
const sourceId = this.resolveNodeId(source) ?? source;
|
|
135
|
+
const targetId = this.resolveNodeId(target) ?? target;
|
|
136
|
+
if (!this.nodes.has(sourceId)) {
|
|
137
|
+
this.addNode({ id: sourceId, label: sourceId, inDegree: 0, outDegree: 0 });
|
|
54
138
|
}
|
|
55
|
-
if (!this.nodes.has(
|
|
56
|
-
this.addNode({ id:
|
|
139
|
+
if (!this.nodes.has(targetId)) {
|
|
140
|
+
this.addNode({ id: targetId, label: targetId, inDegree: 0, outDegree: 0 });
|
|
57
141
|
}
|
|
58
|
-
const edge = { source, target, type, weight };
|
|
142
|
+
const edge = { source: sourceId, target: targetId, type, weight };
|
|
59
143
|
// Add to adjacency list (outgoing)
|
|
60
|
-
const outgoing = this.adjacencyList.get(
|
|
144
|
+
const outgoing = this.adjacencyList.get(sourceId) || [];
|
|
61
145
|
// Prevent duplicate edges
|
|
62
|
-
if (!outgoing.some(e => e.target ===
|
|
146
|
+
if (!outgoing.some(e => e.target === targetId && e.type === type)) {
|
|
63
147
|
outgoing.push(edge);
|
|
64
|
-
this.adjacencyList.set(
|
|
148
|
+
this.adjacencyList.set(sourceId, outgoing);
|
|
65
149
|
// Update out-degree
|
|
66
|
-
const sourceNode = this.nodes.get(
|
|
150
|
+
const sourceNode = this.nodes.get(sourceId);
|
|
67
151
|
sourceNode.outDegree++;
|
|
68
152
|
}
|
|
69
153
|
// Add to reverse adjacency list (incoming)
|
|
70
|
-
const incoming = this.reverseAdjacencyList.get(
|
|
71
|
-
if (!incoming.some(e => e.source ===
|
|
154
|
+
const incoming = this.reverseAdjacencyList.get(targetId) || [];
|
|
155
|
+
if (!incoming.some(e => e.source === sourceId && e.type === type)) {
|
|
72
156
|
incoming.push(edge);
|
|
73
|
-
this.reverseAdjacencyList.set(
|
|
157
|
+
this.reverseAdjacencyList.set(targetId, incoming);
|
|
74
158
|
// Update in-degree
|
|
75
|
-
const targetNode = this.nodes.get(
|
|
159
|
+
const targetNode = this.nodes.get(targetId);
|
|
76
160
|
targetNode.inDegree++;
|
|
77
161
|
}
|
|
78
162
|
}
|
|
@@ -81,16 +165,17 @@ class Graph {
|
|
|
81
165
|
* 获取节点的所有出边。
|
|
82
166
|
* @param id Node ID | 节点 ID
|
|
83
167
|
*/
|
|
84
|
-
getOutgoingEdges(
|
|
85
|
-
|
|
168
|
+
getOutgoingEdges(reference) {
|
|
169
|
+
const nodeId = this.resolveNodeId(reference);
|
|
170
|
+
return nodeId ? (this.adjacencyList.get(nodeId) || []) : [];
|
|
86
171
|
}
|
|
87
172
|
/**
|
|
88
173
|
* Gets all outgoing neighbor IDs for a node.
|
|
89
174
|
* 获取节点的所有出度邻居 ID。
|
|
90
175
|
* @param id Node ID | 节点 ID
|
|
91
176
|
*/
|
|
92
|
-
getNeighbors(
|
|
93
|
-
return
|
|
177
|
+
getNeighbors(reference) {
|
|
178
|
+
return this.getOutgoingEdges(reference).map(edge => edge.target);
|
|
94
179
|
}
|
|
95
180
|
/**
|
|
96
181
|
* Gets all nodes in the graph.
|
|
@@ -111,8 +196,9 @@ class Graph {
|
|
|
111
196
|
* 获取节点的所有入边。
|
|
112
197
|
* @param id Node ID | 节点 ID
|
|
113
198
|
*/
|
|
114
|
-
getIncomingEdges(
|
|
115
|
-
|
|
199
|
+
getIncomingEdges(reference) {
|
|
200
|
+
const nodeId = this.resolveNodeId(reference);
|
|
201
|
+
return nodeId ? (this.reverseAdjacencyList.get(nodeId) || []) : [];
|
|
116
202
|
}
|
|
117
203
|
/**
|
|
118
204
|
* Returns the graph data in a serializable format.
|
|
@@ -129,11 +215,14 @@ class Graph {
|
|
|
129
215
|
* 获取所有前驱节点(入边的传递闭包)。
|
|
130
216
|
* @param id Target node ID
|
|
131
217
|
*/
|
|
132
|
-
getPredecessors(
|
|
218
|
+
getPredecessors(reference) {
|
|
133
219
|
const predecessors = new Set();
|
|
134
|
-
const
|
|
220
|
+
const nodeId = this.resolveNodeId(reference);
|
|
221
|
+
if (!nodeId)
|
|
222
|
+
return predecessors;
|
|
223
|
+
const queue = [nodeId];
|
|
135
224
|
const visited = new Set();
|
|
136
|
-
visited.add(
|
|
225
|
+
visited.add(nodeId);
|
|
137
226
|
while (queue.length > 0) {
|
|
138
227
|
const current = queue.shift();
|
|
139
228
|
const incoming = this.getIncomingEdges(current);
|
|
@@ -152,11 +241,14 @@ class Graph {
|
|
|
152
241
|
* 获取所有后继节点(出边的传递闭包)。
|
|
153
242
|
* @param id Source node ID
|
|
154
243
|
*/
|
|
155
|
-
getSuccessors(
|
|
244
|
+
getSuccessors(reference) {
|
|
156
245
|
const successors = new Set();
|
|
157
|
-
const
|
|
246
|
+
const nodeId = this.resolveNodeId(reference);
|
|
247
|
+
if (!nodeId)
|
|
248
|
+
return successors;
|
|
249
|
+
const queue = [nodeId];
|
|
158
250
|
const visited = new Set();
|
|
159
|
-
visited.add(
|
|
251
|
+
visited.add(nodeId);
|
|
160
252
|
while (queue.length > 0) {
|
|
161
253
|
const current = queue.shift();
|
|
162
254
|
const outgoing = this.getOutgoingEdges(current);
|
|
@@ -178,15 +270,19 @@ class Graph {
|
|
|
178
270
|
* @returns Array of node IDs representing the path, or empty if no path found
|
|
179
271
|
*/
|
|
180
272
|
getShortestPath(source, target) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
273
|
+
const sourceId = this.resolveNodeId(source);
|
|
274
|
+
const targetId = this.resolveNodeId(target);
|
|
275
|
+
if (!sourceId || !targetId)
|
|
276
|
+
return [];
|
|
277
|
+
if (sourceId === targetId)
|
|
278
|
+
return [sourceId];
|
|
279
|
+
const queue = [sourceId];
|
|
184
280
|
const visited = new Set();
|
|
185
281
|
const parent = new Map();
|
|
186
|
-
visited.add(
|
|
282
|
+
visited.add(sourceId);
|
|
187
283
|
while (queue.length > 0) {
|
|
188
284
|
const current = queue.shift();
|
|
189
|
-
if (current ===
|
|
285
|
+
if (current === targetId)
|
|
190
286
|
break;
|
|
191
287
|
const neighbors = this.getNeighbors(current);
|
|
192
288
|
for (const neighbor of neighbors) {
|
|
@@ -197,12 +293,12 @@ class Graph {
|
|
|
197
293
|
}
|
|
198
294
|
}
|
|
199
295
|
}
|
|
200
|
-
if (!visited.has(
|
|
296
|
+
if (!visited.has(targetId))
|
|
201
297
|
return [];
|
|
202
298
|
// Reconstruct path
|
|
203
|
-
const path = [
|
|
204
|
-
let curr =
|
|
205
|
-
while (curr !==
|
|
299
|
+
const path = [targetId];
|
|
300
|
+
let curr = targetId;
|
|
301
|
+
while (curr !== sourceId) {
|
|
206
302
|
curr = parent.get(curr);
|
|
207
303
|
path.unshift(curr);
|
|
208
304
|
}
|
|
@@ -34,4 +34,78 @@ describe('Graph Core', () => {
|
|
|
34
34
|
expect(nodeA?.outDegree).toBe(1);
|
|
35
35
|
expect(graph.getOutgoingEdges('A')).toHaveLength(1);
|
|
36
36
|
});
|
|
37
|
+
test('resolves source URI and relative path aliases without changing the public node ID', () => {
|
|
38
|
+
graph.addNode({
|
|
39
|
+
id: 'Index',
|
|
40
|
+
label: 'Index',
|
|
41
|
+
inDegree: 0,
|
|
42
|
+
outDegree: 0,
|
|
43
|
+
sourceUri: 'note://workspace/v1/algebra/index.md',
|
|
44
|
+
revision: 'sha256:revision',
|
|
45
|
+
identityAliases: ['algebra/index.md', 'Index'],
|
|
46
|
+
});
|
|
47
|
+
graph.addNode({ id: 'Limits', label: 'Limits', inDegree: 0, outDegree: 0 });
|
|
48
|
+
expect(graph.getNode('note://workspace/v1/algebra/index.md')?.id).toBe('Index');
|
|
49
|
+
expect(graph.getNode('algebra\\index.md')?.id).toBe('Index');
|
|
50
|
+
expect(graph.hasNode('INDEX')).toBe(true);
|
|
51
|
+
graph.addEdge('algebra/index.md', 'Limits', 'explicit-next');
|
|
52
|
+
expect(graph.getOutgoingEdges('Index')[0]).toEqual(expect.objectContaining({
|
|
53
|
+
source: 'Index',
|
|
54
|
+
target: 'Limits',
|
|
55
|
+
type: 'explicit-next',
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
test('rejects an alias claimed by two different nodes', () => {
|
|
59
|
+
graph.addNode({
|
|
60
|
+
id: 'A',
|
|
61
|
+
label: 'A',
|
|
62
|
+
inDegree: 0,
|
|
63
|
+
outDegree: 0,
|
|
64
|
+
identityAliases: ['shared/path.md'],
|
|
65
|
+
});
|
|
66
|
+
expect(() => graph.addNode({
|
|
67
|
+
id: 'B',
|
|
68
|
+
label: 'B',
|
|
69
|
+
inDegree: 0,
|
|
70
|
+
outDegree: 0,
|
|
71
|
+
identityAliases: ['shared/path.md'],
|
|
72
|
+
})).toThrow(/alias/i);
|
|
73
|
+
});
|
|
74
|
+
test('replays legacy snapshots without identity metadata and preserves alias-aware edges', () => {
|
|
75
|
+
const restored = Graph_1.Graph.fromJSON({
|
|
76
|
+
nodes: [
|
|
77
|
+
{ id: 'A', label: 'A', inDegree: 0, outDegree: 1 },
|
|
78
|
+
{ id: 'B', label: 'B', inDegree: 1, outDegree: 0 },
|
|
79
|
+
],
|
|
80
|
+
edges: [{ source: 'A', target: 'B', type: 'legacy-link', weight: 0.5 }],
|
|
81
|
+
});
|
|
82
|
+
expect(restored.getNode('A')).toEqual(expect.objectContaining({ id: 'A', inDegree: 0, outDegree: 1 }));
|
|
83
|
+
expect(restored.getOutgoingEdges('A')).toEqual([
|
|
84
|
+
{ source: 'A', target: 'B', type: 'legacy-link', weight: 0.5 },
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
test('rejects an edge that references a missing node before restoring', () => {
|
|
88
|
+
expect(() => Graph_1.Graph.fromJSON({
|
|
89
|
+
nodes: [{ id: 'A', label: 'A' }],
|
|
90
|
+
edges: [{ source: 'A', target: 'missing' }],
|
|
91
|
+
})).toThrow(/undeclared node/i);
|
|
92
|
+
});
|
|
93
|
+
test('restores an existing instance atomically', () => {
|
|
94
|
+
graph.addNode({ id: 'old', label: 'old', inDegree: 0, outDegree: 0 });
|
|
95
|
+
expect(() => graph.restore({
|
|
96
|
+
nodes: [{ id: 'new', label: 'new' }],
|
|
97
|
+
edges: [],
|
|
98
|
+
})).not.toThrow();
|
|
99
|
+
expect(graph.hasNode('old')).toBe(false);
|
|
100
|
+
expect(graph.hasNode('new')).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
test('keeps the previous graph when an incoming snapshot fails validation', () => {
|
|
103
|
+
graph.addNode({ id: 'stable', label: 'stable', inDegree: 0, outDegree: 0 });
|
|
104
|
+
expect(() => graph.restore({
|
|
105
|
+
nodes: [{ id: 'candidate', label: 'candidate' }],
|
|
106
|
+
edges: [{ source: 'candidate', target: 'missing' }],
|
|
107
|
+
})).toThrow(/undeclared node/i);
|
|
108
|
+
expect(graph.hasNode('stable')).toBe(true);
|
|
109
|
+
expect(graph.hasNode('candidate')).toBe(false);
|
|
110
|
+
});
|
|
37
111
|
});
|
|
@@ -79,7 +79,7 @@ class NoteConnection {
|
|
|
79
79
|
}
|
|
80
80
|
// 2. Resolve Directory
|
|
81
81
|
const projectRoot = options.projectRoot || path.resolve(__dirname, '..', '..');
|
|
82
|
-
const kbRoot = path.join(projectRoot, 'Knowledge_Base');
|
|
82
|
+
const kbRoot = path.resolve(options.knowledgeBaseRoot || path.join(projectRoot, 'Knowledge_Base'));
|
|
83
83
|
let conceptDir;
|
|
84
84
|
if (options.targetPath) {
|
|
85
85
|
if (path.isAbsolute(options.targetPath)) {
|
|
@@ -97,7 +97,7 @@ class NoteConnection {
|
|
|
97
97
|
}
|
|
98
98
|
// 3. Load Files
|
|
99
99
|
log(`Loading files from: ${conceptDir}`);
|
|
100
|
-
const files = await FileLoader_1.FileLoader.loadFiles(conceptDir);
|
|
100
|
+
const files = await FileLoader_1.FileLoader.loadFiles(conceptDir, ['.md'], kbRoot);
|
|
101
101
|
log(`Loaded ${files.length} files.`);
|
|
102
102
|
// 4. Load Layout (Optional)
|
|
103
103
|
// Note: Layout logic is slightly coupled to file system here,
|