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
@@ -43,6 +43,9 @@ const FileProcessor_1 = require("./FileProcessor");
43
43
  const FormulaFixer_1 = require("./FormulaFixer");
44
44
  const MermaidProcessor_1 = require("./MermaidProcessor");
45
45
  const Translator_1 = require("./Translator");
46
+ const SearchManager_1 = require("./search/SearchManager");
47
+ const diagramGenerationService_1 = require("./diagram/diagramGenerationService");
48
+ const LlmProvider_1 = require("./LlmProvider");
46
49
  const types_1 = require("./types");
47
50
  function defaultReporter() {
48
51
  return {
@@ -51,13 +54,16 @@ function defaultReporter() {
51
54
  };
52
55
  }
53
56
  class NotemdService {
54
- constructor(fileProcessor = new FileProcessor_1.FileProcessor(), translator = new Translator_1.Translator(), contentGenerator = new ContentGenerator_1.ContentGenerator(), mermaidProcessor = new MermaidProcessor_1.MermaidProcessor(), formulaFixer = new FormulaFixer_1.FormulaFixer(), duplicateDetector = new DuplicateDetector_1.DuplicateDetector()) {
57
+ constructor(fileProcessor = new FileProcessor_1.FileProcessor(), translator = new Translator_1.Translator(), contentGenerator = new ContentGenerator_1.ContentGenerator(), mermaidProcessor = new MermaidProcessor_1.MermaidProcessor(), formulaFixer = new FormulaFixer_1.FormulaFixer(), duplicateDetector = new DuplicateDetector_1.DuplicateDetector(), llmProviderClient = new LlmProvider_1.LlmProviderClient()) {
58
+ // ── Batch progress tracking ──
59
+ this.batchOperations = new Map();
55
60
  this.fileProcessor = fileProcessor;
56
61
  this.translator = translator;
57
62
  this.contentGenerator = contentGenerator;
58
63
  this.mermaidProcessor = mermaidProcessor;
59
64
  this.formulaFixer = formulaFixer;
60
65
  this.duplicateDetector = duplicateDetector;
66
+ this.llmProviderClient = llmProviderClient;
61
67
  }
62
68
  processFile(request, settings, reporter = defaultReporter(), signal) {
63
69
  return this.fileProcessor.processFile(request, settings, reporter, signal);
@@ -129,6 +135,51 @@ class NotemdService {
129
135
  content: fixed.content,
130
136
  };
131
137
  }
138
+ async batchFixFormulas(folderPath, inPlace = true) {
139
+ const resolvedFolderPath = path.resolve(String(folderPath || '').trim());
140
+ const stats = await fs.promises.stat(resolvedFolderPath);
141
+ if (!stats.isDirectory())
142
+ throw new types_1.ValidationError(`Not a directory: ${resolvedFolderPath}`);
143
+ const files = await this.collectTextFiles(resolvedFolderPath);
144
+ const results = [];
145
+ for (const filePath of files) {
146
+ const result = await this.fixFormulas(filePath, inPlace);
147
+ results.push({
148
+ sourcePath: filePath,
149
+ modified: result.changed,
150
+ replacementCount: result.fixes.length
151
+ });
152
+ }
153
+ return {
154
+ folderPath: resolvedFolderPath,
155
+ totalFiles: files.length,
156
+ fixedFiles: results.filter(r => r.modified).length,
157
+ results
158
+ };
159
+ }
160
+ async collectTextFiles(rootDir) {
161
+ const results = [];
162
+ const queue = [rootDir];
163
+ while (queue.length > 0) {
164
+ const current = queue.shift();
165
+ if (!current)
166
+ continue;
167
+ const entries = await fs.promises.readdir(current, { withFileTypes: true });
168
+ for (const entry of entries) {
169
+ const fullPath = path.join(current, entry.name);
170
+ if (entry.isDirectory()) {
171
+ queue.push(fullPath);
172
+ continue;
173
+ }
174
+ if (!entry.isFile())
175
+ continue;
176
+ if (!constants_1.NOTEMD_SUPPORTED_TEXT_EXTENSIONS.has(path.extname(fullPath).toLowerCase()))
177
+ continue;
178
+ results.push(fullPath);
179
+ }
180
+ }
181
+ return results.sort((a, b) => a.localeCompare(b));
182
+ }
132
183
  async checkDuplicates(filePath) {
133
184
  const resolvedPath = path.resolve(String(filePath || '').trim());
134
185
  if (!resolvedPath) {
@@ -153,26 +204,460 @@ class NotemdService {
153
204
  concepts: Array.from(concepts).sort((a, b) => a.localeCompare(b)),
154
205
  };
155
206
  }
156
- async oneClickExtract(filePath, settings, reporter = defaultReporter(), signal) {
157
- const resolvedPath = path.resolve(String(filePath || '').trim());
158
- if (!resolvedPath) {
207
+ // ── Workflow Pipeline (NoteConnection native) ──
208
+ async runWorkflow(request, settings, reporter = defaultReporter(), signal) {
209
+ const resolvedPath = path.resolve(String(request.filePath || '').trim());
210
+ if (!resolvedPath)
159
211
  throw new types_1.ValidationError('Missing filePath.');
160
- }
161
- const source = await fs.promises.readFile(resolvedPath, 'utf8');
162
- const concepts = Array.from(await this.fileProcessor.extractConceptsFromText(source, settings, reporter, signal)).sort((a, b) => a.localeCompare(b));
163
- const outputFolderPath = path.join(this.resolveKnowledgeBaseRootForFile(resolvedPath), path.basename(resolvedPath, path.extname(resolvedPath)));
212
+ const startedAt = Date.now();
213
+ const stages = [];
214
+ const errors = [];
215
+ const ext = path.extname(resolvedPath);
216
+ const baseName = path.basename(resolvedPath, ext);
217
+ const outputFolderPath = request.outputFolderPath
218
+ ? path.resolve(request.outputFolderPath)
219
+ : path.join(path.dirname(resolvedPath), `${baseName}_notemd_output`);
164
220
  await fs.promises.mkdir(outputFolderPath, { recursive: true });
165
- await this.scaffoldConceptFiles(outputFolderPath, concepts);
166
- const generated = await this.contentGenerator.generateFolderFromTitles(outputFolderPath, settings, reporter, signal);
167
- const mermaid = await this.batchFixMermaid(outputFolderPath, true);
221
+ const enableWikilinks = request.addWikiLinks === true;
222
+ const enableGenerate = !request.skipGenerate;
223
+ const enableMermaid = !request.skipMermaidFix;
224
+ const totalStages = 1 + (enableWikilinks ? 1 : 0) + (enableGenerate ? 1 : 0) + (enableMermaid ? 1 : 0);
225
+ let stageNum = 0;
226
+ // Stage 1: Extract Concepts
227
+ stageNum += 1;
228
+ stages.push({ stage: 'extract-concepts', status: 'running', percent: 0, message: 'Extracting concepts from source...' });
229
+ reporter.report({ type: 'status', message: `[${stageNum}/${totalStages}] Extracting concepts from ${baseName}${ext}`, percent: Math.floor((stageNum - 1) / totalStages * 100), operationId: 'workflow-extract' });
230
+ let concepts = [];
231
+ let sourceContent = '';
232
+ try {
233
+ sourceContent = await fs.promises.readFile(resolvedPath, 'utf8');
234
+ concepts = Array.from(await this.fileProcessor.extractConceptsFromText(sourceContent, settings, reporter, signal)).sort((a, b) => a.localeCompare(b));
235
+ await this.scaffoldConceptFiles(outputFolderPath, concepts);
236
+ stages[0] = { stage: 'extract-concepts', status: 'completed', percent: 100, message: `Extracted ${concepts.length} concepts`, details: { count: concepts.length, concepts } };
237
+ reporter.report({ type: 'status', message: `[${stageNum}/${totalStages}] ✓ Extracted ${concepts.length} concepts`, percent: Math.floor(stageNum / totalStages * 100), operationId: 'workflow-extract' });
238
+ }
239
+ catch (e) {
240
+ const msg = e instanceof Error ? e.message : String(e);
241
+ stages[0] = { stage: 'extract-concepts', status: 'error', percent: 100, message: msg };
242
+ errors.push(`extract-concepts: ${msg}`);
243
+ reporter.report({ type: 'error', message: `[${stageNum}/${totalStages}] ✕ Concept extraction failed: ${msg}`, percent: Math.floor(stageNum / totalStages * 100), operationId: 'workflow-extract' });
244
+ }
245
+ // Stage 2 (optional): Add Wiki-Links to source
246
+ let wikiLinkCount = 0;
247
+ if (enableWikilinks && concepts.length > 0) {
248
+ stageNum += 1;
249
+ stages.push({ stage: 'add-wikilinks', status: 'running', percent: 0, message: 'Adding wiki-links to source...' });
250
+ reporter.report({ type: 'status', message: `[${stageNum}/${totalStages}] Adding [[wiki-links]] for ${concepts.length} concepts`, percent: Math.floor((stageNum - 1) / totalStages * 100), operationId: 'workflow-wikilinks' });
251
+ try {
252
+ const injected = this.fileProcessor.injectWikiLinks(sourceContent, concepts);
253
+ wikiLinkCount = injected.linkCount;
254
+ // Write wikified output: in-place or _wikified copy
255
+ let wikifiedPath;
256
+ if (request.wikiLinksInPlace) {
257
+ wikifiedPath = resolvedPath;
258
+ }
259
+ else {
260
+ wikifiedPath = path.join(path.dirname(resolvedPath), `${baseName}_wikified${ext}`);
261
+ }
262
+ await fs.promises.writeFile(wikifiedPath, injected.content, 'utf8');
263
+ stages.push({ stage: 'add-wikilinks', status: 'completed', percent: 100, message: `Added ${wikiLinkCount} wiki-links`, details: { linkCount: wikiLinkCount, outputPath: wikifiedPath, inPlace: !!request.wikiLinksInPlace } });
264
+ reporter.report({ type: 'status', message: `[${stageNum}/${totalStages}] ✓ Added ${wikiLinkCount} wiki-links → ${path.basename(wikifiedPath)}`, percent: Math.floor(stageNum / totalStages * 100), operationId: 'workflow-wikilinks' });
265
+ }
266
+ catch (e) {
267
+ const msg = e instanceof Error ? e.message : String(e);
268
+ stages.push({ stage: 'add-wikilinks', status: 'error', percent: 100, message: msg });
269
+ errors.push(`add-wikilinks: ${msg}`);
270
+ reporter.report({ type: 'error', message: `[${stageNum}/${totalStages}] ✕ Wiki-link injection failed: ${msg}`, percent: Math.floor(stageNum / totalStages * 100), operationId: 'workflow-wikilinks' });
271
+ }
272
+ }
273
+ else if (enableWikilinks && concepts.length === 0) {
274
+ stageNum += 1;
275
+ stages.push({ stage: 'add-wikilinks', status: 'skipped', percent: 100, message: 'No concepts to link', details: { linkCount: 0 } });
276
+ }
277
+ // Stage 3 (optional): Generate from Titles
278
+ let generated = { totalFiles: 0, generatedFiles: 0, failedFiles: 0, outputs: [] };
279
+ if (enableGenerate) {
280
+ stageNum += 1;
281
+ stages.push({ stage: 'generate-titles', status: 'running', percent: 0, message: 'Generating content from titles...' });
282
+ reporter.report({ type: 'status', message: `[${stageNum}/${totalStages}] Generating content from titles in ${path.basename(outputFolderPath)}`, percent: Math.floor((stageNum - 1) / totalStages * 100), operationId: 'workflow-generate' });
283
+ try {
284
+ generated = await this.contentGenerator.generateFolderFromTitles(outputFolderPath, settings, reporter, signal);
285
+ stages.push({ stage: 'generate-titles', status: 'completed', percent: 100, message: `Generated ${generated.generatedFiles}/${generated.totalFiles} files`, details: { totalFiles: generated.totalFiles, generatedFiles: generated.generatedFiles, failedFiles: generated.failedFiles, outputs: generated.outputs } });
286
+ reporter.report({ type: 'status', message: `[${stageNum}/${totalStages}] ✓ Generated ${generated.generatedFiles}/${generated.totalFiles} files`, percent: Math.floor(stageNum / totalStages * 100), operationId: 'workflow-generate' });
287
+ }
288
+ catch (e) {
289
+ const msg = e instanceof Error ? e.message : String(e);
290
+ stages.push({ stage: 'generate-titles', status: 'error', percent: 100, message: msg });
291
+ errors.push(`generate-titles: ${msg}`);
292
+ reporter.report({ type: 'error', message: `[${stageNum}/${totalStages}] ✕ Content generation failed: ${msg}`, percent: Math.floor(stageNum / totalStages * 100), operationId: 'workflow-generate' });
293
+ }
294
+ }
295
+ // Stage 4 (optional): Mermaid Fix
296
+ let mermaid = { folderPath: outputFolderPath, totalFiles: 0, fixedFiles: 0, results: [] };
297
+ if (enableMermaid) {
298
+ stageNum += 1;
299
+ stages.push({ stage: 'mermaid-fix', status: 'running', percent: 0, message: 'Fixing Mermaid diagrams...' });
300
+ reporter.report({ type: 'status', message: `[${stageNum}/${totalStages}] Fixing Mermaid diagrams in ${path.basename(outputFolderPath)}`, percent: Math.floor((stageNum - 1) / totalStages * 100), operationId: 'workflow-mermaid' });
301
+ try {
302
+ mermaid = await this.batchFixMermaid(outputFolderPath, true);
303
+ stages.push({ stage: 'mermaid-fix', status: 'completed', percent: 100, message: `Fixed ${mermaid.fixedFiles}/${mermaid.totalFiles} files`, details: { fixedFiles: mermaid.fixedFiles, totalFiles: mermaid.totalFiles, results: mermaid.results } });
304
+ reporter.report({ type: 'status', message: `[${stageNum}/${totalStages}] ✓ Fixed ${mermaid.fixedFiles}/${mermaid.totalFiles} Mermaid diagrams`, percent: 100, operationId: 'workflow-mermaid' });
305
+ }
306
+ catch (e) {
307
+ const msg = e instanceof Error ? e.message : String(e);
308
+ stages.push({ stage: 'mermaid-fix', status: 'error', percent: 100, message: msg });
309
+ errors.push(`mermaid-fix: ${msg}`);
310
+ reporter.report({ type: 'error', message: `[${stageNum}/${totalStages}] ✕ Mermaid fix failed: ${msg}`, percent: 100, operationId: 'workflow-mermaid' });
311
+ }
312
+ }
313
+ reporter.report({ type: 'done', message: `Workflow complete for ${baseName}${ext}`, percent: 100, operationId: 'workflow' });
168
314
  return {
169
315
  sourceFilePath: resolvedPath,
170
316
  outputFolderPath,
317
+ stages,
318
+ summary: {
319
+ conceptsExtracted: concepts.length,
320
+ wikiLinksAdded: wikiLinkCount,
321
+ titlesGenerated: generated.generatedFiles,
322
+ titlesFailed: generated.failedFiles,
323
+ mermaidFilesFixed: mermaid.fixedFiles,
324
+ totalElapsedMs: Date.now() - startedAt,
325
+ },
326
+ errors,
327
+ };
328
+ }
329
+ async runBatchWorkflow(request, settings, reporter = defaultReporter(), signal) {
330
+ const resolvedFolderPath = path.resolve(String(request.folderPath || '').trim());
331
+ if (!resolvedFolderPath)
332
+ throw new types_1.ValidationError('Missing folderPath.');
333
+ const startedAt = Date.now();
334
+ const stats = await fs.promises.stat(resolvedFolderPath);
335
+ if (!stats.isDirectory())
336
+ throw new types_1.ValidationError(`Not a directory: ${resolvedFolderPath}`);
337
+ const outputBase = request.outputBasePath
338
+ ? path.resolve(request.outputBasePath)
339
+ : path.join(resolvedFolderPath, '_notemd_batch_output');
340
+ await fs.promises.mkdir(outputBase, { recursive: true });
341
+ reporter.report({ type: 'status', message: `Scanning ${resolvedFolderPath} for matching files...`, percent: 0, operationId: 'batch-workflow' });
342
+ // Collect matching files
343
+ let files = await this.collectTextFiles(resolvedFolderPath);
344
+ // Apply extension filter
345
+ if (request.fileExtensions && request.fileExtensions.length > 0) {
346
+ const exts = new Set(request.fileExtensions.map(e => e.toLowerCase()));
347
+ files = files.filter(f => exts.has(path.extname(f).toLowerCase()));
348
+ }
349
+ // Apply regex pattern filter
350
+ if (request.filePattern) {
351
+ try {
352
+ const regex = new RegExp(request.filePattern, 'i');
353
+ files = files.filter(f => regex.test(path.basename(f)));
354
+ }
355
+ catch {
356
+ throw new types_1.ValidationError(`Invalid regex pattern: ${request.filePattern}`);
357
+ }
358
+ }
359
+ // Limit files
360
+ if (request.maxFiles && request.maxFiles > 0) {
361
+ files = files.slice(0, request.maxFiles);
362
+ }
363
+ const totalFiles = files.length;
364
+ reporter.report({ type: 'status', message: `Found ${totalFiles} matching files. Starting batch workflow...`, percent: 5, operationId: 'batch-workflow' });
365
+ const results = [];
366
+ const errors = [];
367
+ for (let i = 0; i < files.length; i++) {
368
+ if (signal?.aborted)
369
+ break;
370
+ const filePath = files[i];
371
+ const fileBaseName = path.basename(filePath, path.extname(filePath));
372
+ const baseProgress = 5 + Math.floor((i / files.length) * 90);
373
+ reporter.report({
374
+ type: 'status',
375
+ message: `[${i + 1}/${totalFiles}] Processing ${path.basename(filePath)}...`,
376
+ percent: baseProgress,
377
+ operationId: 'batch-workflow'
378
+ });
379
+ try {
380
+ const fileOutputFolder = path.join(outputBase, fileBaseName);
381
+ const result = await this.runWorkflow({
382
+ filePath,
383
+ outputFolderPath: fileOutputFolder,
384
+ language: request.language,
385
+ skipGenerate: request.skipGenerate,
386
+ skipMermaidFix: request.skipMermaidFix,
387
+ }, settings, reporter, signal);
388
+ results.push(result);
389
+ }
390
+ catch (e) {
391
+ const msg = e instanceof Error ? e.message : String(e);
392
+ errors.push({ filePath, error: msg });
393
+ reporter.report({ type: 'error', message: `Failed ${path.basename(filePath)}: ${msg}`, percent: baseProgress, operationId: 'batch-workflow' });
394
+ }
395
+ }
396
+ reporter.report({ type: 'done', message: `Batch workflow complete: ${results.length}/${totalFiles} files processed`, percent: 100, operationId: 'batch-workflow' });
397
+ return {
398
+ folderPath: resolvedFolderPath,
399
+ outputBasePath: outputBase,
400
+ filter: { pattern: request.filePattern, extensions: request.fileExtensions },
401
+ totalFiles,
402
+ completedFiles: results.length,
403
+ failedFiles: errors.length,
404
+ results,
405
+ errors,
406
+ totalElapsedMs: Date.now() - startedAt,
407
+ };
408
+ }
409
+ // Backward-compatible wrapper — uses knowledge-base-root-relative output path
410
+ async oneClickExtract(filePath, settings, reporter = defaultReporter(), signal) {
411
+ const resolvedPath = path.resolve(String(filePath || '').trim());
412
+ const kbRoot = this.resolveKnowledgeBaseRootForFile(resolvedPath);
413
+ const outputFolderPath = path.join(kbRoot, path.basename(resolvedPath, path.extname(resolvedPath)));
414
+ const result = await this.runWorkflow({
415
+ filePath: resolvedPath,
416
+ outputFolderPath,
417
+ }, settings, reporter, signal);
418
+ const findStage = (name) => [...result.stages].reverse().find(s => s.stage === name);
419
+ const conceptsStage = findStage('extract-concepts');
420
+ const concepts = (conceptsStage?.status === 'completed' && conceptsStage?.details?.concepts)
421
+ ? conceptsStage.details.concepts
422
+ : [];
423
+ const genStage = findStage('generate-titles');
424
+ const genDetails = (genStage?.details || {});
425
+ const mermaidStage = findStage('mermaid-fix');
426
+ const mermaidDetails = (mermaidStage?.details || {});
427
+ return {
428
+ sourceFilePath: resolvedPath,
429
+ outputFolderPath: result.outputFolderPath,
171
430
  concepts,
172
- generated,
173
- mermaid,
431
+ generated: {
432
+ totalFiles: genDetails.totalFiles || 0,
433
+ generatedFiles: genDetails.generatedFiles || 0,
434
+ failedFiles: genDetails.failedFiles || 0,
435
+ outputs: genDetails.outputs || [],
436
+ },
437
+ mermaid: {
438
+ folderPath: result.outputFolderPath,
439
+ totalFiles: mermaidDetails.totalFiles || 0,
440
+ fixedFiles: mermaidDetails.fixedFiles || 0,
441
+ results: mermaidDetails.results || [],
442
+ },
443
+ };
444
+ }
445
+ // ── Diagram generation (obsidian-notemd v1.8.4) ──
446
+ async generateDiagram(request, settings, llmInvoker) {
447
+ const content = String(request.content || '').trim();
448
+ if (!content)
449
+ throw new types_1.ValidationError('Missing content for diagram generation.');
450
+ const intent = (request.intent || 'mermaid');
451
+ const compatMode = (request.compatibilityMode ?? settings.experimentalDiagramCompatibilityMode ?? 'legacy-mermaid');
452
+ const errors = [];
453
+ if (llmInvoker) {
454
+ try {
455
+ const result = await (0, diagramGenerationService_1.generateDiagramArtifact)(content, {
456
+ compatibilityMode: compatMode,
457
+ requestedIntent: intent,
458
+ targetLanguage: undefined,
459
+ llmInvoker
460
+ });
461
+ return {
462
+ diagramType: result.spec.intent,
463
+ spec: JSON.stringify(result.spec, null, 2),
464
+ mermaidCode: result.mermaidContent,
465
+ renderErrors: result.renderError ? [result.renderError] : [],
466
+ intent: result.spec.intent,
467
+ generatedAt: new Date().toISOString(),
468
+ };
469
+ }
470
+ catch (error) {
471
+ errors.push(error instanceof Error ? error.message : String(error));
472
+ }
473
+ }
474
+ // Fallback: basic Mermaid fix without LLM
475
+ const fixed = this.mermaidProcessor.fixInMarkdown(content);
476
+ return {
477
+ diagramType: intent,
478
+ spec: content,
479
+ mermaidCode: fixed.changed ? fixed.content : content,
480
+ renderErrors: fixed.changed ? ['Auto-fixed Mermaid syntax errors detected.', ...errors] : errors,
481
+ intent,
482
+ generatedAt: new Date().toISOString(),
483
+ };
484
+ }
485
+ async previewDiagram(request, _settings) {
486
+ const content = String(request.content || '').trim();
487
+ if (!content)
488
+ throw new types_1.ValidationError('Missing content for diagram preview.');
489
+ const fmt = request.format || 'png';
490
+ const fixed = this.mermaidProcessor.fixInMarkdown(content);
491
+ const errors = fixed.changed ? ['Mermaid syntax was auto-fixed in preview.'] : [];
492
+ return {
493
+ format: fmt,
494
+ dataUrl: '',
495
+ errors,
496
+ };
497
+ }
498
+ async exportDiagram(request, _settings) {
499
+ const content = String(request.content || '').trim();
500
+ if (!content)
501
+ throw new types_1.ValidationError('Missing content for diagram export.');
502
+ const outputPath = request.outputPath || '';
503
+ return {
504
+ outputPath,
505
+ format: request.format || 'png',
506
+ size: Buffer.byteLength(content, 'utf8'),
507
+ };
508
+ }
509
+ // ── Search (obsidian-notemd v1.8.4) ──
510
+ async search(request, settings) {
511
+ const query = String(request.query || '').trim();
512
+ if (!query) {
513
+ throw new types_1.ValidationError('Missing query for search.');
514
+ }
515
+ const provider = SearchManager_1.SearchManager.getProvider(request.provider || settings.searchProvider || 'duckduckgo');
516
+ let apiKey;
517
+ if (provider.name === 'tavily') {
518
+ apiKey = settings.tavilyApiKey;
519
+ if (!apiKey)
520
+ throw new types_1.ValidationError('Tavily API key is required in settings.');
521
+ }
522
+ const results = await provider.search({
523
+ query,
524
+ maxResults: request.maxResults ?? settings.tavilyMaxResults ?? 5,
525
+ searchDepth: request.searchDepth ?? settings.tavilySearchDepth ?? 'basic'
526
+ }, apiKey, settings.ddgFetchTimeout ?? 10000);
527
+ return {
528
+ query,
529
+ provider: provider.name,
530
+ results,
531
+ totalResults: results.length,
532
+ searchedAt: new Date().toISOString(),
533
+ };
534
+ }
535
+ // ── LLM diagnostics (obsidian-notemd v1.8.4) ──
536
+ async diagnoseLlmProvider(request, settings, llmCallImpl) {
537
+ const providerName = request.provider || settings.activeProvider || 'unknown';
538
+ const modelName = request.model || settings.providers.find(p => p.name === providerName)?.model || 'unknown';
539
+ const provider = settings.providers.find(p => p.name === providerName);
540
+ if (!provider)
541
+ throw new types_1.ValidationError(`Provider not found: ${providerName}`);
542
+ if (!llmCallImpl)
543
+ throw new types_1.ValidationError('LLM call implementation required for diagnostics.');
544
+ const startMs = Date.now();
545
+ try {
546
+ const { buildDefaultProviderDiagnosticPayload } = await Promise.resolve().then(() => __importStar(require('./providerDiagnostics')));
547
+ const payload = buildDefaultProviderDiagnosticPayload(providerName);
548
+ await llmCallImpl(provider, payload.prompt, payload.content, { ...settings, enableApiErrorDebugMode: true, enableStableApiCall: true });
549
+ return { provider: providerName, model: modelName, status: 'ok', latencyMs: Date.now() - startMs };
550
+ }
551
+ catch (e) {
552
+ const msg = e instanceof Error ? e.message : String(e);
553
+ return { provider: providerName, model: modelName, status: 'error', latencyMs: Date.now() - startMs, error: msg };
554
+ }
555
+ }
556
+ async testLlmConnection(request, settings) {
557
+ const requestedProvider = request && typeof request.provider === 'object'
558
+ ? request.provider
559
+ : null;
560
+ const requestedProviderName = String((request && typeof request.providerName === 'string' ? request.providerName : '') ||
561
+ (requestedProvider && typeof requestedProvider.name === 'string' ? requestedProvider.name : '') ||
562
+ settings.activeProvider ||
563
+ '').trim();
564
+ const persistedProvider = settings.providers.find((provider) => provider.name === requestedProviderName);
565
+ if (!persistedProvider && !requestedProvider) {
566
+ throw new types_1.ValidationError(`Provider not found: ${requestedProviderName || settings.activeProvider}`);
567
+ }
568
+ const resolvedProviderName = String((persistedProvider && persistedProvider.name) ||
569
+ requestedProviderName ||
570
+ settings.activeProvider).trim();
571
+ const provider = {
572
+ ...(persistedProvider || {
573
+ name: resolvedProviderName,
574
+ apiKey: '',
575
+ baseUrl: '',
576
+ model: '',
577
+ temperature: 0.5,
578
+ enabled: true,
579
+ }),
580
+ ...(requestedProvider ? {
581
+ name: resolvedProviderName,
582
+ apiKey: String(requestedProvider.apiKey || '').trim(),
583
+ baseUrl: String(requestedProvider.baseUrl || '').trim(),
584
+ model: String(requestedProvider.model || '').trim(),
585
+ temperature: Number.isFinite(Number(requestedProvider.temperature))
586
+ ? Number(requestedProvider.temperature)
587
+ : Number((persistedProvider && persistedProvider.temperature) || 0.5),
588
+ apiVersion: String(requestedProvider.apiVersion || '').trim(),
589
+ } : {}),
590
+ };
591
+ const result = await this.llmProviderClient.testConnection(provider);
592
+ return {
593
+ success: result.success,
594
+ message: result.message,
595
+ provider: provider.name,
596
+ model: provider.model,
174
597
  };
175
598
  }
599
+ // ── Extract original text (obsidian-notemd v1.8.4) ──
600
+ async extractOriginalText(request, _settings, _reporter = defaultReporter(), _signal) {
601
+ const resolvedPath = path.resolve(String(request.filePath || '').trim());
602
+ if (!resolvedPath)
603
+ throw new types_1.ValidationError('Missing filePath.');
604
+ const source = await fs.promises.readFile(resolvedPath, 'utf8');
605
+ const mergedMode = request.mergedMode ?? false;
606
+ const outputPath = request.outputPath || resolvedPath.replace(/\.md$/, '_original.md');
607
+ const replacedSuffixPath = resolvedPath.replace(/\.md$/, `${request.outputPath ? '' : '_original'}.md`);
608
+ const targetPath = outputPath || replacedSuffixPath;
609
+ if (targetPath !== resolvedPath) {
610
+ await fs.promises.writeFile(targetPath, source, 'utf8');
611
+ }
612
+ return {
613
+ filePath: resolvedPath,
614
+ outputPath: targetPath,
615
+ originalText: mergedMode ? source.replace(/\n/g, ' ').replace(/\[\[([^\]]+)\]\]/g, '$1') : source,
616
+ changed: targetPath !== resolvedPath,
617
+ };
618
+ }
619
+ getBatchProgress(operationId) {
620
+ if (operationId && this.batchOperations.has(operationId)) {
621
+ return this.batchOperations.get(operationId);
622
+ }
623
+ return {
624
+ operationId: operationId || '',
625
+ status: 'completed',
626
+ totalItems: 0,
627
+ completedItems: 0,
628
+ failedItems: 0,
629
+ logs: [],
630
+ percent: 100,
631
+ startedAt: new Date().toISOString(),
632
+ updatedAt: new Date().toISOString(),
633
+ };
634
+ }
635
+ startBatchOperation(operationId, totalItems) {
636
+ const progress = {
637
+ operationId,
638
+ status: 'running',
639
+ totalItems,
640
+ completedItems: 0,
641
+ failedItems: 0,
642
+ logs: [],
643
+ percent: 0,
644
+ startedAt: new Date().toISOString(),
645
+ updatedAt: new Date().toISOString(),
646
+ };
647
+ this.batchOperations.set(operationId, progress);
648
+ return progress;
649
+ }
650
+ updateBatchOperation(operationId, update) {
651
+ const existing = this.batchOperations.get(operationId);
652
+ if (!existing)
653
+ return undefined;
654
+ const updated = { ...existing, ...update, updatedAt: new Date().toISOString() };
655
+ if (updated.failedItems > 0 && updated.status === 'running') {
656
+ updated.status = 'error';
657
+ }
658
+ this.batchOperations.set(operationId, updated);
659
+ return updated;
660
+ }
176
661
  async scaffoldConceptFiles(outputFolderPath, concepts) {
177
662
  for (const concept of concepts) {
178
663
  const safeName = concept.replace(/[\\/:*?"<>|]/g, '').trim();
@@ -33,6 +33,21 @@ const DEFAULT_PROMPTS = {
33
33
  'Do not paraphrase.',
34
34
  'If none found, output: No match found in reference.',
35
35
  ].join('\n'),
36
+ extractOriginalTextMerged: [
37
+ 'For each user query, extract exact matching passages from reference content.',
38
+ 'Combine matching passages into a single merged response.',
39
+ 'Do not paraphrase. If none found, output: No match found in reference.',
40
+ ].join('\n'),
41
+ searchResearch: [
42
+ 'Research the given topic using web search results.',
43
+ 'Summarize findings in structured markdown with citations.',
44
+ 'Include key facts, different perspectives, and references.',
45
+ ].join('\n'),
46
+ ragSufficiencyJudge: [
47
+ 'Review whether the supplied RAG context is sufficient to answer the user query.',
48
+ 'Use only the supplied context and do not write the final answer.',
49
+ 'Return exactly one JSON object with status, score, reasons, and degradationState.',
50
+ ].join('\n'),
36
51
  };
37
52
  exports.DEFAULT_PROMPTS = DEFAULT_PROMPTS;
38
53
  function safeReplace(template, replacements) {