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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const GraphBuilder_1 = require("./GraphBuilder");
|
|
4
|
+
const config_1 = require("./config");
|
|
5
|
+
describe('GraphBuilder identity compatibility', () => {
|
|
6
|
+
const originalConfig = {
|
|
7
|
+
...config_1.config,
|
|
8
|
+
exclusionList: [...config_1.config.exclusionList],
|
|
9
|
+
};
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
config_1.config.enableTags = false;
|
|
12
|
+
config_1.config.enableStatisticalInference = false;
|
|
13
|
+
config_1.config.enableVectorSimilarity = false;
|
|
14
|
+
config_1.config.enableHybridInference = false;
|
|
15
|
+
config_1.config.enableGPU = false;
|
|
16
|
+
config_1.config.enableGPULayout = false;
|
|
17
|
+
config_1.config.clusteringStrategy = 'folder';
|
|
18
|
+
config_1.config.exclusionList = [];
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
Object.assign(config_1.config, originalConfig);
|
|
22
|
+
});
|
|
23
|
+
test('preserves identity metadata, reads URI frontmatter, and accepts URI layouts', async () => {
|
|
24
|
+
const files = [
|
|
25
|
+
{
|
|
26
|
+
filepath: 'workspace/algebra/a.md',
|
|
27
|
+
filename: 'A',
|
|
28
|
+
content: '# A',
|
|
29
|
+
relativePath: 'algebra/a.md',
|
|
30
|
+
sourceUri: 'note://workspace/v1/algebra/a.md',
|
|
31
|
+
canonicalId: 'algebra/a',
|
|
32
|
+
revision: 'sha256:a',
|
|
33
|
+
identityAliases: ['A', 'algebra/a.md'],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
filepath: 'workspace/algebra/b.md',
|
|
37
|
+
filename: 'B',
|
|
38
|
+
content: '---\nprerequisites:\n - note://workspace/v1/algebra/a.md\n---\n# B',
|
|
39
|
+
relativePath: 'algebra/b.md',
|
|
40
|
+
sourceUri: 'note://workspace/v1/algebra/b.md',
|
|
41
|
+
revision: 'sha256:b',
|
|
42
|
+
identityAliases: ['B', 'algebra/b.md'],
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
const graph = await GraphBuilder_1.GraphBuilder.build(files, new Map([
|
|
46
|
+
['note://workspace/v1/algebra/a.md', { x: 12, y: 34 }],
|
|
47
|
+
]));
|
|
48
|
+
expect(graph.getNode('A')).toEqual(expect.objectContaining({
|
|
49
|
+
sourceUri: 'note://workspace/v1/algebra/a.md',
|
|
50
|
+
canonicalId: 'algebra/a',
|
|
51
|
+
revision: 'sha256:a',
|
|
52
|
+
identityAliases: expect.arrayContaining(['algebra/a.md']),
|
|
53
|
+
x: 12,
|
|
54
|
+
y: 34,
|
|
55
|
+
}));
|
|
56
|
+
expect(graph.getIncomingEdges('B')).toEqual(expect.arrayContaining([
|
|
57
|
+
expect.objectContaining({ source: 'A', target: 'B', type: 'explicit-prerequisite' }),
|
|
58
|
+
]));
|
|
59
|
+
});
|
|
60
|
+
test('uses indexed keyword candidates without changing exact-match semantics', async () => {
|
|
61
|
+
const files = [
|
|
62
|
+
{
|
|
63
|
+
filepath: 'workspace/alpha.md',
|
|
64
|
+
filename: 'Alpha',
|
|
65
|
+
content: 'Alpha is a prerequisite concept.',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
filepath: 'workspace/context.md',
|
|
69
|
+
filename: 'Context',
|
|
70
|
+
content: 'This context mentions Alpha as an explicit phrase.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
filepath: 'workspace/beta.md',
|
|
74
|
+
filename: 'Beta',
|
|
75
|
+
content: 'This context mentions alphabet but not the standalone concept.',
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
const graph = await GraphBuilder_1.GraphBuilder.build(files);
|
|
79
|
+
expect(graph.getOutgoingEdges('Alpha')).toEqual(expect.arrayContaining([
|
|
80
|
+
expect.objectContaining({ target: 'Context', type: 'keyword-match' }),
|
|
81
|
+
]));
|
|
82
|
+
expect(graph.getOutgoingEdges('Alpha')).not.toEqual(expect.arrayContaining([
|
|
83
|
+
expect.objectContaining({ target: 'Beta', type: 'keyword-match' }),
|
|
84
|
+
]));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -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
|
+
});
|