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.
Files changed (195) hide show
  1. package/README.md +257 -1803
  2. package/dist/src/agent_workspace.contract.parity.test.js +105 -421
  3. package/dist/src/agent_workspace.frontend.test.js +410 -11
  4. package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
  5. package/dist/src/appimage.patchelf.test.js +113 -0
  6. package/dist/src/appimage.portability.test.js +156 -0
  7. package/dist/src/backend/FileLoader.js +18 -4
  8. package/dist/src/backend/FileLoader.test.js +125 -0
  9. package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
  10. package/dist/src/backend/GraphBuilder.js +66 -18
  11. package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
  12. package/dist/src/backend/GraphMetrics.js +0 -3
  13. package/dist/src/backend/ResourceIdentity.js +155 -0
  14. package/dist/src/backend/ResourceIdentity.test.js +113 -0
  15. package/dist/src/backend/algorithms/CycleDetection.js +0 -1
  16. package/dist/src/backend/algorithms/HybridEngine.js +0 -3
  17. package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
  18. package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
  19. package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
  20. package/dist/src/backend/build_dag.js +0 -1
  21. package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
  22. package/dist/src/capacitor.runtime.contract.test.js +11 -2
  23. package/dist/src/core/Graph.js +138 -42
  24. package/dist/src/core/Graph.test.js +74 -0
  25. package/dist/src/core/NoteConnection.js +2 -2
  26. package/dist/src/core/PathBridge.js +265 -2
  27. package/dist/src/core/ResourceReference.js +17 -0
  28. package/dist/src/desktop.godot.pack.test.js +142 -0
  29. package/dist/src/desktop.runtime.graph.test.js +66 -0
  30. package/dist/src/export/WorkspaceExportBundle.js +44 -0
  31. package/dist/src/export/WorkspaceExportBundle.test.js +61 -0
  32. package/dist/src/foundation.release.evidence.contract.test.js +127 -9
  33. package/dist/src/foundation.sqlite.runtime.contract.test.js +6 -0
  34. package/dist/src/frontend/README.md +257 -1803
  35. package/dist/src/frontend/agent_workspace.js +665 -7
  36. package/dist/src/frontend/index.html +31 -2
  37. package/dist/src/frontend/knowledge_projection_contract.js +230 -0
  38. package/dist/src/frontend/knowledge_projection_store.js +214 -0
  39. package/dist/src/frontend/libs/path_core.js +158 -42
  40. package/dist/src/frontend/locales/en.json +32 -0
  41. package/dist/src/frontend/locales/zh.json +32 -0
  42. package/dist/src/frontend/mobile_budget_runtime.js +33 -0
  43. package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
  44. package/dist/src/frontend/mobile_identity_contract.js +221 -0
  45. package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
  46. package/dist/src/frontend/path.html +6 -2
  47. package/dist/src/frontend/simulationWorker.js +1 -1
  48. package/dist/src/frontend/source_manager.js +110 -4
  49. package/dist/src/frontend/storage_provider.js +661 -71
  50. package/dist/src/frontend/styles.css +107 -13
  51. package/dist/src/frontend/workspace_panes.js +111 -7
  52. package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
  53. package/dist/src/graph.answer.operator.contract.test.js +72 -0
  54. package/dist/src/index.js +6 -2
  55. package/dist/src/index.knowledge-root.test.js +118 -0
  56. package/dist/src/indexing/IndexLifecycle.js +22 -0
  57. package/dist/src/indexing/IndexLifecycle.test.js +24 -0
  58. package/dist/src/knowledge.api.contract.test.js +4 -2
  59. package/dist/src/knowledge_projection.contract.test.js +134 -0
  60. package/dist/src/knowledge_projection_store.test.js +200 -0
  61. package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
  62. package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.js +4687 -3892
  64. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +418 -0
  65. package/dist/src/learning/KnowledgeLearningPlatform.test.js +243 -6
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +25 -1
  67. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +13 -5
  68. package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
  69. package/dist/src/learning/PublicAnswerContract.test.js +85 -0
  70. package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
  71. package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
  72. package/dist/src/learning/agentConversationExecution.js +211 -0
  73. package/dist/src/learning/agentConversationExecution.test.js +209 -0
  74. package/dist/src/learning/agentConversationSerialization.js +266 -0
  75. package/dist/src/learning/agentConversationSerialization.test.js +165 -0
  76. package/dist/src/learning/agentResponseBudget.js +179 -0
  77. package/dist/src/learning/agentResponseBudget.test.js +97 -0
  78. package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
  79. package/dist/src/learning/answerReleaseReview.js +1191 -250
  80. package/dist/src/learning/answerReleaseReview.test.js +1293 -7
  81. package/dist/src/learning/answerTaskPlan.js +473 -0
  82. package/dist/src/learning/conversationComposer.js +627 -386
  83. package/dist/src/learning/conversationComposer.test.js +1403 -0
  84. package/dist/src/learning/evidenceContextAssembler.js +312 -120
  85. package/dist/src/learning/graphAnswerCoverage.js +37 -0
  86. package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
  87. package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
  88. package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
  89. package/dist/src/learning/graphAnswerFacts.js +94 -0
  90. package/dist/src/learning/graphAnswerFacts.test.js +39 -0
  91. package/dist/src/learning/graphAnswerPlan.js +908 -0
  92. package/dist/src/learning/graphAnswerPlan.test.js +907 -0
  93. package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
  94. package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
  95. package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
  96. package/dist/src/learning/graphClaimMatcher.js +104 -0
  97. package/dist/src/learning/graphConditionedContext.js +109 -0
  98. package/dist/src/learning/graphConditionedContext.test.js +99 -0
  99. package/dist/src/learning/graphContextAssembler.js +3 -13
  100. package/dist/src/learning/graphContextAssembler.test.js +2 -2
  101. package/dist/src/learning/graphExpansionPolicy.js +22 -0
  102. package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
  103. package/dist/src/learning/identityMetadata.test.js +26 -0
  104. package/dist/src/learning/index.js +1 -0
  105. package/dist/src/learning/mobileAnswerProjection.js +104 -0
  106. package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
  107. package/dist/src/learning/queryBackend.js +34 -10
  108. package/dist/src/learning/queryBackend.test.js +21 -0
  109. package/dist/src/learning/ragContextPack.js +115 -16
  110. package/dist/src/learning/ragContextPack.test.js +151 -0
  111. package/dist/src/learning/ragEvidenceQuality.js +96 -0
  112. package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
  113. package/dist/src/learning/ragPublicText.js +67 -4
  114. package/dist/src/learning/ragPublicText.test.js +57 -0
  115. package/dist/src/learning/ragSufficiencyJudge.js +11 -1
  116. package/dist/src/learning/ragSufficiencyProviderJudge.js +16 -26
  117. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +51 -0
  118. package/dist/src/learning/requestNormalization.js +106 -0
  119. package/dist/src/learning/requestNormalization.test.js +101 -0
  120. package/dist/src/learning/store.js +116 -12
  121. package/dist/src/learning/store.test.js +299 -0
  122. package/dist/src/learning/vectorAccelerationAdapter.js +22 -6
  123. package/dist/src/learning/vectorAccelerationAdapter.test.js +30 -0
  124. package/dist/src/learning/workspaceHydration.js +168 -0
  125. package/dist/src/learning/workspaceHydration.test.js +30 -0
  126. package/dist/src/license.policy.contract.test.js +9 -4
  127. package/dist/src/markdown/MarkdownGateway.js +4 -0
  128. package/dist/src/markdown/MarkdownGateway.test.js +59 -0
  129. package/dist/src/middleware/SseResponseWriter.js +122 -0
  130. package/dist/src/middleware/SseResponseWriter.test.js +54 -0
  131. package/dist/src/middleware/auth.js +28 -7
  132. package/dist/src/middleware/auth.test.js +33 -0
  133. package/dist/src/middleware/index.js +2 -1
  134. package/dist/src/mobile.android.evidence.contract.test.js +146 -0
  135. package/dist/src/mobile.android.signing.contract.test.js +66 -0
  136. package/dist/src/mobile.artifact.contract.test.js +144 -0
  137. package/dist/src/mobile.budget.contract.test.js +90 -0
  138. package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
  139. package/dist/src/mobile.graph.parity.contract.test.js +207 -0
  140. package/dist/src/mobile.identity.contract.test.js +66 -0
  141. package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
  142. package/dist/src/mobile.identity.corpus.test.js +40 -0
  143. package/dist/src/mobile.native.recovery.contract.test.js +64 -0
  144. package/dist/src/mobile.pipeline.test.js +11 -1
  145. package/dist/src/mobile.profile.contract.test.js +225 -0
  146. package/dist/src/mobile.semantic.parity.test.js +92 -0
  147. package/dist/src/mobile_exact_analyzer.test.js +113 -0
  148. package/dist/src/notemd/ContentGenerator.js +4 -9
  149. package/dist/src/notemd/FileProcessor.js +4 -9
  150. package/dist/src/notemd/NotemdService.js +12 -17
  151. package/dist/src/notemd/Translator.js +4 -9
  152. package/dist/src/notemd/progressReporter.js +9 -0
  153. package/dist/src/notemd.server.integration.test.js +119 -4
  154. package/dist/src/notemd.workflow.pipeline.test.js +1 -1
  155. package/dist/src/pathbridge.handshake.contract.test.js +29 -0
  156. package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
  157. package/dist/src/pathcore.bundle.contract.test.js +8 -0
  158. package/dist/src/platform/ExportProfile.js +28 -0
  159. package/dist/src/platform/MobileBudget.js +11 -0
  160. package/dist/src/platform/PlatformCapabilities.js +14 -0
  161. package/dist/src/platform/PlatformCapabilities.test.js +6 -0
  162. package/dist/src/query_backend.external_http.integration.test.js +2 -2
  163. package/dist/src/release.godot.mirror.contract.test.js +28 -4
  164. package/dist/src/resources/ResourceRegistry.js +40 -0
  165. package/dist/src/resources/ResourceRegistry.test.js +36 -0
  166. package/dist/src/routes/index.js +0 -4
  167. package/dist/src/routes/knowledge.js +46 -33
  168. package/dist/src/routes/knowledgePayload.js +185 -0
  169. package/dist/src/routes/knowledgePayload.test.js +53 -0
  170. package/dist/src/routes/markdown.js +8 -0
  171. package/dist/src/routes/notemd.js +990 -234
  172. package/dist/src/routes/registry.contract.test.js +5 -2
  173. package/dist/src/routes/registry.shadow.contract.test.js +22 -0
  174. package/dist/src/routes/render.js +14 -51
  175. package/dist/src/runtime.capabilities.test.js +18 -2
  176. package/dist/src/server.js +566 -1090
  177. package/dist/src/server.migration.test.js +417 -15
  178. package/dist/src/sidecar.build.lock.contract.test.js +201 -0
  179. package/dist/src/sidecar.freshness.contract.test.js +184 -0
  180. package/dist/src/sidecar.replacement.boundary.contract.test.js +6 -3
  181. package/dist/src/sidecar.supply.readiness.contract.test.js +11 -0
  182. package/dist/src/simulation.worker.offline.test.js +104 -0
  183. package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
  184. package/dist/src/storage.provider.contract.test.js +152 -1
  185. package/dist/src/tauri.frontend.build.contract.test.js +8 -0
  186. package/dist/src/tauri.window.evidence.execution.test.js +68 -0
  187. package/dist/src/utils/RuntimePaths.js +6 -0
  188. package/dist/src/utils/RuntimePaths.test.js +27 -0
  189. package/dist/src/windows.installer.build.test.js +109 -0
  190. package/dist/src/windows.installer.payload.test.js +76 -0
  191. package/dist/src/windows.installer.runtime.test.js +69 -0
  192. package/dist/src/workspace/WorkspaceRegistry.js +13 -0
  193. package/package.json +32 -10
  194. package/dist/src/routes/data.js +0 -267
  195. 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
- if (layout && layout.has(file.filename)) {
90
- const pos = layout.get(file.filename);
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
- // Fallback
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
- files.forEach(sourceFile => {
450
- const sourceId = sourceFile.filename;
451
- const content = sourceFile.content;
452
- files.forEach(targetFile => {
453
- const targetId = targetFile.filename;
454
- if (sourceId === targetId)
455
- return; // Skip self | 跳过自身
456
- // Exclusion Check
457
- if (config_1.config.exclusionList.includes(targetId)) {
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
+ });
@@ -105,7 +105,6 @@ class CycleDetector {
105
105
  stack.pop();
106
106
  continue;
107
107
  }
108
- // Get next neighbor
109
108
  const neighborId = frame.neighbors[frame.index];
110
109
  frame.index++; // Advance for next time
111
110
  if (onPath.has(neighborId)) {
@@ -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) {