noteconnection 1.8.0 → 1.9.0
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/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
|
@@ -52,11 +52,34 @@ const HybridEngine_1 = require("./algorithms/HybridEngine");
|
|
|
52
52
|
const PerformanceLogger_1 = require("./utils/PerformanceLogger");
|
|
53
53
|
const LayoutEngine_1 = require("./algorithms/LayoutEngine");
|
|
54
54
|
const WorkerRuntime_1 = require("./utils/WorkerRuntime");
|
|
55
|
+
const ResourceIdentity_1 = require("./ResourceIdentity");
|
|
55
56
|
/**
|
|
56
57
|
* Service to build the graph from raw files.
|
|
57
58
|
* 从原始文件构建图的服务。
|
|
58
59
|
*/
|
|
59
60
|
class GraphBuilder {
|
|
61
|
+
static findLayoutPosition(file, layout) {
|
|
62
|
+
if (!layout)
|
|
63
|
+
return undefined;
|
|
64
|
+
const references = [
|
|
65
|
+
file.sourceUri,
|
|
66
|
+
file.relativePath,
|
|
67
|
+
...(file.identityAliases ?? []),
|
|
68
|
+
file.filename,
|
|
69
|
+
].filter((reference) => Boolean(reference));
|
|
70
|
+
for (const reference of references) {
|
|
71
|
+
const exactPosition = layout.get(reference);
|
|
72
|
+
if (exactPosition)
|
|
73
|
+
return exactPosition;
|
|
74
|
+
}
|
|
75
|
+
const normalizedReferences = new Set(references.map(ResourceIdentity_1.normalizeResourceReference));
|
|
76
|
+
for (const [reference, position] of layout.entries()) {
|
|
77
|
+
if (normalizedReferences.has((0, ResourceIdentity_1.normalizeResourceReference)(reference))) {
|
|
78
|
+
return position;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
60
83
|
/**
|
|
61
84
|
* Builds a graph from raw files using keyword matching.
|
|
62
85
|
* 使用关键词匹配从原始文件构建图。
|
|
@@ -65,13 +88,13 @@ class GraphBuilder {
|
|
|
65
88
|
*/
|
|
66
89
|
static async build(files, layout) {
|
|
67
90
|
PerformanceLogger_1.PerformanceLogger.logSystemInfo();
|
|
91
|
+
(0, ResourceIdentity_1.assertUniqueLegacyResourceIds)(files);
|
|
68
92
|
const graph = new Graph_1.Graph();
|
|
69
93
|
// 1. Add all nodes first
|
|
70
94
|
PerformanceLogger_1.PerformanceLogger.start('Node Initialization');
|
|
71
95
|
// 1. 首先添加所有节点
|
|
72
96
|
const fileMap = new Map();
|
|
73
97
|
files.forEach(file => {
|
|
74
|
-
// Parse Metadata (Tags, Prerequisites, Next)
|
|
75
98
|
const metadata = frontmatterParser_1.FrontmatterParser.parse(file.content);
|
|
76
99
|
const node = {
|
|
77
100
|
id: file.filename,
|
|
@@ -79,21 +102,28 @@ class GraphBuilder {
|
|
|
79
102
|
inDegree: 0,
|
|
80
103
|
outDegree: 0,
|
|
81
104
|
content: file.content,
|
|
105
|
+
sourceUri: file.sourceUri,
|
|
106
|
+
canonicalId: file.canonicalId,
|
|
107
|
+
revision: file.revision,
|
|
108
|
+
identityAliases: file.identityAliases,
|
|
82
109
|
metadata: {
|
|
83
110
|
filepath: file.filepath,
|
|
111
|
+
relativePath: file.relativePath,
|
|
112
|
+
sourceUri: file.sourceUri,
|
|
113
|
+
revision: file.revision,
|
|
84
114
|
tags: metadata.tags,
|
|
85
115
|
prerequisites: metadata.prerequisites,
|
|
86
116
|
next: metadata.next
|
|
87
117
|
}
|
|
88
118
|
};
|
|
89
|
-
|
|
90
|
-
|
|
119
|
+
const savedPosition = this.findLayoutPosition(file, layout);
|
|
120
|
+
if (savedPosition) {
|
|
121
|
+
const pos = savedPosition;
|
|
91
122
|
node.x = pos.x;
|
|
92
123
|
node.y = pos.y;
|
|
93
124
|
}
|
|
94
125
|
graph.addNode(node);
|
|
95
126
|
fileMap.set(file.filename, file);
|
|
96
|
-
// 1b. Add Tag Nodes
|
|
97
127
|
if (config_1.config.enableTags) {
|
|
98
128
|
metadata.tags.forEach(tag => {
|
|
99
129
|
const tagId = `#${tag}`;
|
|
@@ -105,7 +135,6 @@ class GraphBuilder {
|
|
|
105
135
|
clusterId: 'tags' // Group tags together
|
|
106
136
|
});
|
|
107
137
|
}
|
|
108
|
-
// Edge: Note -> Tag
|
|
109
138
|
graph.addEdge(node.id, tagId, 'tagged');
|
|
110
139
|
});
|
|
111
140
|
}
|
|
@@ -209,7 +238,6 @@ class GraphBuilder {
|
|
|
209
238
|
const similar = sharedVectorSpace.getSimilar(file.filename, 3); // Top 3 similar
|
|
210
239
|
similar.forEach(sim => {
|
|
211
240
|
if (sim.score > 0.3) { // Threshold
|
|
212
|
-
// Add UNDIRECTED association
|
|
213
241
|
graph.addEdge(file.filename, sim.id, 'vector-association', sim.score);
|
|
214
242
|
similarityEdges++;
|
|
215
243
|
}
|
|
@@ -369,6 +397,7 @@ class GraphBuilder {
|
|
|
369
397
|
const chunkSize = Math.ceil(files.length / workerCount);
|
|
370
398
|
const targetIds = files.map(f => f.filename);
|
|
371
399
|
const workerPromises = [];
|
|
400
|
+
const matchingWorkers = [];
|
|
372
401
|
const workerRuntime = (0, WorkerRuntime_1.resolveWorkerRuntimePath)(__dirname, 'workers/keywordMatchWorker.ts');
|
|
373
402
|
const actualWorkerPath = workerRuntime.workerPath;
|
|
374
403
|
const isTsNode = workerRuntime.isTsNode;
|
|
@@ -409,6 +438,7 @@ class GraphBuilder {
|
|
|
409
438
|
workerData: workerPayload,
|
|
410
439
|
execArgv
|
|
411
440
|
});
|
|
441
|
+
matchingWorkers.push(worker);
|
|
412
442
|
worker.on('message', (results) => {
|
|
413
443
|
results.forEach(res => {
|
|
414
444
|
graph.addEdge(res.target, res.source, 'keyword-match');
|
|
@@ -441,24 +471,42 @@ class GraphBuilder {
|
|
|
441
471
|
}
|
|
442
472
|
catch (err) {
|
|
443
473
|
console.error('[GraphBuilder] Parallel matching failed, falling back to sequential.', err);
|
|
444
|
-
//
|
|
474
|
+
// No worker may append edges while downstream graph analysis sees the fallback.
|
|
475
|
+
await Promise.allSettled(matchingWorkers.map(worker => worker.terminate()));
|
|
476
|
+
await Promise.allSettled(workerPromises);
|
|
445
477
|
this.runSequentialMatching(files, graph);
|
|
446
478
|
}
|
|
447
479
|
}
|
|
448
480
|
static runSequentialMatching(files, graph) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
481
|
+
// Exact-phrase matching uses JavaScript's ASCII word boundaries. Index
|
|
482
|
+
// those same word runs so mixed CJK/ASCII text cannot hide a valid match.
|
|
483
|
+
// Substring matching requires a full candidate set; token equality is not
|
|
484
|
+
// a conservative prefilter for fuzzy matches inside a longer word.
|
|
485
|
+
const candidatesByAnchor = new Map();
|
|
486
|
+
files.forEach((sourceFile) => {
|
|
487
|
+
const tokens = sourceFile.content.toLowerCase().match(/[A-Za-z0-9_]+/g) || [];
|
|
488
|
+
const seenTokens = new Set(tokens);
|
|
489
|
+
seenTokens.forEach((token) => {
|
|
490
|
+
const candidates = candidatesByAnchor.get(token) || [];
|
|
491
|
+
candidates.push(sourceFile);
|
|
492
|
+
candidatesByAnchor.set(token, candidates);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
files.forEach((targetFile) => {
|
|
496
|
+
const targetId = targetFile.filename;
|
|
497
|
+
if (config_1.config.exclusionList.includes(targetId)) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const anchor = targetId.toLowerCase().match(/[A-Za-z0-9_]+/)?.[0] || '';
|
|
501
|
+
const candidateSources = config_1.config.matchingStrategy === 'exact-phrase' && anchor
|
|
502
|
+
? (candidatesByAnchor.get(anchor) || [])
|
|
503
|
+
: files;
|
|
504
|
+
candidateSources.forEach((sourceFile) => {
|
|
505
|
+
const sourceId = sourceFile.filename;
|
|
506
|
+
if (sourceId === targetId) {
|
|
458
507
|
return;
|
|
459
508
|
}
|
|
460
|
-
if ((0, stringUtils_1.checkMatch)(content, targetId, config_1.config.matchingStrategy)) {
|
|
461
|
-
// Found a reference!
|
|
509
|
+
if ((0, stringUtils_1.checkMatch)(sourceFile.content, targetId, config_1.config.matchingStrategy)) {
|
|
462
510
|
// Target (Concept) -> Source (Context)
|
|
463
511
|
graph.addEdge(targetId, sourceId, 'keyword-match');
|
|
464
512
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const GraphBuilder_1 = require("./GraphBuilder");
|
|
43
|
+
const config_1 = require("./config");
|
|
44
|
+
const stringUtils_1 = require("./utils/stringUtils");
|
|
45
|
+
const worker_threads_1 = __importDefault(require("worker_threads"));
|
|
46
|
+
const events_1 = require("events");
|
|
47
|
+
describe('graph keyword matching equivalence', () => {
|
|
48
|
+
const original = { ...config_1.config, exclusionList: [...config_1.config.exclusionList] };
|
|
49
|
+
let root;
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), 'graph-matching-'));
|
|
52
|
+
Object.assign(config_1.config, {
|
|
53
|
+
enableTags: false, enableStatisticalInference: false, enableVectorSimilarity: false,
|
|
54
|
+
enableHybridInference: false, enableGPU: false, enableGPULayout: false,
|
|
55
|
+
clusteringStrategy: 'folder', exclusionList: ['ignored'], maxWorkers: 1,
|
|
56
|
+
});
|
|
57
|
+
jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
58
|
+
});
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
Object.assign(config_1.config, original);
|
|
61
|
+
jest.restoreAllMocks();
|
|
62
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
63
|
+
});
|
|
64
|
+
function corpus() {
|
|
65
|
+
const records = [
|
|
66
|
+
['cat', ''], ['知识', ''], ['Alpha', ''], ['++', ''], ['café', ''],
|
|
67
|
+
['ignored', ''], ['self', 'self'],
|
|
68
|
+
['english-context', 'concatenate'], ['chinese-context', '知识图谱'],
|
|
69
|
+
['mixed-context', 'Alpha知识'], ['punctuation-context', 'a++b'],
|
|
70
|
+
['unicode-context', 'précaféiné'], ['excluded-context', 'ignored'],
|
|
71
|
+
];
|
|
72
|
+
return records.map(([filename, content]) => {
|
|
73
|
+
const filepath = path.join(root, filename + '.md');
|
|
74
|
+
fs.writeFileSync(filepath, content);
|
|
75
|
+
return { filename, filepath, content };
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function expectedEdges(files) {
|
|
79
|
+
return files.flatMap(source => files
|
|
80
|
+
.filter(target => source.filename !== target.filename
|
|
81
|
+
&& !config_1.config.exclusionList.includes(target.filename)
|
|
82
|
+
&& (0, stringUtils_1.checkMatch)(source.content, target.filename, config_1.config.matchingStrategy))
|
|
83
|
+
.map(target => target.filename + '->' + source.filename)).sort();
|
|
84
|
+
}
|
|
85
|
+
test.each(['exact-phrase', 'fuzzy'])('sequential matching preserves %s semantics', async (strategy) => {
|
|
86
|
+
config_1.config.matchingStrategy = strategy;
|
|
87
|
+
const files = corpus();
|
|
88
|
+
const graph = await GraphBuilder_1.GraphBuilder.build(files);
|
|
89
|
+
expect(graph.getEdges().filter(edge => edge.type === 'keyword-match')
|
|
90
|
+
.map(edge => edge.source + '->' + edge.target).sort()).toEqual(expectedEdges(files));
|
|
91
|
+
});
|
|
92
|
+
test.each([['exact-phrase', true], ['exact-phrase', false], ['fuzzy', true], ['fuzzy', false]])('worker matching preserves %s semantics with memorySavingMode=%s', async (strategy, memorySavingMode) => {
|
|
93
|
+
config_1.config.matchingStrategy = strategy;
|
|
94
|
+
config_1.config.memorySavingMode = memorySavingMode;
|
|
95
|
+
const files = corpus();
|
|
96
|
+
for (let index = 0; files.length <= 200; index++) {
|
|
97
|
+
const filename = 'unused-' + index;
|
|
98
|
+
const filepath = path.join(root, filename + '.md');
|
|
99
|
+
fs.writeFileSync(filepath, '');
|
|
100
|
+
files.push({ filename, filepath, content: '' });
|
|
101
|
+
}
|
|
102
|
+
const graph = await GraphBuilder_1.GraphBuilder.build(files);
|
|
103
|
+
expect(graph.getEdges().filter(edge => edge.type === 'keyword-match')
|
|
104
|
+
.map(edge => edge.source + '->' + edge.target).sort()).toEqual(expectedEdges(files));
|
|
105
|
+
}, 30000);
|
|
106
|
+
test('quiesces all workers before publishing sequential fallback after a worker failure', async () => {
|
|
107
|
+
config_1.config.matchingStrategy = 'fuzzy';
|
|
108
|
+
config_1.config.maxWorkers = 2;
|
|
109
|
+
const files = corpus();
|
|
110
|
+
for (let index = 0; files.length <= 200; index++)
|
|
111
|
+
files.push({ filename: 'padding-' + index, filepath: path.join(root, 'padding-' + index + '.md'), content: '' });
|
|
112
|
+
const children = [];
|
|
113
|
+
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
114
|
+
jest.spyOn(worker_threads_1.default, 'Worker').mockImplementation(() => {
|
|
115
|
+
const child = Object.assign(new events_1.EventEmitter(), { terminate: jest.fn(async () => { child.emit('exit', 0); return 0; }) });
|
|
116
|
+
children.push(child);
|
|
117
|
+
if (children.length === 1)
|
|
118
|
+
setImmediate(() => child.emit('error', new Error('injected worker failure')));
|
|
119
|
+
return child;
|
|
120
|
+
});
|
|
121
|
+
const graph = await GraphBuilder_1.GraphBuilder.build(files);
|
|
122
|
+
expect(graph.getEdges().filter(edge => edge.type === 'keyword-match').map(edge => edge.source + '->' + edge.target).sort()).toEqual(expectedEdges(files));
|
|
123
|
+
expect(children).toHaveLength(2);
|
|
124
|
+
for (const child of children)
|
|
125
|
+
expect(child.terminate).toHaveBeenCalledTimes(1);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -235,8 +235,6 @@ class GraphMetrics {
|
|
|
235
235
|
}
|
|
236
236
|
try {
|
|
237
237
|
const results = await Promise.all(workerPromises);
|
|
238
|
-
// Merge results
|
|
239
|
-
// 合并结果
|
|
240
238
|
const totalCB = new Map();
|
|
241
239
|
nodes.forEach(n => totalCB.set(n.id, 0));
|
|
242
240
|
results.forEach(partial => {
|
|
@@ -261,7 +259,6 @@ class GraphMetrics {
|
|
|
261
259
|
static calculateBetweenness(graph) {
|
|
262
260
|
const nodes = graph.toJSON().nodes;
|
|
263
261
|
const cb = new Map();
|
|
264
|
-
// Initialize
|
|
265
262
|
nodes.forEach(n => cb.set(n.id, 0));
|
|
266
263
|
// For each node s, calculate dependencies
|
|
267
264
|
nodes.forEach(sNode => {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.normalizeResourceReference = void 0;
|
|
37
|
+
exports.normalizePortableResourcePath = normalizePortableResourcePath;
|
|
38
|
+
exports.createResourceIdentity = createResourceIdentity;
|
|
39
|
+
exports.normalizeResourceRelativePath = normalizeResourceRelativePath;
|
|
40
|
+
exports.assertUniqueLegacyResourceIds = assertUniqueLegacyResourceIds;
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const crypto_1 = require("crypto");
|
|
43
|
+
const ResourceReference_1 = require("../core/ResourceReference");
|
|
44
|
+
var ResourceReference_2 = require("../core/ResourceReference");
|
|
45
|
+
Object.defineProperty(exports, "normalizeResourceReference", { enumerable: true, get: function () { return ResourceReference_2.normalizeResourceReference; } });
|
|
46
|
+
const SOURCE_URI_PREFIX = 'note://workspace/v1/';
|
|
47
|
+
function normalizeUnicodeText(value, fieldName) {
|
|
48
|
+
if (typeof value !== 'string') {
|
|
49
|
+
throw new Error(`${fieldName} must be a string`);
|
|
50
|
+
}
|
|
51
|
+
if (value.includes('\0')) {
|
|
52
|
+
throw new Error(`${fieldName} must not contain NUL characters`);
|
|
53
|
+
}
|
|
54
|
+
return value.normalize('NFC');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Produces the case-folded path key used by portable aliases and source URIs.
|
|
58
|
+
* The lower-case policy is deliberate: a workspace must not change identity when
|
|
59
|
+
* moved between Windows and a case-sensitive POSIX host.
|
|
60
|
+
*/
|
|
61
|
+
function normalizePortableResourcePath(relativePath) {
|
|
62
|
+
const normalized = normalizeUnicodeText(relativePath, 'Relative resource path')
|
|
63
|
+
.replace(/\\/g, '/');
|
|
64
|
+
if (!normalized
|
|
65
|
+
|| normalized.startsWith('/')
|
|
66
|
+
|| /^[A-Za-z]:\//.test(normalized)) {
|
|
67
|
+
throw new Error(`Relative resource path must stay inside the workspace: ${relativePath}`);
|
|
68
|
+
}
|
|
69
|
+
const segments = normalized.split('/');
|
|
70
|
+
if (segments.some(segment => !segment || segment === '..')) {
|
|
71
|
+
throw new Error(`Relative resource path contains an unsafe segment: ${relativePath}`);
|
|
72
|
+
}
|
|
73
|
+
return segments
|
|
74
|
+
.filter(segment => segment !== '.')
|
|
75
|
+
.map(segment => segment.toLowerCase())
|
|
76
|
+
.join('/');
|
|
77
|
+
}
|
|
78
|
+
function createSourceUri(canonicalRelativePath) {
|
|
79
|
+
const encodedSegments = canonicalRelativePath
|
|
80
|
+
.split('/')
|
|
81
|
+
.map(segment => encodeURIComponent(segment));
|
|
82
|
+
return `${SOURCE_URI_PREFIX}${encodedSegments.join('/')}`;
|
|
83
|
+
}
|
|
84
|
+
function createCanonicalId(canonicalRelativePath) {
|
|
85
|
+
return canonicalRelativePath.replace(/\.(?:md|markdown)$/i, '');
|
|
86
|
+
}
|
|
87
|
+
function createContentRevision(content) {
|
|
88
|
+
const normalizedContent = normalizeUnicodeText(content, 'Resource content');
|
|
89
|
+
return `sha256:${(0, crypto_1.createHash)('sha256').update(normalizedContent, 'utf8').digest('hex')}`;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Builds additive identity metadata for a loaded Markdown resource.
|
|
93
|
+
* `relativePath` keeps its display spelling in aliases while the source URI is
|
|
94
|
+
* canonicalized for cross-platform persistence.
|
|
95
|
+
*/
|
|
96
|
+
function createResourceIdentity(relativePath, legacyId, content) {
|
|
97
|
+
const displayRelativePath = normalizeUnicodeText(relativePath, 'Relative resource path')
|
|
98
|
+
.replace(/\\/g, '/');
|
|
99
|
+
const canonicalRelativePath = normalizePortableResourcePath(displayRelativePath);
|
|
100
|
+
const normalizedLegacyId = normalizeUnicodeText(legacyId, 'Legacy resource ID');
|
|
101
|
+
if (!normalizedLegacyId.trim()) {
|
|
102
|
+
throw new Error('Legacy resource ID must not be empty');
|
|
103
|
+
}
|
|
104
|
+
const identityAliases = Array.from(new Set([
|
|
105
|
+
normalizedLegacyId,
|
|
106
|
+
normalizedLegacyId.toLowerCase().endsWith('.md') ? normalizedLegacyId : `${normalizedLegacyId}.md`,
|
|
107
|
+
displayRelativePath,
|
|
108
|
+
canonicalRelativePath,
|
|
109
|
+
]));
|
|
110
|
+
return {
|
|
111
|
+
sourceUri: createSourceUri(canonicalRelativePath),
|
|
112
|
+
canonicalId: createCanonicalId(canonicalRelativePath),
|
|
113
|
+
revision: createContentRevision(content),
|
|
114
|
+
identityAliases,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Returns the stable, workspace-relative spelling used by persistence and diagnostics.
|
|
119
|
+
* The legacy basename remains a compatibility alias; this value is the migration input
|
|
120
|
+
* for a future stable source URI.
|
|
121
|
+
*/
|
|
122
|
+
function normalizeResourceRelativePath(rootPath, filePath) {
|
|
123
|
+
normalizeUnicodeText(rootPath, 'Workspace root');
|
|
124
|
+
normalizeUnicodeText(filePath, 'Resource path');
|
|
125
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
126
|
+
const resolvedFile = path.resolve(filePath);
|
|
127
|
+
const relativePath = path.relative(resolvedRoot, resolvedFile);
|
|
128
|
+
const parentPrefix = `..${path.sep}`;
|
|
129
|
+
if (!relativePath
|
|
130
|
+
|| relativePath === '..'
|
|
131
|
+
|| relativePath.startsWith(parentPrefix)
|
|
132
|
+
|| path.isAbsolute(relativePath)) {
|
|
133
|
+
throw new Error(`Resource path is outside workspace root: ${filePath}`);
|
|
134
|
+
}
|
|
135
|
+
return relativePath.split(path.sep).join('/').normalize('NFC');
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Prevents the current basename-based graph identity from silently dropping files.
|
|
139
|
+
* This guard is intentionally fail-fast until stable source IDs and aliases are migrated.
|
|
140
|
+
*/
|
|
141
|
+
function assertUniqueLegacyResourceIds(files) {
|
|
142
|
+
const pathsById = new Map();
|
|
143
|
+
files.forEach((file) => {
|
|
144
|
+
const identityKey = (0, ResourceReference_1.normalizeResourceReference)(file.filename);
|
|
145
|
+
const paths = pathsById.get(identityKey) ?? [];
|
|
146
|
+
paths.push(file.filepath);
|
|
147
|
+
pathsById.set(identityKey, paths);
|
|
148
|
+
});
|
|
149
|
+
const collisions = Array.from(pathsById.entries())
|
|
150
|
+
.filter(([, paths]) => paths.length > 1)
|
|
151
|
+
.map(([id, paths]) => `${id}: ${paths.join(', ')}`);
|
|
152
|
+
if (collisions.length > 0) {
|
|
153
|
+
throw new Error(`Ambiguous legacy resource identity detected; duplicate basenames must be resolved before graph construction: ${collisions.join('; ')}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const path = __importStar(require("path"));
|
|
37
|
+
const ResourceIdentity_1 = require("./ResourceIdentity");
|
|
38
|
+
describe('Resource identity boundary', () => {
|
|
39
|
+
test('normalizes workspace-relative paths to POSIX separators', () => {
|
|
40
|
+
const root = path.resolve('workspace', 'Knowledge_Base');
|
|
41
|
+
const filePath = path.join(root, 'algebra', 'index.md');
|
|
42
|
+
expect((0, ResourceIdentity_1.normalizeResourceRelativePath)(root, filePath)).toBe('algebra/index.md');
|
|
43
|
+
});
|
|
44
|
+
test('rejects a path outside the workspace root', () => {
|
|
45
|
+
const root = path.resolve('workspace', 'Knowledge_Base');
|
|
46
|
+
expect(() => (0, ResourceIdentity_1.normalizeResourceRelativePath)(root, path.resolve(root, '..', 'other.md')))
|
|
47
|
+
.toThrow(/outside workspace root/i);
|
|
48
|
+
});
|
|
49
|
+
test('fails before graph construction when legacy basenames collide', () => {
|
|
50
|
+
expect(() => (0, ResourceIdentity_1.assertUniqueLegacyResourceIds)([
|
|
51
|
+
{ filename: 'index', filepath: 'algebra/index.md' },
|
|
52
|
+
{ filename: 'index', filepath: 'physics/index.md' },
|
|
53
|
+
])).toThrow(/index.*algebra\/index\.md.*physics\/index\.md/i);
|
|
54
|
+
});
|
|
55
|
+
test('accepts unique legacy IDs', () => {
|
|
56
|
+
expect(() => (0, ResourceIdentity_1.assertUniqueLegacyResourceIds)([
|
|
57
|
+
{ filename: 'algebra-index', filepath: 'algebra/index.md' },
|
|
58
|
+
{ filename: 'physics-index', filepath: 'physics/index.md' },
|
|
59
|
+
])).not.toThrow();
|
|
60
|
+
});
|
|
61
|
+
test('builds a versioned portable source URI and stable content revision', () => {
|
|
62
|
+
const identity = (0, ResourceIdentity_1.createResourceIdentity)('Algebra Notes\\Index #1.MD', 'Index #1', '# Algebra');
|
|
63
|
+
expect(identity.sourceUri).toBe('note://workspace/v1/algebra%20notes/index%20%231.md');
|
|
64
|
+
expect(identity.canonicalId).toBe('algebra notes/index #1');
|
|
65
|
+
expect(identity.revision).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
66
|
+
expect(identity.identityAliases).toEqual(expect.arrayContaining([
|
|
67
|
+
'Index #1',
|
|
68
|
+
'Algebra Notes/Index #1.MD',
|
|
69
|
+
'algebra notes/index #1.md',
|
|
70
|
+
]));
|
|
71
|
+
});
|
|
72
|
+
test('keeps the revision stable for equal content and changes it for different content', () => {
|
|
73
|
+
const first = (0, ResourceIdentity_1.createResourceIdentity)('notes/a.md', 'a', 'same');
|
|
74
|
+
const same = (0, ResourceIdentity_1.createResourceIdentity)('notes/a.md', 'a', 'same');
|
|
75
|
+
const changed = (0, ResourceIdentity_1.createResourceIdentity)('notes/a.md', 'a', 'changed');
|
|
76
|
+
expect(same.revision).toBe(first.revision);
|
|
77
|
+
expect(changed.revision).not.toBe(first.revision);
|
|
78
|
+
});
|
|
79
|
+
test('rejects unsafe relative source paths', () => {
|
|
80
|
+
expect(() => (0, ResourceIdentity_1.createResourceIdentity)('../outside.md', 'outside', 'content'))
|
|
81
|
+
.toThrow(/relative resource path/i);
|
|
82
|
+
expect(() => (0, ResourceIdentity_1.createResourceIdentity)('notes/\0.md', 'nul', 'content'))
|
|
83
|
+
.toThrow(/NUL/i);
|
|
84
|
+
});
|
|
85
|
+
test('rejects case-folded legacy collisions for cross-platform determinism', () => {
|
|
86
|
+
expect(() => (0, ResourceIdentity_1.assertUniqueLegacyResourceIds)([
|
|
87
|
+
{ filename: 'Index', filepath: 'Algebra/Index.md' },
|
|
88
|
+
{ filename: 'index', filepath: 'Physics/index.md' },
|
|
89
|
+
])).toThrow(/case-insensitive|ambiguous/i);
|
|
90
|
+
});
|
|
91
|
+
test('keeps same-content documents distinct by source URI while sharing the revision', () => {
|
|
92
|
+
const first = (0, ResourceIdentity_1.createResourceIdentity)('notes/first.md', 'First', 'same body');
|
|
93
|
+
const second = (0, ResourceIdentity_1.createResourceIdentity)('notes/second.md', 'Second', 'same body');
|
|
94
|
+
expect(first.revision).toBe(second.revision);
|
|
95
|
+
expect(first.sourceUri).not.toBe(second.sourceUri);
|
|
96
|
+
expect(first.identityAliases).toEqual(expect.arrayContaining(['First', 'notes/first.md']));
|
|
97
|
+
expect(second.identityAliases).toEqual(expect.arrayContaining(['Second', 'notes/second.md']));
|
|
98
|
+
});
|
|
99
|
+
test('treats move and rename as an additive alias transition before any canonical cutover', () => {
|
|
100
|
+
const before = (0, ResourceIdentity_1.createResourceIdentity)('algebra/intro.md', 'Intro', 'body');
|
|
101
|
+
const after = (0, ResourceIdentity_1.createResourceIdentity)('foundations/start.md', 'Intro', 'body');
|
|
102
|
+
expect(after.revision).toBe(before.revision);
|
|
103
|
+
expect(after.sourceUri).not.toBe(before.sourceUri);
|
|
104
|
+
expect(before.identityAliases).toContain('algebra/intro.md');
|
|
105
|
+
expect(after.identityAliases).toContain('foundations/start.md');
|
|
106
|
+
});
|
|
107
|
+
test('rejects Unicode-normalized collisions before graph mutation', () => {
|
|
108
|
+
expect(() => (0, ResourceIdentity_1.assertUniqueLegacyResourceIds)([
|
|
109
|
+
{ filename: 'Cafe\u0301', filepath: 'one/Cafe\u0301.md' },
|
|
110
|
+
{ filename: 'Caf\u00e9', filepath: 'two/Caf\u00e9.md' },
|
|
111
|
+
])).toThrow(/ambiguous|duplicate/i);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -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) {
|