noteconnection 1.6.8 → 1.8.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 (212) hide show
  1. package/LICENSE +674 -21
  2. package/README.md +258 -64
  3. package/dist/src/agent_workspace.contract.parity.test.js +475 -0
  4. package/dist/src/agent_workspace.frontend.test.js +9989 -0
  5. package/dist/src/agent_workspace.locale.contract.test.js +95 -0
  6. package/dist/src/agent_workspace.runtime.behavior.test.js +5072 -0
  7. package/dist/src/copy.assets.contract.test.js +130 -0
  8. package/dist/src/core/PathBridge.js +28 -3
  9. package/dist/src/export/WorkspaceExportBundle.js +1130 -0
  10. package/dist/src/export/WorkspaceExportBundle.test.js +1480 -0
  11. package/dist/src/export/index.js +18 -0
  12. package/dist/src/export/types.js +2 -0
  13. package/dist/src/fixrisk.issue.verifier.contract.test.js +3 -1
  14. package/dist/src/foundation.ann.runtime.contract.test.js +81 -0
  15. package/dist/src/foundation.release.evidence.contract.test.js +406 -0
  16. package/dist/src/foundation.sqlite.runtime.contract.test.js +82 -0
  17. package/dist/src/frontend/README.md +258 -64
  18. package/dist/src/frontend/agent_workspace.js +5353 -0
  19. package/dist/src/frontend/agent_workspace_runtime.js +4434 -0
  20. package/dist/src/frontend/analysis.js +31 -4
  21. package/dist/src/frontend/app.js +3597 -141
  22. package/dist/src/frontend/focus_mode_interactions.js +33 -0
  23. package/dist/src/frontend/godot_future_path_renderer.js +540 -0
  24. package/dist/src/frontend/godot_tree_interactions.js +178 -0
  25. package/dist/src/frontend/graph_state.mjs +105 -0
  26. package/dist/src/frontend/hosted_future_path_runtime.js +157 -0
  27. package/dist/src/frontend/i18n.mjs +186 -0
  28. package/dist/src/frontend/index.html +641 -266
  29. package/dist/src/frontend/layout_gpu.js +12 -4
  30. package/dist/src/frontend/locales/en.json +721 -5
  31. package/dist/src/frontend/locales/zh.json +721 -5
  32. package/dist/src/frontend/main.mjs +60 -0
  33. package/dist/src/frontend/markdown_runtime.js +827 -0
  34. package/dist/src/frontend/notemd.css +49 -0
  35. package/dist/src/frontend/notemd.html +7 -1
  36. package/dist/src/frontend/notemd.js +64 -0
  37. package/dist/src/frontend/path.html +107 -0
  38. package/dist/src/frontend/path_app.js +2189 -150
  39. package/dist/src/frontend/path_layout.mjs +143 -0
  40. package/dist/src/frontend/path_mermaid_utils.mjs +108 -0
  41. package/dist/src/frontend/path_modules_bridge.js +486 -0
  42. package/dist/src/frontend/path_state.mjs +118 -0
  43. package/dist/src/frontend/path_styles.css +146 -0
  44. package/dist/src/frontend/path_worker_bridge.mjs +85 -0
  45. package/dist/src/frontend/reader.js +522 -27
  46. package/dist/src/frontend/runtime_bridge.js +67 -54
  47. package/dist/src/frontend/runtime_bridge.mjs +279 -0
  48. package/dist/src/frontend/settings.js +130 -12
  49. package/dist/src/frontend/simulationWorker.js +241 -6
  50. package/dist/src/frontend/source_manager.js +190 -21
  51. package/dist/src/frontend/styles.css +2853 -72
  52. package/dist/src/frontend/workbench_state.mjs +101 -0
  53. package/dist/src/frontend/workspace_panes.js +10168 -0
  54. package/dist/src/frontend.locale.contract.test.js +62 -0
  55. package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
  56. package/dist/src/indexing/IndexLifecycle.js +195 -0
  57. package/dist/src/indexing/IndexLifecycle.test.js +49 -0
  58. package/dist/src/indexing/SegmentBuilder.js +64 -0
  59. package/dist/src/indexing/UnitBuilder.js +48 -0
  60. package/dist/src/indexing/types.js +2 -0
  61. package/dist/src/knowledge.api.contract.test.js +170 -0
  62. package/dist/src/learning/KnowledgeLearningPlatform.js +10386 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +327 -0
  64. package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
  65. package/dist/src/learning/KnowledgeLearningPlatform.test.js +2971 -0
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2974 -0
  67. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3928 -0
  68. package/dist/src/learning/answerReleaseReview.js +4319 -0
  69. package/dist/src/learning/answerReleaseReview.test.js +2888 -0
  70. package/dist/src/learning/api.js +2 -0
  71. package/dist/src/learning/conversationComposer.js +1480 -0
  72. package/dist/src/learning/conversationComposer.test.js +1817 -0
  73. package/dist/src/learning/domains/ConversationManager.js +53 -0
  74. package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
  75. package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
  76. package/dist/src/learning/domains/MasteryEngine.js +387 -0
  77. package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
  78. package/dist/src/learning/domains/QualityEvaluator.js +307 -0
  79. package/dist/src/learning/domains/TutorRouter.js +313 -0
  80. package/dist/src/learning/domains/index.js +33 -0
  81. package/dist/src/learning/domains/types.js +7 -0
  82. package/dist/src/learning/errors.js +29 -0
  83. package/dist/src/learning/evidenceContextAssembler.js +1176 -0
  84. package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
  85. package/dist/src/learning/graphContextAssembler.js +870 -0
  86. package/dist/src/learning/graphContextAssembler.test.js +1033 -0
  87. package/dist/src/learning/index.js +28 -0
  88. package/dist/src/learning/queryBackend.js +1898 -0
  89. package/dist/src/learning/queryBackend.test.js +955 -0
  90. package/dist/src/learning/ragContextPack.js +257 -0
  91. package/dist/src/learning/ragContextPack.test.js +160 -0
  92. package/dist/src/learning/ragPublicText.js +38 -0
  93. package/dist/src/learning/ragSufficiencyJudge.js +161 -0
  94. package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
  95. package/dist/src/learning/ragSufficiencyProviderJudge.js +227 -0
  96. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +156 -0
  97. package/dist/src/learning/requestNormalization.js +198 -0
  98. package/dist/src/learning/runtimeCapability.js +4677 -0
  99. package/dist/src/learning/runtimeCapability.test.js +3635 -0
  100. package/dist/src/learning/store.js +1240 -0
  101. package/dist/src/learning/store.test.js +1126 -0
  102. package/dist/src/learning/tutorAdapter.js +2 -0
  103. package/dist/src/learning/types.js +2 -0
  104. package/dist/src/learning/vectorAccelerationAdapter.js +942 -0
  105. package/dist/src/learning/vectorAccelerationAdapter.test.js +382 -0
  106. package/dist/src/lfs.asset.policy.contract.test.js +153 -0
  107. package/dist/src/license.policy.contract.test.js +66 -0
  108. package/dist/src/memory/MemoryGovernance.js +74 -0
  109. package/dist/src/memory/MemoryGovernance.test.js +46 -0
  110. package/dist/src/memory/types.js +2 -0
  111. package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
  112. package/dist/src/middleware/auth.js +17 -0
  113. package/dist/src/middleware/body-parser.js +45 -0
  114. package/dist/src/middleware/cors.js +44 -0
  115. package/dist/src/middleware/index.js +21 -0
  116. package/dist/src/middleware/request-trace.js +96 -0
  117. package/dist/src/notemd/AppConfigToml.js +6 -4
  118. package/dist/src/notemd/MermaidProcessor.js +400 -50
  119. package/dist/src/notemd/NotemdService.js +498 -13
  120. package/dist/src/notemd/PromptManager.js +15 -0
  121. package/dist/src/notemd/cli/commands.js +357 -0
  122. package/dist/src/notemd/cli/dispatcher.js +225 -0
  123. package/dist/src/notemd/cli/index.js +169 -0
  124. package/dist/src/notemd/cli/parser.js +68 -0
  125. package/dist/src/notemd/cli/types.js +2 -0
  126. package/dist/src/notemd/constants.js +43 -0
  127. package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
  128. package/dist/src/notemd/diagram/diagramSpec.js +79 -0
  129. package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
  130. package/dist/src/notemd/diagram/intent.js +95 -0
  131. package/dist/src/notemd/diagram/planner.js +71 -0
  132. package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
  133. package/dist/src/notemd/diagram/types.js +18 -0
  134. package/dist/src/notemd/index.js +26 -0
  135. package/dist/src/notemd/operations/capabilityManifest.js +23 -0
  136. package/dist/src/notemd/operations/cliContracts.js +17 -0
  137. package/dist/src/notemd/operations/configProfileCommands.js +85 -0
  138. package/dist/src/notemd/operations/registry.contract.test.js +95 -0
  139. package/dist/src/notemd/operations/registry.js +991 -0
  140. package/dist/src/notemd/operations/types.js +2 -0
  141. package/dist/src/notemd/providerDiagnostics.js +220 -0
  142. package/dist/src/notemd/providerProfiles.js +42 -0
  143. package/dist/src/notemd/providerTemplates.js +231 -0
  144. package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
  145. package/dist/src/notemd/search/SearchManager.js +13 -0
  146. package/dist/src/notemd/search/SearchProvider.js +2 -0
  147. package/dist/src/notemd/search/TavilyProvider.js +44 -0
  148. package/dist/src/notemd.agent.manifest.test.js +85 -0
  149. package/dist/src/notemd.api.contract.test.js +14 -1
  150. package/dist/src/notemd.app_config_toml.test.js +2 -1
  151. package/dist/src/notemd.batch.workflow.test.js +117 -0
  152. package/dist/src/notemd.cli.e2e.test.js +136 -0
  153. package/dist/src/notemd.core.test.js +51 -0
  154. package/dist/src/notemd.diagram.pipeline.test.js +233 -0
  155. package/dist/src/notemd.providerTemplates.test.js +34 -0
  156. package/dist/src/notemd.server.integration.test.js +143 -35
  157. package/dist/src/notemd.workflow.pipeline.test.js +162 -0
  158. package/dist/src/pathbridge.handshake.contract.test.js +16 -2
  159. package/dist/src/pathmode.background.contract.test.js +69 -0
  160. package/dist/src/pathmode.settings.api.contract.test.js +9 -0
  161. package/dist/src/pkg.sidecar.contract.test.js +9 -3
  162. package/dist/src/platform/ExportProfile.js +58 -0
  163. package/dist/src/platform/PlatformCapabilities.js +45 -0
  164. package/dist/src/platform/PlatformCapabilities.test.js +30 -0
  165. package/dist/src/platform/RenderMaterializer.js +33 -0
  166. package/dist/src/platform/RenderMaterializer.test.js +32 -0
  167. package/dist/src/query_backend.external_http.integration.test.js +410 -0
  168. package/dist/src/reader_renderer.js +404 -3
  169. package/dist/src/reader_renderer.test.js +87 -0
  170. package/dist/src/release.godot.mirror.contract.test.js +73 -0
  171. package/dist/src/resources/ResourceRegistry.js +223 -0
  172. package/dist/src/resources/ResourceRegistry.test.js +61 -0
  173. package/dist/src/resources/types.js +2 -0
  174. package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
  175. package/dist/src/routes/data.js +267 -0
  176. package/dist/src/routes/diagnostics.js +51 -0
  177. package/dist/src/routes/index.js +23 -0
  178. package/dist/src/routes/knowledge.js +968 -0
  179. package/dist/src/routes/markdown.js +287 -0
  180. package/dist/src/routes/notemd.js +565 -0
  181. package/dist/src/routes/registry.contract.test.js +130 -0
  182. package/dist/src/routes/render.js +285 -0
  183. package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
  184. package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
  185. package/dist/src/routes/settings.js +6 -0
  186. package/dist/src/routes/staticFiles.js +94 -0
  187. package/dist/src/routes/types.js +2 -0
  188. package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
  189. package/dist/src/server.js +11090 -1549
  190. package/dist/src/server.migration.test.js +193 -21
  191. package/dist/src/server.port.fallback.contract.test.js +63 -0
  192. package/dist/src/session/SessionStateStore.js +81 -0
  193. package/dist/src/session/SessionStateStore.test.js +58 -0
  194. package/dist/src/session/types.js +2 -0
  195. package/dist/src/settings.runtime.contract.test.js +50 -0
  196. package/dist/src/shared/types.contract.test.js +107 -0
  197. package/dist/src/shared/types.js +22 -0
  198. package/dist/src/sidecar.replacement.boundary.contract.test.js +128 -0
  199. package/dist/src/sidecar.supply.readiness.contract.test.js +144 -0
  200. package/dist/src/source_manager.loadflow.test.js +46 -0
  201. package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
  202. package/dist/src/tauri.frontend.build.contract.test.js +60 -0
  203. package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
  204. package/dist/src/utils/RuntimePaths.js +4 -13
  205. package/dist/src/utils/platform.js +153 -0
  206. package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
  207. package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
  208. package/dist/src/workflows/types.js +2 -0
  209. package/dist/src/workspace/WorkspaceRegistry.js +122 -0
  210. package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
  211. package/dist/src/workspace/types.js +2 -0
  212. package/package.json +61 -10
@@ -0,0 +1,1240 @@
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.FileBackedKnowledgeGraphStore = void 0;
37
+ exports.isOpsAdapter = isOpsAdapter;
38
+ exports.createFileBackedKnowledgeGraphStore = createFileBackedKnowledgeGraphStore;
39
+ exports.normalizeKnowledgeGraphStoreBackend = normalizeKnowledgeGraphStoreBackend;
40
+ exports.normalizeGraphDbSnapshotAdapterProvider = normalizeGraphDbSnapshotAdapterProvider;
41
+ exports.normalizeGraphDbStoreOperationMode = normalizeGraphDbStoreOperationMode;
42
+ exports.createFileGraphDbSnapshotAdapter = createFileGraphDbSnapshotAdapter;
43
+ exports.createGraphDbSnapshotAdapter = createGraphDbSnapshotAdapter;
44
+ exports.createKnowledgeGraphStore = createKnowledgeGraphStore;
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ function filterNodesFromSnapshot(snapshot, filter) {
48
+ let results = snapshot.atoms;
49
+ if (filter.nodeIds && filter.nodeIds.length > 0) {
50
+ const idSet = new Set(filter.nodeIds);
51
+ results = results.filter((atom) => idSet.has(atom.id) || idSet.has(atom.stableKey ?? ''));
52
+ }
53
+ if (filter.stableKey) {
54
+ results = results.filter((atom) => atom.stableKey === filter.stableKey);
55
+ }
56
+ if (filter.limit && filter.limit > 0) {
57
+ results = results.slice(0, filter.limit);
58
+ }
59
+ return results;
60
+ }
61
+ function filterEdgesFromSnapshot(snapshot, filter) {
62
+ let results = snapshot.relationEdges;
63
+ if (filter.fromNodeId) {
64
+ results = results.filter((edge) => edge.sourceAtomId === filter.fromNodeId);
65
+ }
66
+ if (filter.toNodeId) {
67
+ results = results.filter((edge) => edge.targetAtomId === filter.toNodeId);
68
+ }
69
+ if (filter.relationKind) {
70
+ results = results.filter((edge) => edge.relationKind === filter.relationKind);
71
+ }
72
+ if (filter.limit && filter.limit > 0) {
73
+ results = results.slice(0, filter.limit);
74
+ }
75
+ return results;
76
+ }
77
+ function findPathInSnapshot(snapshot, sourceId, targetId, maxDepth = 10) {
78
+ const adjacency = new Map();
79
+ for (const edge of snapshot.relationEdges) {
80
+ if (!adjacency.has(edge.sourceAtomId))
81
+ adjacency.set(edge.sourceAtomId, []);
82
+ adjacency.get(edge.sourceAtomId).push({ to: edge.targetAtomId, edge });
83
+ }
84
+ const visited = new Set();
85
+ const queue = [
86
+ { nodeId: sourceId, path: [sourceId], edges: [] }
87
+ ];
88
+ visited.add(sourceId);
89
+ while (queue.length > 0) {
90
+ const current = queue.shift();
91
+ if (current.path.length > maxDepth)
92
+ continue;
93
+ if (current.nodeId === targetId) {
94
+ return {
95
+ path: current.path,
96
+ length: current.path.length - 1,
97
+ edges: current.edges,
98
+ found: true,
99
+ };
100
+ }
101
+ const neighbors = adjacency.get(current.nodeId) ?? [];
102
+ for (const { to, edge } of neighbors) {
103
+ if (!visited.has(to)) {
104
+ visited.add(to);
105
+ queue.push({
106
+ nodeId: to,
107
+ path: [...current.path, to],
108
+ edges: [...current.edges, { from: edge.sourceAtomId, to: edge.targetAtomId, relation: edge.relationKind }],
109
+ });
110
+ }
111
+ }
112
+ }
113
+ return { path: [], length: 0, edges: [], found: false };
114
+ }
115
+ /** Check if a store adapter supports operation-level queries. */
116
+ function isOpsAdapter(store) {
117
+ return typeof store.getCapabilities === 'function';
118
+ }
119
+ class FileBackedKnowledgeGraphStore {
120
+ constructor(options) {
121
+ this.options = options;
122
+ this.loaded = false;
123
+ /** Cached snapshot for operation-level queries without re-reading from disk. */
124
+ this.cachedSnapshot = null;
125
+ }
126
+ // ── M10.5: Operational Semantics ──
127
+ getCapabilities() {
128
+ return {
129
+ snapshotSupported: true,
130
+ nodeQuerySupported: true,
131
+ edgeQuerySupported: true,
132
+ pathQuerySupported: true,
133
+ writeSupported: true,
134
+ serverSideQuery: false, // File-backed loads full snapshot into memory
135
+ };
136
+ }
137
+ async getNode(atomId) {
138
+ const snapshot = await this.ensureSnapshot();
139
+ if (!snapshot)
140
+ return null;
141
+ return snapshot.atoms.find(a => a.id === atomId || a.stableKey === atomId) ?? null;
142
+ }
143
+ async queryNodes(filter) {
144
+ const snapshot = await this.ensureSnapshot();
145
+ if (!snapshot)
146
+ return [];
147
+ return filterNodesFromSnapshot(snapshot, filter);
148
+ }
149
+ async queryEdges(filter) {
150
+ const snapshot = await this.ensureSnapshot();
151
+ if (!snapshot)
152
+ return [];
153
+ return filterEdgesFromSnapshot(snapshot, filter);
154
+ }
155
+ async findPath(sourceId, targetId, maxDepth = 10) {
156
+ const snapshot = await this.ensureSnapshot();
157
+ if (!snapshot)
158
+ return { path: [], length: 0, edges: [], found: false };
159
+ return findPathInSnapshot(snapshot, sourceId, targetId, maxDepth);
160
+ }
161
+ async ensureSnapshot() {
162
+ if (this.cachedSnapshot)
163
+ return this.cachedSnapshot;
164
+ this.cachedSnapshot = await this.loadSnapshot();
165
+ return this.cachedSnapshot;
166
+ }
167
+ async loadSnapshot() {
168
+ const filePath = path.resolve(this.options.filePath);
169
+ try {
170
+ const content = await fs.promises.readFile(filePath, 'utf8');
171
+ const parsed = JSON.parse(content);
172
+ if ((parsed.schemaVersion !== 1 && parsed.schemaVersion !== 2) || !Array.isArray(parsed.atoms) || !Array.isArray(parsed.documents)) {
173
+ throw new Error('Invalid knowledge graph snapshot schema.');
174
+ }
175
+ this.loaded = true;
176
+ this.lastLoadAt = new Date().toISOString();
177
+ return parsed;
178
+ }
179
+ catch (error) {
180
+ const code = error?.code;
181
+ if (code === 'ENOENT' || code === 'ENOTDIR') {
182
+ this.loaded = false;
183
+ this.lastError = undefined;
184
+ return null;
185
+ }
186
+ this.loaded = false;
187
+ this.lastError = String(error?.message || error);
188
+ throw error;
189
+ }
190
+ }
191
+ async saveSnapshot(snapshot) {
192
+ const filePath = path.resolve(this.options.filePath);
193
+ const directory = path.dirname(filePath);
194
+ const tempPath = `${filePath}.tmp`;
195
+ await fs.promises.mkdir(directory, { recursive: true });
196
+ const serialized = JSON.stringify(snapshot, null, 2);
197
+ try {
198
+ await fs.promises.writeFile(tempPath, serialized, 'utf8');
199
+ await fs.promises.rename(tempPath, filePath);
200
+ this.lastSaveAt = new Date().toISOString();
201
+ this.lastError = undefined;
202
+ }
203
+ catch (error) {
204
+ this.lastError = String(error?.message || error);
205
+ throw error;
206
+ }
207
+ finally {
208
+ try {
209
+ await fs.promises.unlink(tempPath);
210
+ }
211
+ catch (_cleanupError) {
212
+ }
213
+ }
214
+ }
215
+ getDiagnostics() {
216
+ const filePath = path.resolve(this.options.filePath);
217
+ const caps = this.getCapabilities();
218
+ // Staleness tracking (GitNexus pattern): compare snapshot save time to file mtime
219
+ let staleSince;
220
+ let fileMtime;
221
+ try {
222
+ const stat = fs.statSync(filePath);
223
+ fileMtime = stat.mtime.toISOString();
224
+ if (this.lastSaveAt && stat.mtime > new Date(this.lastSaveAt)) {
225
+ staleSince = stat.mtime.toISOString();
226
+ }
227
+ }
228
+ catch { /* file may not exist yet */ }
229
+ return {
230
+ storeType: 'file',
231
+ location: filePath,
232
+ exists: fs.existsSync(filePath),
233
+ loaded: this.loaded,
234
+ lastLoadAt: this.lastLoadAt,
235
+ lastSaveAt: this.lastSaveAt,
236
+ lastError: this.lastError,
237
+ adapterId: 'file-backed-ops',
238
+ graphDbOperationMode: caps.serverSideQuery ? 'server-side-query' : 'snapshot-with-ops',
239
+ backendReady: this.loaded,
240
+ staleSince,
241
+ fileMtime,
242
+ };
243
+ }
244
+ }
245
+ exports.FileBackedKnowledgeGraphStore = FileBackedKnowledgeGraphStore;
246
+ function createFileBackedKnowledgeGraphStore(options) {
247
+ return new FileBackedKnowledgeGraphStore(options);
248
+ }
249
+ function hasGraphQueryOpsPath(adapter) {
250
+ return typeof adapter.getNodeByOps === 'function'
251
+ || typeof adapter.queryNodesByOps === 'function'
252
+ || typeof adapter.queryEdgesByOps === 'function'
253
+ || typeof adapter.findPathByOps === 'function';
254
+ }
255
+ // Why ops-capable detection: Some graphdb adapters support fine-grained operations
256
+ // (getNode, queryEdges, findPath) in addition to full-snapshot load/save. We detect
257
+ // this via capability negotiation: check for *ByOps methods + explicit opsCapable flag
258
+ // or getCapabilities().mode. This allows the same store interface to serve both
259
+ // simple file-backed stores and advanced graphdb backends without changing callers.
260
+ function hasOpsCapablePath(adapter) {
261
+ const hasSnapshotOpsMethods = typeof adapter.loadSnapshotByOps === 'function'
262
+ && typeof adapter.saveSnapshotByOps === 'function';
263
+ const hasQueryOpsMethods = hasGraphQueryOpsPath(adapter);
264
+ if (!hasSnapshotOpsMethods && !hasQueryOpsMethods)
265
+ return false;
266
+ if (adapter.opsCapable === true)
267
+ return true;
268
+ const caps = adapter.getCapabilities?.();
269
+ if (caps?.mode === 'ops_capable')
270
+ return true;
271
+ if (hasQueryOpsMethods) {
272
+ const nodeQuerySupported = caps?.nodeQuerySupported === true;
273
+ const edgeQuerySupported = caps?.edgeQuerySupported === true;
274
+ const pathQuerySupported = caps?.pathQuerySupported === true;
275
+ return nodeQuerySupported || edgeQuerySupported || pathQuerySupported;
276
+ }
277
+ return false;
278
+ }
279
+ function normalizeKnowledgeGraphStoreBackend(v) {
280
+ const valid = new Set(['file', 'graphdb', 'none', 'memory']);
281
+ const s = String(v ?? 'file').trim().toLowerCase();
282
+ if (s === 'memory')
283
+ return 'memory';
284
+ return valid.has(s) ? s : 'file';
285
+ }
286
+ function normalizeGraphDbSnapshotAdapterProvider(v) {
287
+ const s = String(v ?? 'file').trim().toLowerCase();
288
+ if (s === 'local-file' || s === 'file')
289
+ return 'file';
290
+ if (s === 'external_http' || s === 'remote-http' || s === 'service' || s === 'http')
291
+ return 'http';
292
+ if (s === 'sqlite' || s === 'embedded_sqlite' || s === 'embedded-sqlite' || s === 'embedded')
293
+ return 'sqlite';
294
+ if (s === 'none' || s === 'disabled' || s === 'fallback_only')
295
+ return 'none';
296
+ if (s === 'unknown')
297
+ return 'file';
298
+ return 'file';
299
+ }
300
+ function normalizeGraphDbStoreOperationMode(v) {
301
+ const s = String(v ?? 'snapshot').trim().toLowerCase();
302
+ if (s === 'snapshot' || s === 'snapshot_only')
303
+ return 'snapshot_only';
304
+ if (s === 'ops' || s === 'ops_preferred' || s === 'operations')
305
+ return 'ops_preferred';
306
+ return 'snapshot_only';
307
+ }
308
+ function createFileGraphDbSnapshotAdapter(options) {
309
+ const filePath = options?.filePath ?? '/tmp/notemd-kg-snapshot.json';
310
+ const store = new FileBackedKnowledgeGraphStore({ filePath });
311
+ return {
312
+ id: options?.id ?? 'file-graphdb-local',
313
+ provider: 'file',
314
+ opsCapable: true,
315
+ getCapabilities: () => ({
316
+ snapshotSupported: true, nodeQuerySupported: true,
317
+ edgeQuerySupported: true, pathQuerySupported: true,
318
+ writeSupported: true, serverSideQuery: false,
319
+ mode: 'ops_capable',
320
+ supportedReadOperations: ['load_snapshot', 'load_snapshot_by_ops', 'probe_snapshot_metadata'],
321
+ supportedWriteOperations: ['save_snapshot', 'save_snapshot_by_ops'],
322
+ }),
323
+ loadSnapshot: () => store.loadSnapshot(),
324
+ saveSnapshot: (snapshot) => store.saveSnapshot(snapshot),
325
+ loadSnapshotByOps: () => store.loadSnapshot(),
326
+ saveSnapshotByOps: (snapshot) => store.saveSnapshot(snapshot),
327
+ probeSnapshotMetadata: async () => {
328
+ const s = await store.loadSnapshot();
329
+ return s ? { schemaVersion: s.schemaVersion, savedAt: s.savedAt, atomCount: s.atoms.length, relationEdgeCount: s.relationEdges.length, temporalEdgeCount: s.temporalEdges.length, documentCount: s.documents.length } : null;
330
+ },
331
+ getDiagnostics: () => ({
332
+ ...store.getDiagnostics(),
333
+ capabilityMode: 'ops_capable',
334
+ supportedReadOperations: ['load_snapshot', 'load_snapshot_by_ops', 'probe_snapshot_metadata'],
335
+ supportedWriteOperations: ['save_snapshot', 'save_snapshot_by_ops'],
336
+ lastReadPath: 'ops',
337
+ lastWritePath: 'ops',
338
+ }),
339
+ };
340
+ }
341
+ function tryLoadNodeSqlite() {
342
+ try {
343
+ return require('node:sqlite');
344
+ }
345
+ catch {
346
+ return null;
347
+ }
348
+ }
349
+ function createSqliteGraphDbSnapshotAdapter(options) {
350
+ const sqliteModule = tryLoadNodeSqlite();
351
+ if (!sqliteModule || typeof sqliteModule.DatabaseSync !== 'function') {
352
+ return null;
353
+ }
354
+ const sqlitePath = path.resolve(String(options?.sqlitePath || options?.filePath || '/tmp/notemd-kg-snapshot.sqlite'));
355
+ fs.mkdirSync(path.dirname(sqlitePath), { recursive: true });
356
+ let db = null;
357
+ let closed = false;
358
+ const adapterId = options?.id ?? 'embedded-sqlite-graphdb';
359
+ let loaded = false;
360
+ let lastLoadAt = '';
361
+ let lastSaveAt = '';
362
+ let lastError = '';
363
+ let lastReadPath = 'ops';
364
+ let lastWritePath = 'ops';
365
+ const ensureDb = () => {
366
+ if (!db || closed) {
367
+ db = new sqliteModule.DatabaseSync(sqlitePath);
368
+ closed = false;
369
+ }
370
+ return db;
371
+ };
372
+ const ensureSchema = () => {
373
+ ensureDb().exec(`
374
+ PRAGMA journal_mode = WAL;
375
+ PRAGMA synchronous = NORMAL;
376
+ PRAGMA busy_timeout = 2000;
377
+ CREATE TABLE IF NOT EXISTS snapshot_store (
378
+ snapshot_id INTEGER PRIMARY KEY CHECK (snapshot_id = 1),
379
+ schema_version INTEGER NOT NULL,
380
+ saved_at TEXT NOT NULL,
381
+ payload_json TEXT NOT NULL
382
+ );
383
+ CREATE TABLE IF NOT EXISTS atoms (
384
+ atom_id TEXT PRIMARY KEY,
385
+ stable_key TEXT,
386
+ title TEXT NOT NULL,
387
+ source_path TEXT,
388
+ updated_at TEXT,
389
+ raw_json TEXT NOT NULL
390
+ );
391
+ CREATE INDEX IF NOT EXISTS idx_atoms_stable_key ON atoms(stable_key);
392
+ CREATE TABLE IF NOT EXISTS relation_edges (
393
+ edge_id TEXT PRIMARY KEY,
394
+ source_atom_id TEXT NOT NULL,
395
+ target_atom_id TEXT NOT NULL,
396
+ relation_kind TEXT NOT NULL,
397
+ confidence REAL NOT NULL,
398
+ raw_json TEXT NOT NULL
399
+ );
400
+ CREATE INDEX IF NOT EXISTS idx_relation_edges_source ON relation_edges(source_atom_id);
401
+ CREATE INDEX IF NOT EXISTS idx_relation_edges_target ON relation_edges(target_atom_id);
402
+ CREATE INDEX IF NOT EXISTS idx_relation_edges_kind ON relation_edges(relation_kind);
403
+ `);
404
+ };
405
+ const loadSnapshotRow = () => {
406
+ ensureSchema();
407
+ return ensureDb().prepare(`
408
+ SELECT schema_version, saved_at, payload_json
409
+ FROM snapshot_store
410
+ WHERE snapshot_id = 1
411
+ `).get();
412
+ };
413
+ const loadSnapshotPayload = () => {
414
+ const row = loadSnapshotRow();
415
+ if (!row || !row.payload_json) {
416
+ loaded = false;
417
+ return null;
418
+ }
419
+ const parsed = JSON.parse(String(row.payload_json));
420
+ if ((parsed.schemaVersion !== 1 && parsed.schemaVersion !== 2) || !Array.isArray(parsed.atoms) || !Array.isArray(parsed.documents)) {
421
+ throw new Error('Invalid sqlite knowledge graph snapshot schema.');
422
+ }
423
+ loaded = true;
424
+ lastLoadAt = new Date().toISOString();
425
+ lastError = '';
426
+ return parsed;
427
+ };
428
+ const replaceSnapshotData = (snapshot) => {
429
+ ensureSchema();
430
+ const activeDb = ensureDb();
431
+ activeDb.exec('BEGIN IMMEDIATE TRANSACTION');
432
+ try {
433
+ activeDb.exec('DELETE FROM snapshot_store');
434
+ activeDb.exec('DELETE FROM atoms');
435
+ activeDb.exec('DELETE FROM relation_edges');
436
+ activeDb.prepare(`
437
+ INSERT INTO snapshot_store (snapshot_id, schema_version, saved_at, payload_json)
438
+ VALUES (1, ?, ?, ?)
439
+ `).run(snapshot.schemaVersion, snapshot.savedAt, JSON.stringify(snapshot));
440
+ const insertAtom = activeDb.prepare(`
441
+ INSERT INTO atoms (atom_id, stable_key, title, source_path, updated_at, raw_json)
442
+ VALUES (?, ?, ?, ?, ?, ?)
443
+ `);
444
+ snapshot.atoms.forEach((atom) => {
445
+ insertAtom.run(atom.id, atom.stableKey || '', atom.title, atom.sourcePath || '', atom.updatedAt || '', JSON.stringify(atom));
446
+ });
447
+ const insertRelationEdge = activeDb.prepare(`
448
+ INSERT INTO relation_edges (edge_id, source_atom_id, target_atom_id, relation_kind, confidence, raw_json)
449
+ VALUES (?, ?, ?, ?, ?, ?)
450
+ `);
451
+ snapshot.relationEdges.forEach((edge) => {
452
+ insertRelationEdge.run(edge.id, edge.sourceAtomId, edge.targetAtomId, edge.relationKind, Number(edge.confidence || 0), JSON.stringify(edge));
453
+ });
454
+ activeDb.exec('COMMIT');
455
+ loaded = true;
456
+ lastSaveAt = new Date().toISOString();
457
+ lastError = '';
458
+ }
459
+ catch (error) {
460
+ try {
461
+ activeDb.exec('ROLLBACK');
462
+ }
463
+ catch {
464
+ }
465
+ lastError = String(error?.message || error);
466
+ throw error;
467
+ }
468
+ };
469
+ const mapNodeRows = (rows) => rows
470
+ .map((row) => {
471
+ try {
472
+ return JSON.parse(String(row.raw_json || 'null'));
473
+ }
474
+ catch {
475
+ return null;
476
+ }
477
+ })
478
+ .filter((atom) => Boolean(atom && typeof atom === 'object'));
479
+ const mapEdgeRows = (rows) => rows
480
+ .map((row) => {
481
+ try {
482
+ return JSON.parse(String(row.raw_json || 'null'));
483
+ }
484
+ catch {
485
+ return null;
486
+ }
487
+ })
488
+ .filter((edge) => Boolean(edge && typeof edge === 'object'));
489
+ const buildInClause = (values) => ({
490
+ placeholders: values.map(() => '?').join(', '),
491
+ params: values,
492
+ });
493
+ return {
494
+ id: adapterId,
495
+ provider: 'sqlite',
496
+ opsCapable: true,
497
+ getCapabilities: () => ({
498
+ snapshotSupported: true,
499
+ nodeQuerySupported: true,
500
+ edgeQuerySupported: true,
501
+ pathQuerySupported: true,
502
+ writeSupported: true,
503
+ serverSideQuery: true,
504
+ mode: 'ops_capable',
505
+ supportedReadOperations: ['load_snapshot', 'get_node', 'query_nodes', 'query_edges', 'find_path', 'probe_snapshot_metadata'],
506
+ supportedWriteOperations: ['save_snapshot'],
507
+ }),
508
+ loadSnapshot: async () => {
509
+ lastReadPath = 'ops';
510
+ try {
511
+ return loadSnapshotPayload();
512
+ }
513
+ catch (error) {
514
+ loaded = false;
515
+ lastError = String(error?.message || error);
516
+ throw error;
517
+ }
518
+ },
519
+ saveSnapshot: async (snapshot) => {
520
+ lastWritePath = 'ops';
521
+ replaceSnapshotData(snapshot);
522
+ },
523
+ probeSnapshotMetadata: async () => {
524
+ ensureSchema();
525
+ const snapshotRow = loadSnapshotRow();
526
+ if (!snapshotRow) {
527
+ return null;
528
+ }
529
+ const activeDb = ensureDb();
530
+ const atomCountRow = activeDb.prepare('SELECT COUNT(*) AS count FROM atoms').get();
531
+ const relationCountRow = activeDb.prepare('SELECT COUNT(*) AS count FROM relation_edges').get();
532
+ const snapshot = loadSnapshotPayload();
533
+ return {
534
+ schemaVersion: Number(snapshotRow.schema_version || snapshot?.schemaVersion || 1),
535
+ savedAt: String(snapshotRow.saved_at || snapshot?.savedAt || ''),
536
+ atomCount: Number(atomCountRow?.count || 0),
537
+ relationEdgeCount: Number(relationCountRow?.count || 0),
538
+ temporalEdgeCount: snapshot?.temporalEdges.length || 0,
539
+ documentCount: snapshot?.documents.length || 0,
540
+ };
541
+ },
542
+ loadSnapshotByOps: async () => {
543
+ lastReadPath = 'ops';
544
+ return loadSnapshotPayload();
545
+ },
546
+ saveSnapshotByOps: async (snapshot) => {
547
+ lastWritePath = 'ops';
548
+ replaceSnapshotData(snapshot);
549
+ },
550
+ getNodeByOps: async (atomId) => {
551
+ ensureSchema();
552
+ lastReadPath = 'ops';
553
+ const normalizedId = String(atomId || '').trim();
554
+ if (!normalizedId) {
555
+ return null;
556
+ }
557
+ const row = ensureDb().prepare(`
558
+ SELECT raw_json
559
+ FROM atoms
560
+ WHERE atom_id = ? OR stable_key = ?
561
+ LIMIT 1
562
+ `).get(normalizedId, normalizedId);
563
+ return mapNodeRows(row ? [row] : [])[0] || null;
564
+ },
565
+ queryNodesByOps: async (filter) => {
566
+ ensureSchema();
567
+ lastReadPath = 'ops';
568
+ const clauses = [];
569
+ const params = [];
570
+ if (Array.isArray(filter.nodeIds) && filter.nodeIds.length > 0) {
571
+ const normalizedIds = Array.from(new Set(filter.nodeIds.map((item) => String(item || '').trim()).filter(Boolean)));
572
+ if (normalizedIds.length > 0) {
573
+ const inClause = buildInClause(normalizedIds);
574
+ clauses.push(`(atom_id IN (${inClause.placeholders}) OR stable_key IN (${inClause.placeholders}))`);
575
+ params.push(...inClause.params, ...inClause.params);
576
+ }
577
+ }
578
+ if (String(filter.stableKey || '').trim()) {
579
+ clauses.push('stable_key = ?');
580
+ params.push(String(filter.stableKey).trim());
581
+ }
582
+ let sql = 'SELECT raw_json FROM atoms';
583
+ if (clauses.length > 0) {
584
+ sql += ` WHERE ${clauses.join(' AND ')}`;
585
+ }
586
+ sql += ' ORDER BY atom_id ASC';
587
+ if (filter.limit && filter.limit > 0) {
588
+ sql += ` LIMIT ${Math.max(1, Math.floor(filter.limit))}`;
589
+ }
590
+ return mapNodeRows(ensureDb().prepare(sql).all(...params));
591
+ },
592
+ queryEdgesByOps: async (filter) => {
593
+ ensureSchema();
594
+ lastReadPath = 'ops';
595
+ const clauses = [];
596
+ const params = [];
597
+ if (String(filter.fromNodeId || '').trim()) {
598
+ clauses.push('source_atom_id = ?');
599
+ params.push(String(filter.fromNodeId).trim());
600
+ }
601
+ if (String(filter.toNodeId || '').trim()) {
602
+ clauses.push('target_atom_id = ?');
603
+ params.push(String(filter.toNodeId).trim());
604
+ }
605
+ if (String(filter.relationKind || '').trim()) {
606
+ clauses.push('relation_kind = ?');
607
+ params.push(String(filter.relationKind).trim());
608
+ }
609
+ let sql = 'SELECT raw_json FROM relation_edges';
610
+ if (clauses.length > 0) {
611
+ sql += ` WHERE ${clauses.join(' AND ')}`;
612
+ }
613
+ sql += ' ORDER BY edge_id ASC';
614
+ if (filter.limit && filter.limit > 0) {
615
+ sql += ` LIMIT ${Math.max(1, Math.floor(filter.limit))}`;
616
+ }
617
+ return mapEdgeRows(ensureDb().prepare(sql).all(...params));
618
+ },
619
+ findPathByOps: async (sourceId, targetId, maxDepth = 10) => {
620
+ ensureSchema();
621
+ lastReadPath = 'ops';
622
+ const source = String(sourceId || '').trim();
623
+ const target = String(targetId || '').trim();
624
+ if (!source || !target) {
625
+ return { path: [], length: 0, edges: [], found: false };
626
+ }
627
+ const rows = ensureDb().prepare(`
628
+ SELECT source_atom_id, target_atom_id, relation_kind
629
+ FROM relation_edges
630
+ `).all();
631
+ const adjacency = new Map();
632
+ rows.forEach((row) => {
633
+ const from = String(row.source_atom_id || '').trim();
634
+ const to = String(row.target_atom_id || '').trim();
635
+ if (!from || !to) {
636
+ return;
637
+ }
638
+ if (!adjacency.has(from)) {
639
+ adjacency.set(from, []);
640
+ }
641
+ adjacency.get(from)?.push({
642
+ to,
643
+ relation: String(row.relation_kind || '').trim() || undefined,
644
+ });
645
+ });
646
+ const visited = new Set([source]);
647
+ const queue = [
648
+ { nodeId: source, path: [source], edges: [] },
649
+ ];
650
+ while (queue.length > 0) {
651
+ const current = queue.shift();
652
+ if (current.path.length > maxDepth) {
653
+ continue;
654
+ }
655
+ if (current.nodeId === target) {
656
+ return {
657
+ path: current.path,
658
+ length: current.path.length - 1,
659
+ edges: current.edges,
660
+ found: true,
661
+ };
662
+ }
663
+ const neighbors = adjacency.get(current.nodeId) || [];
664
+ for (const neighbor of neighbors) {
665
+ if (visited.has(neighbor.to)) {
666
+ continue;
667
+ }
668
+ visited.add(neighbor.to);
669
+ queue.push({
670
+ nodeId: neighbor.to,
671
+ path: [...current.path, neighbor.to],
672
+ edges: [...current.edges, { from: current.nodeId, to: neighbor.to, relation: neighbor.relation }],
673
+ });
674
+ }
675
+ }
676
+ return { path: [], length: 0, edges: [], found: false };
677
+ },
678
+ getDiagnostics: () => ({
679
+ storeType: 'graphdb',
680
+ location: sqlitePath,
681
+ exists: fs.existsSync(sqlitePath),
682
+ loaded,
683
+ lastLoadAt: lastLoadAt || undefined,
684
+ lastSaveAt: lastSaveAt || undefined,
685
+ lastError: lastError || undefined,
686
+ capabilityMode: 'ops_capable',
687
+ supportedReadOperations: ['load_snapshot', 'get_node', 'query_nodes', 'query_edges', 'find_path', 'probe_snapshot_metadata'],
688
+ supportedWriteOperations: ['save_snapshot'],
689
+ lastReadPath,
690
+ lastWritePath,
691
+ storageEngine: 'sqlite',
692
+ }),
693
+ close: () => {
694
+ try {
695
+ db?.close?.();
696
+ }
697
+ catch {
698
+ }
699
+ finally {
700
+ db = null;
701
+ closed = true;
702
+ }
703
+ },
704
+ };
705
+ }
706
+ function createGraphDbSnapshotAdapter(options) {
707
+ const rawProvider = options?.provider;
708
+ const provider = normalizeGraphDbSnapshotAdapterProvider(rawProvider);
709
+ if (provider === 'none')
710
+ return null;
711
+ if (provider === 'file') {
712
+ const fileAdapter = createFileGraphDbSnapshotAdapter({
713
+ provider: 'file',
714
+ filePath: options?.filePath,
715
+ id: (options?.id ?? options?.fileAdapterId ?? options?.adapterId),
716
+ });
717
+ if (!fileAdapter)
718
+ return null;
719
+ return fileAdapter;
720
+ }
721
+ if (provider === 'sqlite') {
722
+ const sqliteAdapter = createSqliteGraphDbSnapshotAdapter({
723
+ provider: 'sqlite',
724
+ filePath: options?.filePath,
725
+ sqlitePath: options?.sqlitePath,
726
+ id: (options?.id ?? options?.sqliteAdapterId ?? options?.adapterId),
727
+ });
728
+ if (!sqliteAdapter)
729
+ return null;
730
+ return sqliteAdapter;
731
+ }
732
+ // HTTP adapter — real HTTP communication with graphdb endpoint
733
+ const httpEndpoint = (options?.httpEndpoint ?? options?.baseUrl);
734
+ if (provider === 'http' && httpEndpoint) {
735
+ const endpoint = httpEndpoint.replace(/\/$/, '');
736
+ const snapUrl = `${endpoint}/snapshot`;
737
+ const nodeUrl = `${endpoint}/ops/node`;
738
+ const nodesUrl = `${endpoint}/ops/nodes`;
739
+ const edgesUrl = `${endpoint}/ops/edges`;
740
+ const pathUrl = `${endpoint}/ops/path`;
741
+ let reqCount = 0;
742
+ let okCount = 0;
743
+ let failCount = 0;
744
+ let lastReqId = '';
745
+ let lastErrCode = '';
746
+ let lastStatusCode = 0;
747
+ const doFetch = async (requestUrl, method, body) => {
748
+ reqCount++;
749
+ const headers = body ? { 'Content-Type': 'application/json' } : {};
750
+ const res = await fetch(requestUrl, { method, headers, body: body ? JSON.stringify(body) : undefined });
751
+ lastReqId = res.headers.get('X-Request-Id') || 'http-graphdb-' + reqCount;
752
+ lastStatusCode = Math.max(0, Math.floor(Number(res.status || 0)));
753
+ if (res.ok)
754
+ okCount++;
755
+ else {
756
+ failCount++;
757
+ lastErrCode = res.headers.get('X-Error-Code') || String(res.status);
758
+ }
759
+ return res;
760
+ };
761
+ // Why circuit breaker: The HTTP graphdb adapter can fail transiently (network
762
+ // blips, backend restarts). Without a circuit breaker, every request would
763
+ // attempt the failing backend, causing latency spikes and cascading failures.
764
+ // After `circuitThreshold` consecutive failures, the circuit opens: subsequent
765
+ // requests fail immediately (fail-fast) instead of waiting for timeouts.
766
+ // This is the standard "fail-fast under proven failure" pattern (Netflix Hystrix).
767
+ const circuitThreshold = options?.httpCircuitFailureThreshold ?? options.httpCircuitFailureThreshold ?? 3;
768
+ const circuitCooldown = options?.httpCircuitCooldownMs ?? 8000;
769
+ const adapterId = (options?.id ?? options?.adapterId ?? options?.httpAdapterId ?? 'http-graphdb');
770
+ let consecutiveFailures = 0;
771
+ let circuitState = 'closed';
772
+ let shortCircuitCount = 0;
773
+ let circuitOpenedAt = '';
774
+ const checkCircuit = () => {
775
+ if (circuitState === 'open') {
776
+ shortCircuitCount++;
777
+ throw new Error('graphdb_http_circuit_open');
778
+ }
779
+ };
780
+ return {
781
+ id: adapterId,
782
+ provider: 'http',
783
+ opsCapable: true,
784
+ getCapabilities: () => ({
785
+ snapshotSupported: true, nodeQuerySupported: true,
786
+ edgeQuerySupported: true, pathQuerySupported: true,
787
+ writeSupported: true, serverSideQuery: true,
788
+ mode: 'ops_capable',
789
+ supportedReadOperations: ['load_snapshot', 'get_node', 'query_nodes', 'query_edges', 'find_path'],
790
+ supportedWriteOperations: ['save_snapshot'],
791
+ }),
792
+ loadSnapshot: async () => {
793
+ checkCircuit();
794
+ const res = await doFetch(snapUrl, 'GET');
795
+ if (!res.ok) {
796
+ consecutiveFailures++;
797
+ if (consecutiveFailures >= circuitThreshold) {
798
+ circuitState = 'open';
799
+ circuitOpenedAt = new Date().toISOString();
800
+ }
801
+ throw new Error('graphdb_http_request_failed:' + res.status);
802
+ }
803
+ consecutiveFailures = 0;
804
+ const data = await res.json();
805
+ return (data?.snapshot ?? data);
806
+ },
807
+ saveSnapshot: async (snapshot) => {
808
+ checkCircuit();
809
+ const res = await doFetch(snapUrl, 'POST', { snapshot });
810
+ if (!res.ok) {
811
+ consecutiveFailures++;
812
+ if (consecutiveFailures >= circuitThreshold) {
813
+ circuitState = 'open';
814
+ circuitOpenedAt = new Date().toISOString();
815
+ }
816
+ throw new Error('graphdb_http_request_failed:' + res.status);
817
+ }
818
+ consecutiveFailures = 0;
819
+ },
820
+ getNodeByOps: async (atomId) => {
821
+ checkCircuit();
822
+ const safeId = encodeURIComponent(String(atomId || '').trim());
823
+ if (!safeId) {
824
+ return null;
825
+ }
826
+ const res = await doFetch(`${nodeUrl}/${safeId}`, 'GET');
827
+ if (res.status === 404) {
828
+ return null;
829
+ }
830
+ if (!res.ok) {
831
+ consecutiveFailures++;
832
+ if (consecutiveFailures >= circuitThreshold) {
833
+ circuitState = 'open';
834
+ circuitOpenedAt = new Date().toISOString();
835
+ }
836
+ throw new Error('graphdb_http_request_failed:' + res.status);
837
+ }
838
+ consecutiveFailures = 0;
839
+ const payload = await res.json();
840
+ const node = payload?.node ?? payload?.atom ?? payload?.item ?? payload ?? null;
841
+ if (!node || typeof node !== 'object') {
842
+ return null;
843
+ }
844
+ return node;
845
+ },
846
+ queryNodesByOps: async (filter) => {
847
+ checkCircuit();
848
+ const res = await doFetch(nodesUrl, 'POST', { filter: filter || {} });
849
+ if (!res.ok) {
850
+ consecutiveFailures++;
851
+ if (consecutiveFailures >= circuitThreshold) {
852
+ circuitState = 'open';
853
+ circuitOpenedAt = new Date().toISOString();
854
+ }
855
+ throw new Error('graphdb_http_request_failed:' + res.status);
856
+ }
857
+ consecutiveFailures = 0;
858
+ const payload = await res.json();
859
+ const nodes = payload?.nodes ?? payload?.atoms ?? payload?.items ?? [];
860
+ return Array.isArray(nodes) ? nodes.filter((item) => Boolean(item && typeof item === 'object')) : [];
861
+ },
862
+ queryEdgesByOps: async (filter) => {
863
+ checkCircuit();
864
+ const res = await doFetch(edgesUrl, 'POST', { filter: filter || {} });
865
+ if (!res.ok) {
866
+ consecutiveFailures++;
867
+ if (consecutiveFailures >= circuitThreshold) {
868
+ circuitState = 'open';
869
+ circuitOpenedAt = new Date().toISOString();
870
+ }
871
+ throw new Error('graphdb_http_request_failed:' + res.status);
872
+ }
873
+ consecutiveFailures = 0;
874
+ const payload = await res.json();
875
+ const edges = payload?.edges ?? payload?.relationEdges ?? payload?.items ?? [];
876
+ return Array.isArray(edges) ? edges.filter((item) => Boolean(item && typeof item === 'object')) : [];
877
+ },
878
+ findPathByOps: async (sourceId, targetId, maxDepth) => {
879
+ checkCircuit();
880
+ const res = await doFetch(pathUrl, 'POST', {
881
+ sourceId: String(sourceId || ''),
882
+ targetId: String(targetId || ''),
883
+ maxDepth: typeof maxDepth === 'number' ? Math.max(1, Math.floor(maxDepth)) : undefined,
884
+ });
885
+ if (!res.ok) {
886
+ consecutiveFailures++;
887
+ if (consecutiveFailures >= circuitThreshold) {
888
+ circuitState = 'open';
889
+ circuitOpenedAt = new Date().toISOString();
890
+ }
891
+ throw new Error('graphdb_http_request_failed:' + res.status);
892
+ }
893
+ consecutiveFailures = 0;
894
+ const payload = await res.json();
895
+ const pathResult = payload?.pathResult ?? payload?.result ?? payload;
896
+ if (!pathResult || typeof pathResult !== 'object') {
897
+ return { path: [], length: 0, edges: [], found: false };
898
+ }
899
+ return {
900
+ path: Array.isArray(pathResult.path) ? pathResult.path.map((item) => String(item || '')) : [],
901
+ length: Math.max(0, Math.floor(Number(pathResult.length || 0))),
902
+ edges: Array.isArray(pathResult.edges)
903
+ ? pathResult.edges.map((item) => ({
904
+ from: String(item?.from || ''),
905
+ to: String(item?.to || ''),
906
+ relation: item?.relation ? String(item.relation) : undefined,
907
+ }))
908
+ : [],
909
+ found: pathResult.found === true,
910
+ };
911
+ },
912
+ getDiagnostics: () => ({
913
+ storeType: 'graphdb',
914
+ exists: okCount > 0,
915
+ loaded: okCount > 0,
916
+ location: snapUrl,
917
+ capabilityMode: 'ops_capable',
918
+ supportedReadOperations: ['load_snapshot', 'get_node', 'query_nodes', 'query_edges', 'find_path'],
919
+ supportedWriteOperations: ['save_snapshot'],
920
+ connector: {
921
+ healthStatus: circuitState === 'open' ? 'unavailable' : failCount > 0 ? 'degraded' : 'ready',
922
+ circuitState,
923
+ requestCount: reqCount,
924
+ successCount: okCount,
925
+ failureCount: failCount,
926
+ consecutiveFailures,
927
+ shortCircuitCount,
928
+ circuitOpenedAt: circuitOpenedAt || undefined,
929
+ lastRequestId: lastReqId,
930
+ lastErrorCode: circuitState === 'open' ? 'circuit_open' : (lastErrCode || undefined),
931
+ lastStatusCode: lastStatusCode > 0 ? lastStatusCode : undefined,
932
+ },
933
+ }),
934
+ };
935
+ }
936
+ return null;
937
+ }
938
+ function createKnowledgeGraphStore(o) {
939
+ const backend = normalizeKnowledgeGraphStoreBackend(o.backend);
940
+ const filePath = o.filePath ?? '/tmp/notemd-kg-default.json';
941
+ // Support both old (flat) and new (nested) parameter styles
942
+ const graphdbAdapter = o.graphdb?.adapter ?? o.graphDbAdapter ?? null;
943
+ const graphdbOperationMode = (o.graphdb?.operationMode ?? o.graphDbOperationMode ?? 'snapshot');
944
+ const graphdbFallbackEnabled = (o.graphdb?.fallbackEnabled ?? o.graphDbFallbackEnabled ?? true);
945
+ if (backend === 'memory') {
946
+ // In-memory store for testing
947
+ let memorySnapshot = null;
948
+ return {
949
+ loadSnapshot: async () => memorySnapshot,
950
+ saveSnapshot: async (snapshot) => { memorySnapshot = snapshot; },
951
+ getDiagnostics: () => ({
952
+ storeType: 'memory',
953
+ exists: memorySnapshot !== null,
954
+ loaded: memorySnapshot !== null,
955
+ }),
956
+ close: () => {
957
+ memorySnapshot = null;
958
+ },
959
+ };
960
+ }
961
+ if (backend === 'graphdb') {
962
+ if (graphdbAdapter && typeof graphdbAdapter.loadSnapshot === 'function') {
963
+ const a = graphdbAdapter;
964
+ const opsCapable = hasOpsCapablePath(a);
965
+ const requestMode = normalizeGraphDbStoreOperationMode(graphdbOperationMode);
966
+ const opsState = { probed: false, opsReadUsed: false, opsWriteUsed: false, lastProbeResult: null };
967
+ const queryOpsState = {
968
+ nodeOpsReadCount: 0,
969
+ nodeFallbackCount: 0,
970
+ nodesOpsReadCount: 0,
971
+ nodesFallbackCount: 0,
972
+ edgesOpsReadCount: 0,
973
+ edgesFallbackCount: 0,
974
+ pathOpsReadCount: 0,
975
+ pathFallbackCount: 0,
976
+ };
977
+ const resolveOpsPath = async () => {
978
+ if (!opsCapable || requestMode !== 'ops_preferred')
979
+ return false;
980
+ if (opsState.probed)
981
+ return true;
982
+ if (typeof a.probeSnapshotMetadata === 'function') {
983
+ try {
984
+ opsState.lastProbeResult = await a.probeSnapshotMetadata();
985
+ opsState.probed = true;
986
+ return true;
987
+ }
988
+ catch { /* probe failed, fall through to snapshot */ }
989
+ }
990
+ // No probe method: try ops path anyway, let it fail gracefully
991
+ return true; // allow ops attempt — downgrade on failure
992
+ };
993
+ const loadSnapshotThroughAdapter = async () => {
994
+ if (opsCapable && requestMode === 'ops_preferred') {
995
+ if (typeof a.probeSnapshotMetadata === 'function' && !opsState.probed) {
996
+ try {
997
+ opsState.lastProbeResult = await a.probeSnapshotMetadata();
998
+ opsState.probed = true;
999
+ }
1000
+ catch { /* fall through */ }
1001
+ }
1002
+ if ((opsState.probed || typeof a.probeSnapshotMetadata !== 'function') && typeof a.loadSnapshotByOps === 'function') {
1003
+ try {
1004
+ const result = await a.loadSnapshotByOps();
1005
+ opsState.probed = true;
1006
+ opsState.opsReadUsed = true;
1007
+ return result;
1008
+ }
1009
+ catch {
1010
+ // Downgrade: ops read failed, fall back to snapshot read
1011
+ }
1012
+ }
1013
+ }
1014
+ return a.loadSnapshot();
1015
+ };
1016
+ const saveSnapshotThroughAdapter = async (snapshot) => {
1017
+ if (await resolveOpsPath() && typeof a.saveSnapshotByOps === 'function') {
1018
+ try {
1019
+ await a.saveSnapshotByOps(snapshot);
1020
+ opsState.opsWriteUsed = true;
1021
+ if (typeof a.probeSnapshotMetadata === 'function') {
1022
+ try {
1023
+ opsState.lastProbeResult = await a.probeSnapshotMetadata();
1024
+ }
1025
+ catch { /* ignore */ }
1026
+ }
1027
+ return;
1028
+ }
1029
+ catch {
1030
+ // Downgrade: ops write failed, fall back to snapshot write
1031
+ }
1032
+ }
1033
+ await a.saveSnapshot(snapshot);
1034
+ if (typeof a.probeSnapshotMetadata === 'function') {
1035
+ try {
1036
+ opsState.lastProbeResult = await a.probeSnapshotMetadata();
1037
+ opsState.probed = true;
1038
+ }
1039
+ catch {
1040
+ /* ignore */
1041
+ }
1042
+ }
1043
+ };
1044
+ const resolveOpsCapabilities = () => {
1045
+ const caps = a.getCapabilities?.() ?? {};
1046
+ const supportedReadOperations = Array.isArray(caps.supportedReadOperations)
1047
+ ? caps.supportedReadOperations
1048
+ : [];
1049
+ const supportedWriteOperations = Array.isArray(caps.supportedWriteOperations)
1050
+ ? caps.supportedWriteOperations
1051
+ : [];
1052
+ const nodeQuerySupported = caps.nodeQuerySupported === true || typeof a.getNodeByOps === 'function';
1053
+ const edgeQuerySupported = caps.edgeQuerySupported === true || typeof a.queryEdgesByOps === 'function';
1054
+ const pathQuerySupported = caps.pathQuerySupported === true || typeof a.findPathByOps === 'function';
1055
+ const snapshotSupported = caps.snapshotSupported !== false;
1056
+ const writeSupported = caps.writeSupported !== false;
1057
+ const serverSideQuery = caps.serverSideQuery === true
1058
+ || nodeQuerySupported
1059
+ || edgeQuerySupported
1060
+ || pathQuerySupported;
1061
+ return {
1062
+ snapshotSupported,
1063
+ nodeQuerySupported,
1064
+ edgeQuerySupported,
1065
+ pathQuerySupported,
1066
+ writeSupported,
1067
+ serverSideQuery,
1068
+ mode: String(caps.mode || (opsCapable ? 'ops_capable' : 'snapshot_only')),
1069
+ supportedReadOperations,
1070
+ supportedWriteOperations,
1071
+ };
1072
+ };
1073
+ const querySnapshotForNode = async (atomId) => {
1074
+ const snapshot = await loadSnapshotThroughAdapter();
1075
+ if (!snapshot)
1076
+ return null;
1077
+ return snapshot.atoms.find((atom) => atom.id === atomId || atom.stableKey === atomId) ?? null;
1078
+ };
1079
+ const querySnapshotForNodes = async (filter) => {
1080
+ const snapshot = await loadSnapshotThroughAdapter();
1081
+ if (!snapshot)
1082
+ return [];
1083
+ return filterNodesFromSnapshot(snapshot, filter);
1084
+ };
1085
+ const querySnapshotForEdges = async (filter) => {
1086
+ const snapshot = await loadSnapshotThroughAdapter();
1087
+ if (!snapshot)
1088
+ return [];
1089
+ return filterEdgesFromSnapshot(snapshot, filter);
1090
+ };
1091
+ const querySnapshotForPath = async (sourceId, targetId, maxDepth = 10) => {
1092
+ const snapshot = await loadSnapshotThroughAdapter();
1093
+ if (!snapshot)
1094
+ return { path: [], length: 0, edges: [], found: false };
1095
+ return findPathInSnapshot(snapshot, sourceId, targetId, maxDepth);
1096
+ };
1097
+ return {
1098
+ loadSnapshot: async () => loadSnapshotThroughAdapter(),
1099
+ saveSnapshot: async (snapshot) => saveSnapshotThroughAdapter(snapshot),
1100
+ close: () => {
1101
+ try {
1102
+ a.close?.();
1103
+ }
1104
+ catch {
1105
+ }
1106
+ },
1107
+ getDiagnostics: () => {
1108
+ const diag = a.getDiagnostics?.() ?? {};
1109
+ const caps = resolveOpsCapabilities();
1110
+ const totalQueryOpsReadCount = queryOpsState.nodeOpsReadCount
1111
+ + queryOpsState.nodesOpsReadCount
1112
+ + queryOpsState.edgesOpsReadCount
1113
+ + queryOpsState.pathOpsReadCount;
1114
+ const totalQueryFallbackCount = queryOpsState.nodeFallbackCount
1115
+ + queryOpsState.nodesFallbackCount
1116
+ + queryOpsState.edgesFallbackCount
1117
+ + queryOpsState.pathFallbackCount;
1118
+ return {
1119
+ exists: diag.exists ?? false,
1120
+ loaded: diag.loaded ?? false,
1121
+ backendReady: true,
1122
+ usingFallback: false,
1123
+ adapterId: a.id,
1124
+ ...diag,
1125
+ storeType: 'graphdb',
1126
+ graphDbOperationMode: normalizeGraphDbStoreOperationMode(graphdbOperationMode),
1127
+ fallbackEnabled: graphdbFallbackEnabled,
1128
+ graphDbAdapterCapabilityMode: diag.capabilityMode ?? caps.mode ?? (opsCapable ? 'ops_capable' : 'snapshot_only'),
1129
+ graphDbReadPath: opsState.opsReadUsed ? 'ops' : diag.lastReadPath ?? 'snapshot',
1130
+ graphDbWritePath: opsState.opsWriteUsed ? 'ops' : diag.lastWritePath ?? 'snapshot',
1131
+ graphDbSupportedReadOperations: diag.supportedReadOperations ?? caps.supportedReadOperations,
1132
+ graphDbSupportedWriteOperations: diag.supportedWriteOperations ?? caps.supportedWriteOperations,
1133
+ graphDbLastSnapshotMetadata: opsState.lastProbeResult ?? diag.lastSnapshotMetadata,
1134
+ graphDbQueryPath: totalQueryOpsReadCount > 0 ? 'ops' : 'snapshot',
1135
+ graphDbQueryOpsReadCount: totalQueryOpsReadCount,
1136
+ graphDbQueryFallbackCount: totalQueryFallbackCount,
1137
+ graphDbNodeQuerySupported: caps.nodeQuerySupported,
1138
+ graphDbEdgeQuerySupported: caps.edgeQuerySupported,
1139
+ graphDbPathQuerySupported: caps.pathQuerySupported,
1140
+ };
1141
+ },
1142
+ getCapabilities: () => resolveOpsCapabilities(),
1143
+ getNode: async (atomId) => {
1144
+ if (requestMode === 'ops_preferred' && typeof a.getNodeByOps === 'function') {
1145
+ try {
1146
+ queryOpsState.nodeOpsReadCount += 1;
1147
+ return await a.getNodeByOps(atomId);
1148
+ }
1149
+ catch {
1150
+ queryOpsState.nodeFallbackCount += 1;
1151
+ }
1152
+ }
1153
+ return querySnapshotForNode(atomId);
1154
+ },
1155
+ queryNodes: async (filter) => {
1156
+ if (requestMode === 'ops_preferred' && typeof a.queryNodesByOps === 'function') {
1157
+ try {
1158
+ queryOpsState.nodesOpsReadCount += 1;
1159
+ return await a.queryNodesByOps(filter);
1160
+ }
1161
+ catch {
1162
+ queryOpsState.nodesFallbackCount += 1;
1163
+ }
1164
+ }
1165
+ return querySnapshotForNodes(filter);
1166
+ },
1167
+ queryEdges: async (filter) => {
1168
+ if (requestMode === 'ops_preferred' && typeof a.queryEdgesByOps === 'function') {
1169
+ try {
1170
+ queryOpsState.edgesOpsReadCount += 1;
1171
+ return await a.queryEdgesByOps(filter);
1172
+ }
1173
+ catch {
1174
+ queryOpsState.edgesFallbackCount += 1;
1175
+ }
1176
+ }
1177
+ return querySnapshotForEdges(filter);
1178
+ },
1179
+ findPath: async (sourceId, targetId, maxDepth = 10) => {
1180
+ if (requestMode === 'ops_preferred' && typeof a.findPathByOps === 'function') {
1181
+ try {
1182
+ queryOpsState.pathOpsReadCount += 1;
1183
+ return await a.findPathByOps(sourceId, targetId, maxDepth);
1184
+ }
1185
+ catch {
1186
+ queryOpsState.pathFallbackCount += 1;
1187
+ }
1188
+ }
1189
+ return querySnapshotForPath(sourceId, targetId, maxDepth);
1190
+ },
1191
+ };
1192
+ }
1193
+ // Fail closed when fallback is disabled and adapter is unavailable
1194
+ if (!graphdbFallbackEnabled) {
1195
+ return {
1196
+ loadSnapshot: async () => { throw new Error('graphdb_adapter_unavailable_no_fallback'); },
1197
+ saveSnapshot: async () => { throw new Error('graphdb_adapter_unavailable_no_fallback'); },
1198
+ close: () => { },
1199
+ getDiagnostics: () => ({
1200
+ storeType: 'graphdb',
1201
+ exists: false, loaded: false,
1202
+ backendReady: false,
1203
+ usingFallback: false,
1204
+ fallbackEnabled: false,
1205
+ lastError: 'graphdb_adapter_unavailable_no_fallback',
1206
+ graphDbOperationMode: normalizeGraphDbStoreOperationMode(graphdbOperationMode),
1207
+ graphDbAdapterCapabilityMode: 'unknown',
1208
+ graphDbReadPath: 'fallback',
1209
+ graphDbWritePath: 'fallback',
1210
+ }),
1211
+ };
1212
+ }
1213
+ // Fall back to file store
1214
+ const fallback = new FileBackedKnowledgeGraphStore({ filePath });
1215
+ return {
1216
+ loadSnapshot: () => fallback.loadSnapshot(),
1217
+ saveSnapshot: (snapshot) => fallback.saveSnapshot(snapshot),
1218
+ close: () => {
1219
+ try {
1220
+ graphdbAdapter?.close?.();
1221
+ }
1222
+ catch {
1223
+ }
1224
+ },
1225
+ getDiagnostics: () => ({
1226
+ ...fallback.getDiagnostics(),
1227
+ storeType: 'graphdb',
1228
+ backendReady: false,
1229
+ usingFallback: true,
1230
+ fallbackEnabled: true,
1231
+ fallbackStoreType: 'file',
1232
+ graphDbOperationMode: 'snapshot_only',
1233
+ graphDbAdapterCapabilityMode: 'unknown',
1234
+ graphDbReadPath: 'fallback',
1235
+ graphDbWritePath: 'fallback',
1236
+ }),
1237
+ };
1238
+ }
1239
+ return new FileBackedKnowledgeGraphStore({ filePath });
1240
+ }