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.
- package/LICENSE +674 -21
- package/README.md +258 -64
- package/dist/src/agent_workspace.contract.parity.test.js +475 -0
- package/dist/src/agent_workspace.frontend.test.js +9989 -0
- package/dist/src/agent_workspace.locale.contract.test.js +95 -0
- package/dist/src/agent_workspace.runtime.behavior.test.js +5072 -0
- package/dist/src/copy.assets.contract.test.js +130 -0
- package/dist/src/core/PathBridge.js +28 -3
- package/dist/src/export/WorkspaceExportBundle.js +1130 -0
- package/dist/src/export/WorkspaceExportBundle.test.js +1480 -0
- package/dist/src/export/index.js +18 -0
- package/dist/src/export/types.js +2 -0
- package/dist/src/fixrisk.issue.verifier.contract.test.js +3 -1
- package/dist/src/foundation.ann.runtime.contract.test.js +81 -0
- package/dist/src/foundation.release.evidence.contract.test.js +406 -0
- package/dist/src/foundation.sqlite.runtime.contract.test.js +82 -0
- package/dist/src/frontend/README.md +258 -64
- package/dist/src/frontend/agent_workspace.js +5353 -0
- package/dist/src/frontend/agent_workspace_runtime.js +4434 -0
- package/dist/src/frontend/analysis.js +31 -4
- package/dist/src/frontend/app.js +3597 -141
- package/dist/src/frontend/focus_mode_interactions.js +33 -0
- package/dist/src/frontend/godot_future_path_renderer.js +540 -0
- package/dist/src/frontend/godot_tree_interactions.js +178 -0
- package/dist/src/frontend/graph_state.mjs +105 -0
- package/dist/src/frontend/hosted_future_path_runtime.js +157 -0
- package/dist/src/frontend/i18n.mjs +186 -0
- package/dist/src/frontend/index.html +641 -266
- package/dist/src/frontend/layout_gpu.js +12 -4
- package/dist/src/frontend/locales/en.json +721 -5
- package/dist/src/frontend/locales/zh.json +721 -5
- package/dist/src/frontend/main.mjs +60 -0
- package/dist/src/frontend/markdown_runtime.js +827 -0
- package/dist/src/frontend/notemd.css +49 -0
- package/dist/src/frontend/notemd.html +7 -1
- package/dist/src/frontend/notemd.js +64 -0
- package/dist/src/frontend/path.html +107 -0
- package/dist/src/frontend/path_app.js +2189 -150
- package/dist/src/frontend/path_layout.mjs +143 -0
- package/dist/src/frontend/path_mermaid_utils.mjs +108 -0
- package/dist/src/frontend/path_modules_bridge.js +486 -0
- package/dist/src/frontend/path_state.mjs +118 -0
- package/dist/src/frontend/path_styles.css +146 -0
- package/dist/src/frontend/path_worker_bridge.mjs +85 -0
- package/dist/src/frontend/reader.js +522 -27
- package/dist/src/frontend/runtime_bridge.js +67 -54
- package/dist/src/frontend/runtime_bridge.mjs +279 -0
- package/dist/src/frontend/settings.js +130 -12
- package/dist/src/frontend/simulationWorker.js +241 -6
- package/dist/src/frontend/source_manager.js +190 -21
- package/dist/src/frontend/styles.css +2853 -72
- package/dist/src/frontend/workbench_state.mjs +101 -0
- package/dist/src/frontend/workspace_panes.js +10168 -0
- package/dist/src/frontend.locale.contract.test.js +62 -0
- package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
- package/dist/src/indexing/IndexLifecycle.js +195 -0
- package/dist/src/indexing/IndexLifecycle.test.js +49 -0
- package/dist/src/indexing/SegmentBuilder.js +64 -0
- package/dist/src/indexing/UnitBuilder.js +48 -0
- package/dist/src/indexing/types.js +2 -0
- package/dist/src/knowledge.api.contract.test.js +170 -0
- package/dist/src/learning/KnowledgeLearningPlatform.js +10386 -0
- package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +327 -0
- package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
- package/dist/src/learning/KnowledgeLearningPlatform.test.js +2971 -0
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2974 -0
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3928 -0
- package/dist/src/learning/answerReleaseReview.js +4319 -0
- package/dist/src/learning/answerReleaseReview.test.js +2888 -0
- package/dist/src/learning/api.js +2 -0
- package/dist/src/learning/conversationComposer.js +1480 -0
- package/dist/src/learning/conversationComposer.test.js +1817 -0
- package/dist/src/learning/domains/ConversationManager.js +53 -0
- package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
- package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
- package/dist/src/learning/domains/MasteryEngine.js +387 -0
- package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
- package/dist/src/learning/domains/QualityEvaluator.js +307 -0
- package/dist/src/learning/domains/TutorRouter.js +313 -0
- package/dist/src/learning/domains/index.js +33 -0
- package/dist/src/learning/domains/types.js +7 -0
- package/dist/src/learning/errors.js +29 -0
- package/dist/src/learning/evidenceContextAssembler.js +1176 -0
- package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
- package/dist/src/learning/graphContextAssembler.js +870 -0
- package/dist/src/learning/graphContextAssembler.test.js +1033 -0
- package/dist/src/learning/index.js +28 -0
- package/dist/src/learning/queryBackend.js +1898 -0
- package/dist/src/learning/queryBackend.test.js +955 -0
- package/dist/src/learning/ragContextPack.js +257 -0
- package/dist/src/learning/ragContextPack.test.js +160 -0
- package/dist/src/learning/ragPublicText.js +38 -0
- package/dist/src/learning/ragSufficiencyJudge.js +161 -0
- package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
- package/dist/src/learning/ragSufficiencyProviderJudge.js +227 -0
- package/dist/src/learning/ragSufficiencyProviderJudge.test.js +156 -0
- package/dist/src/learning/requestNormalization.js +198 -0
- package/dist/src/learning/runtimeCapability.js +4677 -0
- package/dist/src/learning/runtimeCapability.test.js +3635 -0
- package/dist/src/learning/store.js +1240 -0
- package/dist/src/learning/store.test.js +1126 -0
- package/dist/src/learning/tutorAdapter.js +2 -0
- package/dist/src/learning/types.js +2 -0
- package/dist/src/learning/vectorAccelerationAdapter.js +942 -0
- package/dist/src/learning/vectorAccelerationAdapter.test.js +382 -0
- package/dist/src/lfs.asset.policy.contract.test.js +153 -0
- package/dist/src/license.policy.contract.test.js +66 -0
- package/dist/src/memory/MemoryGovernance.js +74 -0
- package/dist/src/memory/MemoryGovernance.test.js +46 -0
- package/dist/src/memory/types.js +2 -0
- package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
- package/dist/src/middleware/auth.js +17 -0
- package/dist/src/middleware/body-parser.js +45 -0
- package/dist/src/middleware/cors.js +44 -0
- package/dist/src/middleware/index.js +21 -0
- package/dist/src/middleware/request-trace.js +96 -0
- package/dist/src/notemd/AppConfigToml.js +6 -4
- package/dist/src/notemd/MermaidProcessor.js +400 -50
- package/dist/src/notemd/NotemdService.js +498 -13
- package/dist/src/notemd/PromptManager.js +15 -0
- package/dist/src/notemd/cli/commands.js +357 -0
- package/dist/src/notemd/cli/dispatcher.js +225 -0
- package/dist/src/notemd/cli/index.js +169 -0
- package/dist/src/notemd/cli/parser.js +68 -0
- package/dist/src/notemd/cli/types.js +2 -0
- package/dist/src/notemd/constants.js +43 -0
- package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
- package/dist/src/notemd/diagram/diagramSpec.js +79 -0
- package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
- package/dist/src/notemd/diagram/intent.js +95 -0
- package/dist/src/notemd/diagram/planner.js +71 -0
- package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
- package/dist/src/notemd/diagram/types.js +18 -0
- package/dist/src/notemd/index.js +26 -0
- package/dist/src/notemd/operations/capabilityManifest.js +23 -0
- package/dist/src/notemd/operations/cliContracts.js +17 -0
- package/dist/src/notemd/operations/configProfileCommands.js +85 -0
- package/dist/src/notemd/operations/registry.contract.test.js +95 -0
- package/dist/src/notemd/operations/registry.js +991 -0
- package/dist/src/notemd/operations/types.js +2 -0
- package/dist/src/notemd/providerDiagnostics.js +220 -0
- package/dist/src/notemd/providerProfiles.js +42 -0
- package/dist/src/notemd/providerTemplates.js +231 -0
- package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
- package/dist/src/notemd/search/SearchManager.js +13 -0
- package/dist/src/notemd/search/SearchProvider.js +2 -0
- package/dist/src/notemd/search/TavilyProvider.js +44 -0
- package/dist/src/notemd.agent.manifest.test.js +85 -0
- package/dist/src/notemd.api.contract.test.js +14 -1
- package/dist/src/notemd.app_config_toml.test.js +2 -1
- package/dist/src/notemd.batch.workflow.test.js +117 -0
- package/dist/src/notemd.cli.e2e.test.js +136 -0
- package/dist/src/notemd.core.test.js +51 -0
- package/dist/src/notemd.diagram.pipeline.test.js +233 -0
- package/dist/src/notemd.providerTemplates.test.js +34 -0
- package/dist/src/notemd.server.integration.test.js +143 -35
- package/dist/src/notemd.workflow.pipeline.test.js +162 -0
- package/dist/src/pathbridge.handshake.contract.test.js +16 -2
- package/dist/src/pathmode.background.contract.test.js +69 -0
- package/dist/src/pathmode.settings.api.contract.test.js +9 -0
- package/dist/src/pkg.sidecar.contract.test.js +9 -3
- package/dist/src/platform/ExportProfile.js +58 -0
- package/dist/src/platform/PlatformCapabilities.js +45 -0
- package/dist/src/platform/PlatformCapabilities.test.js +30 -0
- package/dist/src/platform/RenderMaterializer.js +33 -0
- package/dist/src/platform/RenderMaterializer.test.js +32 -0
- package/dist/src/query_backend.external_http.integration.test.js +410 -0
- package/dist/src/reader_renderer.js +404 -3
- package/dist/src/reader_renderer.test.js +87 -0
- package/dist/src/release.godot.mirror.contract.test.js +73 -0
- package/dist/src/resources/ResourceRegistry.js +223 -0
- package/dist/src/resources/ResourceRegistry.test.js +61 -0
- package/dist/src/resources/types.js +2 -0
- package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
- package/dist/src/routes/data.js +267 -0
- package/dist/src/routes/diagnostics.js +51 -0
- package/dist/src/routes/index.js +23 -0
- package/dist/src/routes/knowledge.js +968 -0
- package/dist/src/routes/markdown.js +287 -0
- package/dist/src/routes/notemd.js +565 -0
- package/dist/src/routes/registry.contract.test.js +130 -0
- package/dist/src/routes/render.js +285 -0
- package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
- package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
- package/dist/src/routes/settings.js +6 -0
- package/dist/src/routes/staticFiles.js +94 -0
- package/dist/src/routes/types.js +2 -0
- package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
- package/dist/src/server.js +11090 -1549
- package/dist/src/server.migration.test.js +193 -21
- package/dist/src/server.port.fallback.contract.test.js +63 -0
- package/dist/src/session/SessionStateStore.js +81 -0
- package/dist/src/session/SessionStateStore.test.js +58 -0
- package/dist/src/session/types.js +2 -0
- package/dist/src/settings.runtime.contract.test.js +50 -0
- package/dist/src/shared/types.contract.test.js +107 -0
- package/dist/src/shared/types.js +22 -0
- package/dist/src/sidecar.replacement.boundary.contract.test.js +128 -0
- package/dist/src/sidecar.supply.readiness.contract.test.js +144 -0
- package/dist/src/source_manager.loadflow.test.js +46 -0
- package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
- package/dist/src/tauri.frontend.build.contract.test.js +60 -0
- package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
- package/dist/src/utils/RuntimePaths.js +4 -13
- package/dist/src/utils/platform.js +153 -0
- package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
- package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
- package/dist/src/workflows/types.js +2 -0
- package/dist/src/workspace/WorkspaceRegistry.js +122 -0
- package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
- package/dist/src/workspace/types.js +2 -0
- package/package.json +61 -10
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Diagram Pipeline unit tests — validates intent inference, planner,
|
|
5
|
+
* spec validation, and response parser (all pure functions).
|
|
6
|
+
*/
|
|
7
|
+
const intent_1 = require("./notemd/diagram/intent");
|
|
8
|
+
const planner_1 = require("./notemd/diagram/planner");
|
|
9
|
+
const diagramSpec_1 = require("./notemd/diagram/diagramSpec");
|
|
10
|
+
const diagramSpecResponseParser_1 = require("./notemd/diagram/diagramSpecResponseParser");
|
|
11
|
+
const diagramSpecPrompt_1 = require("./notemd/diagram/prompts/diagramSpecPrompt");
|
|
12
|
+
// ── Intent Inference ──
|
|
13
|
+
describe('Diagram Intent Inference', () => {
|
|
14
|
+
test('empty content defaults to mindmap', () => {
|
|
15
|
+
const result = (0, intent_1.inferDiagramIntent)('');
|
|
16
|
+
expect(result.intent).toBe('mindmap');
|
|
17
|
+
expect(result.confidence).toBeLessThan(0.5);
|
|
18
|
+
});
|
|
19
|
+
test('whitespace-only defaults to mindmap', () => {
|
|
20
|
+
const result = (0, intent_1.inferDiagramIntent)(' \n ');
|
|
21
|
+
expect(result.intent).toBe('mindmap');
|
|
22
|
+
});
|
|
23
|
+
test('data table with numeric values infers dataChart', () => {
|
|
24
|
+
const md = [
|
|
25
|
+
'| Metric | Value |',
|
|
26
|
+
'| ------ | ----- |',
|
|
27
|
+
'| CPU | 85% |',
|
|
28
|
+
'| Memory | 72% |',
|
|
29
|
+
'Monthly growth trend: 12% increase.',
|
|
30
|
+
].join('\n');
|
|
31
|
+
const result = (0, intent_1.inferDiagramIntent)(md);
|
|
32
|
+
expect(result.intent).toBe('dataChart');
|
|
33
|
+
expect(result.confidence).toBeGreaterThan(0.8);
|
|
34
|
+
});
|
|
35
|
+
test('request-response vocabulary infers sequence', () => {
|
|
36
|
+
const md = 'Client sends request to server. Server processes and returns response. Gateway routes the request. Service handles the response.';
|
|
37
|
+
const result = (0, intent_1.inferDiagramIntent)(md);
|
|
38
|
+
expect(result.intent).toBe('sequence');
|
|
39
|
+
});
|
|
40
|
+
test('arrow markers infers sequence', () => {
|
|
41
|
+
const result = (0, intent_1.inferDiagramIntent)('User -> API -> Database -> Response');
|
|
42
|
+
expect(result.intent).toBe('sequence');
|
|
43
|
+
});
|
|
44
|
+
test('database schema vocabulary infers erDiagram', () => {
|
|
45
|
+
const md = 'The entity has a primary key. The table references a foreign key. The schema defines the column.';
|
|
46
|
+
const result = (0, intent_1.inferDiagramIntent)(md);
|
|
47
|
+
expect(result.intent).toBe('erDiagram');
|
|
48
|
+
});
|
|
49
|
+
test('state transition vocabulary infers stateDiagram', () => {
|
|
50
|
+
const md = 'The state machine transitions from pending to running. Completed state follows. Failed state triggers retry.';
|
|
51
|
+
const result = (0, intent_1.inferDiagramIntent)(md);
|
|
52
|
+
expect(result.intent).toBe('stateDiagram');
|
|
53
|
+
});
|
|
54
|
+
test('workflow/process vocabulary infers flowchart', () => {
|
|
55
|
+
const md = 'Step 1: Validate input. If valid then process, else stop. Continue to next step.';
|
|
56
|
+
const result = (0, intent_1.inferDiagramIntent)(md);
|
|
57
|
+
expect(result.intent).toBe('flowchart');
|
|
58
|
+
});
|
|
59
|
+
test('numbered steps infers flowchart', () => {
|
|
60
|
+
const md = '1. First step\n2. Second step\n3. Third step';
|
|
61
|
+
const result = (0, intent_1.inferDiagramIntent)(md);
|
|
62
|
+
expect(result.intent).toBe('flowchart');
|
|
63
|
+
});
|
|
64
|
+
test('concept map keywords infers canvasMap', () => {
|
|
65
|
+
const md = 'This is a concept map showing the knowledge graph of spatial clusters.';
|
|
66
|
+
const result = (0, intent_1.inferDiagramIntent)(md);
|
|
67
|
+
expect(result.intent).toBe('canvasMap');
|
|
68
|
+
});
|
|
69
|
+
test('general content defaults to mindmap', () => {
|
|
70
|
+
const result = (0, intent_1.inferDiagramIntent)('The quick brown fox jumps over the lazy dog.');
|
|
71
|
+
expect(result.intent).toBe('mindmap');
|
|
72
|
+
expect(result.confidence).toBeGreaterThan(0.4);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
// ── Planner ──
|
|
76
|
+
describe('Diagram Planner', () => {
|
|
77
|
+
test('best-fit mode routes dataChart to vega-lite', () => {
|
|
78
|
+
const md = '| A | B |\n| - | - |\n| 1 | 2 |\n| 3 | 4 |\nchart';
|
|
79
|
+
const plan = (0, planner_1.buildDiagramPlan)(md, { compatibilityMode: 'best-fit' });
|
|
80
|
+
expect(plan.renderTarget).toBe('vega-lite');
|
|
81
|
+
});
|
|
82
|
+
test('best-fit mode routes sequence to mermaid', () => {
|
|
83
|
+
const md = 'Client sends request. Server returns response. Gateway routes. Service processes.';
|
|
84
|
+
const plan = (0, planner_1.buildDiagramPlan)(md, { compatibilityMode: 'best-fit' });
|
|
85
|
+
expect(plan.intent).toBe('sequence');
|
|
86
|
+
expect(plan.renderTarget).toBe('mermaid');
|
|
87
|
+
expect(plan.mermaidDiagramType).toBe('sequenceDiagram');
|
|
88
|
+
});
|
|
89
|
+
test('legacy-mermaid mode forces mermaid render target', () => {
|
|
90
|
+
const md = '| A | B |\n| - | - |\n| 1 | 2 |\n| 3 | 4 |\nchart share distribution';
|
|
91
|
+
const plan = (0, planner_1.buildDiagramPlan)(md, { compatibilityMode: 'legacy-mermaid' });
|
|
92
|
+
expect(plan.renderTarget).toBe('mermaid');
|
|
93
|
+
expect(plan.legacyCompatibilityMode).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
test('explicit intent overrides inference', () => {
|
|
96
|
+
const plan = (0, planner_1.buildDiagramPlan)('any content', {
|
|
97
|
+
requestedIntent: 'flowchart',
|
|
98
|
+
compatibilityMode: 'best-fit'
|
|
99
|
+
});
|
|
100
|
+
expect(plan.intent).toBe('flowchart');
|
|
101
|
+
expect(plan.confidence).toBeGreaterThan(0.9);
|
|
102
|
+
});
|
|
103
|
+
test('mindmap intent resolves to mindmap mermaid type', () => {
|
|
104
|
+
const plan = (0, planner_1.buildDiagramPlan)('# Just a regular note about topics', { compatibilityMode: 'best-fit' });
|
|
105
|
+
expect(plan.intent).toBe('mindmap');
|
|
106
|
+
expect(plan.mermaidDiagramType).toBe('mindmap');
|
|
107
|
+
});
|
|
108
|
+
test('fallback targets include html for dataChart (non-mermaid)', () => {
|
|
109
|
+
const md = '| A | B |\n| - | - |\n| 1 | 2 |\n| 3 | 4 |\nchart share breakdown';
|
|
110
|
+
const plan = (0, planner_1.buildDiagramPlan)(md, { compatibilityMode: 'best-fit' });
|
|
111
|
+
expect(plan.renderTarget).toBe('vega-lite');
|
|
112
|
+
// dataChart has no mermaidDiagramType, so only html is a valid fallback
|
|
113
|
+
expect(plan.fallbackTargets).toContain('html');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
// ── Spec Validation ──
|
|
117
|
+
describe('Diagram Spec Validation', () => {
|
|
118
|
+
const validMindmapSpec = {
|
|
119
|
+
intent: 'mindmap',
|
|
120
|
+
title: 'Test Diagram',
|
|
121
|
+
nodes: [{ id: 'n1', label: 'Node 1' }, { id: 'n2', label: 'Node 2' }],
|
|
122
|
+
edges: [{ from: 'n1', to: 'n2' }],
|
|
123
|
+
};
|
|
124
|
+
test('valid mindmap spec passes validation', () => {
|
|
125
|
+
const result = (0, diagramSpec_1.validateDiagramSpec)(validMindmapSpec);
|
|
126
|
+
expect(result.valid).toBe(true);
|
|
127
|
+
expect(result.errors).toHaveLength(0);
|
|
128
|
+
});
|
|
129
|
+
test('missing title fails validation', () => {
|
|
130
|
+
const result = (0, diagramSpec_1.validateDiagramSpec)({ ...validMindmapSpec, title: '' });
|
|
131
|
+
expect(result.valid).toBe(false);
|
|
132
|
+
expect(result.errors.some(e => e.includes('title'))).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
test('duplicate node ids fail validation', () => {
|
|
135
|
+
const result = (0, diagramSpec_1.validateDiagramSpec)({
|
|
136
|
+
...validMindmapSpec,
|
|
137
|
+
nodes: [{ id: 'n1', label: 'A' }, { id: 'n1', label: 'B' }],
|
|
138
|
+
});
|
|
139
|
+
expect(result.valid).toBe(false);
|
|
140
|
+
expect(result.errors.some(e => e.includes('duplicate'))).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
test('edge to missing node fails validation', () => {
|
|
143
|
+
const result = (0, diagramSpec_1.validateDiagramSpec)({
|
|
144
|
+
...validMindmapSpec,
|
|
145
|
+
edges: [{ from: 'n1', to: 'missing' }],
|
|
146
|
+
});
|
|
147
|
+
expect(result.valid).toBe(false);
|
|
148
|
+
expect(result.errors.some(e => e.includes('missing'))).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
test('unsupported intent fails validation', () => {
|
|
151
|
+
const result = (0, diagramSpec_1.validateDiagramSpec)({ ...validMindmapSpec, intent: 'bogus' });
|
|
152
|
+
expect(result.valid).toBe(false);
|
|
153
|
+
expect(result.errors.some(e => e.includes('unsupported'))).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
test('assertValidDiagramSpec throws on invalid spec', () => {
|
|
156
|
+
expect(() => (0, diagramSpec_1.assertValidDiagramSpec)({ ...validMindmapSpec, title: '' })).toThrow();
|
|
157
|
+
});
|
|
158
|
+
test('dataChart intent requires data series', () => {
|
|
159
|
+
const result = (0, diagramSpec_1.validateDiagramSpec)({ ...validMindmapSpec, intent: 'dataChart', nodes: [] });
|
|
160
|
+
expect(result.valid).toBe(false);
|
|
161
|
+
expect(result.errors.some(e => e.includes('data series'))).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
// ── Response Parser ──
|
|
165
|
+
describe('Diagram Spec Response Parser', () => {
|
|
166
|
+
test('parses valid JSON DiagramSpec', () => {
|
|
167
|
+
const json = JSON.stringify({
|
|
168
|
+
intent: 'mindmap',
|
|
169
|
+
title: 'Test',
|
|
170
|
+
nodes: [{ id: 'a', label: 'A' }],
|
|
171
|
+
});
|
|
172
|
+
const spec = (0, diagramSpecResponseParser_1.parseDiagramSpecResponse)(json);
|
|
173
|
+
expect(spec.intent).toBe('mindmap');
|
|
174
|
+
expect(spec.title).toBe('Test');
|
|
175
|
+
expect(spec.nodes).toHaveLength(1);
|
|
176
|
+
expect(spec.nodes[0].id).toBe('a');
|
|
177
|
+
});
|
|
178
|
+
test('strips code fences from response', () => {
|
|
179
|
+
const json = '```json\n' + JSON.stringify({
|
|
180
|
+
intent: 'flowchart',
|
|
181
|
+
title: 'Test',
|
|
182
|
+
nodes: [{ id: 'a', label: 'A' }],
|
|
183
|
+
}) + '\n```';
|
|
184
|
+
const spec = (0, diagramSpecResponseParser_1.parseDiagramSpecResponse)(json);
|
|
185
|
+
expect(spec.intent).toBe('flowchart');
|
|
186
|
+
expect(spec.title).toBe('Test');
|
|
187
|
+
});
|
|
188
|
+
test('handles diagramSpec wrapper key', () => {
|
|
189
|
+
const json = JSON.stringify({
|
|
190
|
+
diagramSpec: {
|
|
191
|
+
intent: 'sequence',
|
|
192
|
+
title: 'Test',
|
|
193
|
+
nodes: [{ id: 'a', label: 'A' }],
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
const spec = (0, diagramSpecResponseParser_1.parseDiagramSpecResponse)(json);
|
|
197
|
+
expect(spec.intent).toBe('sequence');
|
|
198
|
+
});
|
|
199
|
+
test('throws on non-JSON input', () => {
|
|
200
|
+
expect(() => (0, diagramSpecResponseParser_1.parseDiagramSpecResponse)('not json at all')).toThrow();
|
|
201
|
+
});
|
|
202
|
+
test('normalizes edge fields (source→from, target→to)', () => {
|
|
203
|
+
const json = JSON.stringify({
|
|
204
|
+
intent: 'mindmap', title: 'E',
|
|
205
|
+
nodes: [{ id: 'a', label: 'A' }, { id: 'b', label: 'B' }],
|
|
206
|
+
edges: [{ source: 'a', target: 'b', label: 'connects' }],
|
|
207
|
+
});
|
|
208
|
+
const spec = (0, diagramSpecResponseParser_1.parseDiagramSpecResponse)(json);
|
|
209
|
+
expect(spec.edges[0].from).toBe('a');
|
|
210
|
+
expect(spec.edges[0].to).toBe('b');
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
// ── Prompt Builder ──
|
|
214
|
+
describe('Diagram Spec Prompt', () => {
|
|
215
|
+
test('includes required intent in output', () => {
|
|
216
|
+
const prompt = (0, diagramSpecPrompt_1.buildDiagramSpecPrompt)({ requiredIntent: 'flowchart' });
|
|
217
|
+
expect(prompt).toContain('REQUIRED');
|
|
218
|
+
expect(prompt).toContain('flowchart');
|
|
219
|
+
});
|
|
220
|
+
test('includes preferred intent', () => {
|
|
221
|
+
const prompt = (0, diagramSpecPrompt_1.buildDiagramSpecPrompt)({ preferredIntent: 'sequence' });
|
|
222
|
+
expect(prompt).toContain('Preferred');
|
|
223
|
+
expect(prompt).toContain('sequence');
|
|
224
|
+
});
|
|
225
|
+
test('includes target language', () => {
|
|
226
|
+
const prompt = (0, diagramSpecPrompt_1.buildDiagramSpecPrompt)({ targetLanguage: 'zh' });
|
|
227
|
+
expect(prompt).toContain('zh');
|
|
228
|
+
});
|
|
229
|
+
test('output rules forbid code fences', () => {
|
|
230
|
+
const prompt = (0, diagramSpecPrompt_1.buildDiagramSpecPrompt)();
|
|
231
|
+
expect(prompt).toContain('Do not wrap');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const providerTemplates_1 = require("./notemd/providerTemplates");
|
|
4
|
+
const constants_1 = require("./notemd/constants");
|
|
5
|
+
describe('notemd provider templates', () => {
|
|
6
|
+
test('exposes curated templates inspired by Cherry Studio and DeepTutor', () => {
|
|
7
|
+
expect(providerTemplates_1.NOTEMD_PROVIDER_TEMPLATES.length).toBeGreaterThanOrEqual(6);
|
|
8
|
+
expect((0, providerTemplates_1.getNotemdProviderTemplate)('openai-official')?.providerName).toBe('OpenAI');
|
|
9
|
+
expect((0, providerTemplates_1.getNotemdProviderTemplate)('lmstudio-local')?.inspiredBy).toContain('DeepTutor');
|
|
10
|
+
});
|
|
11
|
+
test('applies a template to the matching provider and switches activeProvider', () => {
|
|
12
|
+
const next = (0, providerTemplates_1.applyProviderTemplateToSettings)(constants_1.DEFAULT_SETTINGS, 'openrouter-gateway');
|
|
13
|
+
const provider = next.providers.find((item) => item.name === 'OpenRouter');
|
|
14
|
+
expect(next.activeProvider).toBe('OpenRouter');
|
|
15
|
+
expect(provider).toBeTruthy();
|
|
16
|
+
expect(provider?.baseUrl).toContain('openrouter.ai');
|
|
17
|
+
expect(provider?.model).toContain('openai/');
|
|
18
|
+
});
|
|
19
|
+
test('merges provider_templates into the notemd TOML section without dropping existing overrides', () => {
|
|
20
|
+
const next = (0, providerTemplates_1.mergeProviderTemplatesIntoNotemdSection)({
|
|
21
|
+
active_provider: 'DeepSeek',
|
|
22
|
+
provider_templates: {
|
|
23
|
+
custom_keep: {
|
|
24
|
+
label: 'Keep Me',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
const templates = next.provider_templates;
|
|
29
|
+
expect(templates).toBeTruthy();
|
|
30
|
+
expect(templates.custom_keep.label).toBe('Keep Me');
|
|
31
|
+
expect(templates['openai-official']).toBeTruthy();
|
|
32
|
+
expect(templates['openai-official'].provider_name).toBe('OpenAI');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -141,11 +141,35 @@ describe('NoteMD server integration', () => {
|
|
|
141
141
|
let temp;
|
|
142
142
|
let envRestorers;
|
|
143
143
|
let server;
|
|
144
|
+
let serverModule;
|
|
144
145
|
let port;
|
|
145
146
|
let kbFilePath;
|
|
146
147
|
let runtimeDataDir;
|
|
147
148
|
let appConfigPath;
|
|
148
149
|
let originalArgv;
|
|
150
|
+
function loadFreshServerModule() {
|
|
151
|
+
jest.resetModules();
|
|
152
|
+
process.argv = process.argv.slice(0, 2);
|
|
153
|
+
jest.doMock('./index', () => ({
|
|
154
|
+
buildGraph: jest.fn().mockResolvedValue(undefined),
|
|
155
|
+
}));
|
|
156
|
+
jest.doMock('./core/PathBridge', () => ({
|
|
157
|
+
PathBridge: jest.fn().mockImplementation(() => ({})),
|
|
158
|
+
}));
|
|
159
|
+
jest.doMock('./reader_renderer', () => ({
|
|
160
|
+
renderMathPng: jest.fn().mockResolvedValue({
|
|
161
|
+
pngBase64: 'math',
|
|
162
|
+
width: 100,
|
|
163
|
+
height: 50,
|
|
164
|
+
}),
|
|
165
|
+
renderMermaidPng: jest.fn().mockResolvedValue({
|
|
166
|
+
pngBase64: 'mermaid',
|
|
167
|
+
width: 100,
|
|
168
|
+
height: 50,
|
|
169
|
+
}),
|
|
170
|
+
}));
|
|
171
|
+
return require('./server');
|
|
172
|
+
}
|
|
149
173
|
beforeAll(async () => {
|
|
150
174
|
temp = new TempDir('noteconnection-notemd');
|
|
151
175
|
const projectRoot = temp.mkdir('project');
|
|
@@ -186,45 +210,29 @@ describe('NoteMD server integration', () => {
|
|
|
186
210
|
envRestorers.push(setEnv('npm_config_workers', undefined));
|
|
187
211
|
envRestorers.push(setEnv('npm_config_static', undefined));
|
|
188
212
|
port = await getFreePort();
|
|
189
|
-
jest.resetModules();
|
|
190
213
|
originalArgv = [...process.argv];
|
|
191
|
-
|
|
192
|
-
jest.doMock('./index', () => ({
|
|
193
|
-
buildGraph: jest.fn().mockResolvedValue(undefined),
|
|
194
|
-
}));
|
|
195
|
-
jest.doMock('./core/PathBridge', () => ({
|
|
196
|
-
PathBridge: jest.fn().mockImplementation(() => ({})),
|
|
197
|
-
}));
|
|
198
|
-
jest.doMock('./reader_renderer', () => ({
|
|
199
|
-
renderMathPng: jest.fn().mockResolvedValue({
|
|
200
|
-
pngBase64: 'math',
|
|
201
|
-
width: 100,
|
|
202
|
-
height: 50,
|
|
203
|
-
}),
|
|
204
|
-
renderMermaidPng: jest.fn().mockResolvedValue({
|
|
205
|
-
pngBase64: 'mermaid',
|
|
206
|
-
width: 100,
|
|
207
|
-
height: 50,
|
|
208
|
-
}),
|
|
209
|
-
}));
|
|
210
|
-
const serverModule = require('./server');
|
|
214
|
+
serverModule = loadFreshServerModule();
|
|
211
215
|
server = await serverModule.startServer({ port });
|
|
212
216
|
});
|
|
213
|
-
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
server.close((err) => {
|
|
220
|
-
if (err) {
|
|
221
|
-
reject(err);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
resolve();
|
|
225
|
-
});
|
|
226
|
-
});
|
|
217
|
+
async function shutdownServerInstance(instance) {
|
|
218
|
+
if (!instance) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (typeof instance.closeAllConnections === 'function') {
|
|
222
|
+
instance.closeAllConnections();
|
|
227
223
|
}
|
|
224
|
+
await new Promise((resolve, reject) => {
|
|
225
|
+
instance.close((err) => {
|
|
226
|
+
if (err) {
|
|
227
|
+
reject(err);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
resolve();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
afterAll(async () => {
|
|
235
|
+
await shutdownServerInstance(server);
|
|
228
236
|
envRestorers.reverse().forEach((restore) => restore());
|
|
229
237
|
process.argv = originalArgv;
|
|
230
238
|
jest.dontMock('./index');
|
|
@@ -462,4 +470,104 @@ describe('NoteMD server integration', () => {
|
|
|
462
470
|
expect(fs.existsSync(appConfigPath)).toBe(true);
|
|
463
471
|
expect(fs.existsSync(path.join(runtimeDataDir, 'notemd_config.json'))).toBe(false);
|
|
464
472
|
});
|
|
473
|
+
test('foundation readiness and backend sufficiency reflect embedded sqlite graph runtime after ingest', async () => {
|
|
474
|
+
const ingestResponse = await requestJson(port, 'POST', '/api/knowledge/ingest', {
|
|
475
|
+
incremental: true,
|
|
476
|
+
documents: [
|
|
477
|
+
{
|
|
478
|
+
documentId: 'doc_foundation_runtime',
|
|
479
|
+
sourcePath: path.join(path.dirname(kbFilePath), 'foundation_runtime.md'),
|
|
480
|
+
language: 'en',
|
|
481
|
+
content: '# Foundation Runtime\n\nEmbedded graph backend readiness should be measurable after ingest.',
|
|
482
|
+
},
|
|
483
|
+
],
|
|
484
|
+
});
|
|
485
|
+
expect(ingestResponse.status).toBe(200);
|
|
486
|
+
expect(ingestResponse.body.success).toBe(true);
|
|
487
|
+
const readinessResponse = await requestJson(port, 'GET', '/api/knowledge/foundation/readiness');
|
|
488
|
+
expect(readinessResponse.status).toBe(200);
|
|
489
|
+
expect(readinessResponse.body.success).toBe(true);
|
|
490
|
+
expect(readinessResponse.body.readiness).toEqual(expect.objectContaining({
|
|
491
|
+
status: 'integrated',
|
|
492
|
+
decision: 'go',
|
|
493
|
+
ready: true,
|
|
494
|
+
}));
|
|
495
|
+
expect(readinessResponse.body.readiness.baseline).toEqual(expect.objectContaining({
|
|
496
|
+
storeType: 'sqlite',
|
|
497
|
+
graphBackendStatus: 'independent',
|
|
498
|
+
graphBackendSignalKind: 'embedded_graphdb',
|
|
499
|
+
graphBackendIndependent: true,
|
|
500
|
+
queryBackendDefaultMode: 'local_hybrid',
|
|
501
|
+
vectorAdapterStatus: 'independent',
|
|
502
|
+
vectorAdapterSignalKind: 'embedding_ann',
|
|
503
|
+
}));
|
|
504
|
+
expect(readinessResponse.body.readiness.promotionCriteriaPassed).toBe(readinessResponse.body.readiness.promotionCriteriaTotal);
|
|
505
|
+
const sufficiencyResponse = await requestJson(port, 'GET', '/api/knowledge/backend/sufficiency');
|
|
506
|
+
expect(sufficiencyResponse.status).toBe(200);
|
|
507
|
+
expect(sufficiencyResponse.body.success).toBe(true);
|
|
508
|
+
expect(sufficiencyResponse.body.sufficiency).toEqual(expect.objectContaining({
|
|
509
|
+
sufficient: true,
|
|
510
|
+
}));
|
|
511
|
+
expect(sufficiencyResponse.body.sufficiency.checks.knowledgeGraph).toEqual(expect.objectContaining({
|
|
512
|
+
passed: true,
|
|
513
|
+
reason: 'embedded_graphdb',
|
|
514
|
+
}));
|
|
515
|
+
expect(sufficiencyResponse.body.sufficiency.checks.queryBackend).toEqual(expect.objectContaining({
|
|
516
|
+
passed: true,
|
|
517
|
+
}));
|
|
518
|
+
expect(sufficiencyResponse.body.sufficiency.checks.vectorIndex).toEqual(expect.objectContaining({
|
|
519
|
+
passed: true,
|
|
520
|
+
reason: 'embedding_ann',
|
|
521
|
+
}));
|
|
522
|
+
});
|
|
523
|
+
test('embedded sqlite graph runtime survives server restart and preserves query/store diagnostics', async () => {
|
|
524
|
+
const sqliteStorePath = path.join(runtimeDataDir, 'knowledge_graph_store.graphdb.v1.sqlite');
|
|
525
|
+
const ingestResponse = await requestJson(port, 'POST', '/api/knowledge/ingest', {
|
|
526
|
+
incremental: true,
|
|
527
|
+
documents: [
|
|
528
|
+
{
|
|
529
|
+
documentId: 'doc_restart_runtime',
|
|
530
|
+
sourcePath: path.join(path.dirname(kbFilePath), 'restart_runtime.md'),
|
|
531
|
+
language: 'en',
|
|
532
|
+
content: '# Restart Runtime\n\nPersist this graph content across server restart for sqlite proof.',
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
});
|
|
536
|
+
expect(ingestResponse.status).toBe(200);
|
|
537
|
+
expect(ingestResponse.body.success).toBe(true);
|
|
538
|
+
expect(fs.existsSync(sqliteStorePath)).toBe(true);
|
|
539
|
+
await shutdownServerInstance(server);
|
|
540
|
+
port = await getFreePort();
|
|
541
|
+
serverModule = loadFreshServerModule();
|
|
542
|
+
server = await serverModule.startServer({ port });
|
|
543
|
+
const diagnosticsResponse = await requestJson(port, 'GET', '/api/knowledge/store-diagnostics');
|
|
544
|
+
expect(diagnosticsResponse.status).toBe(200);
|
|
545
|
+
expect(diagnosticsResponse.body.success).toBe(true);
|
|
546
|
+
expect(diagnosticsResponse.body.store).toEqual(expect.objectContaining({
|
|
547
|
+
storeType: 'graphdb',
|
|
548
|
+
storageEngine: 'sqlite',
|
|
549
|
+
backendReady: true,
|
|
550
|
+
usingFallback: false,
|
|
551
|
+
}));
|
|
552
|
+
const queryResponse = await requestJson(port, 'POST', '/api/knowledge/query', {
|
|
553
|
+
query: 'persist graph content restart sqlite proof',
|
|
554
|
+
topK: 3,
|
|
555
|
+
});
|
|
556
|
+
expect(queryResponse.status).toBe(200);
|
|
557
|
+
expect(queryResponse.body.success).toBe(true);
|
|
558
|
+
expect(Array.isArray(queryResponse.body.result.items)).toBe(true);
|
|
559
|
+
expect(queryResponse.body.result.items.length).toBeGreaterThan(0);
|
|
560
|
+
expect(String(queryResponse.body.result.items[0]?.atom?.documentId || '')).toBe('doc_restart_runtime');
|
|
561
|
+
const readinessResponse = await requestJson(port, 'GET', '/api/knowledge/foundation/readiness');
|
|
562
|
+
expect(readinessResponse.status).toBe(200);
|
|
563
|
+
expect(readinessResponse.body.readiness).toEqual(expect.objectContaining({
|
|
564
|
+
status: 'integrated',
|
|
565
|
+
decision: 'go',
|
|
566
|
+
}));
|
|
567
|
+
expect(readinessResponse.body.readiness.baseline).toEqual(expect.objectContaining({
|
|
568
|
+
storeType: 'sqlite',
|
|
569
|
+
graphBackendSignalKind: 'embedded_graphdb',
|
|
570
|
+
graphBackendIndependent: true,
|
|
571
|
+
}));
|
|
572
|
+
});
|
|
465
573
|
});
|
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
/**
|
|
37
|
+
* Workflow Pipeline contract test — validates the 4-stage
|
|
38
|
+
* runWorkflow pipeline (extract → wikilinks → generate → mermaid).
|
|
39
|
+
*/
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const NotemdService_1 = require("./notemd/NotemdService");
|
|
44
|
+
class TempDir {
|
|
45
|
+
constructor(prefix) { this.root = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), prefix)); }
|
|
46
|
+
file(rel, content) {
|
|
47
|
+
const p = path.join(this.root, rel);
|
|
48
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
49
|
+
fs.writeFileSync(p, content, 'utf8');
|
|
50
|
+
return p;
|
|
51
|
+
}
|
|
52
|
+
cleanup() { fs.rmSync(this.root, { recursive: true, force: true }); }
|
|
53
|
+
}
|
|
54
|
+
function defaultSettings(overrides = {}) {
|
|
55
|
+
return {
|
|
56
|
+
activeProvider: 'OpenAI',
|
|
57
|
+
searchProvider: 'duckduckgo',
|
|
58
|
+
language: 'en',
|
|
59
|
+
providers: [],
|
|
60
|
+
enableStableApiCall: true,
|
|
61
|
+
enableApiErrorDebugMode: false,
|
|
62
|
+
maxRetries: 0,
|
|
63
|
+
retryDelayMs: 0,
|
|
64
|
+
maxTokens: 256,
|
|
65
|
+
chunkWordCount: 100,
|
|
66
|
+
enableDuplicateDetection: false,
|
|
67
|
+
enableExperimentalDiagramPipeline: false,
|
|
68
|
+
enableBatchParallelism: false,
|
|
69
|
+
developerMode: false,
|
|
70
|
+
developerDiagnosticCallMode: 'runtime-stable',
|
|
71
|
+
developerDiagnosticTimeoutMs: 60000,
|
|
72
|
+
developerDiagnosticStabilityRuns: 3,
|
|
73
|
+
experimentalDiagramCompatibilityMode: 'legacy-mermaid',
|
|
74
|
+
autoMermaidFixAfterGenerate: true,
|
|
75
|
+
tavilyApiKey: '',
|
|
76
|
+
tavilyMaxResults: 5,
|
|
77
|
+
tavilySearchDepth: 'basic',
|
|
78
|
+
ddgFetchTimeout: 10000,
|
|
79
|
+
useCustomConceptNoteFolder: false,
|
|
80
|
+
conceptNoteFolder: '',
|
|
81
|
+
...overrides,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
describe('Notemd Workflow Pipeline', () => {
|
|
85
|
+
let temp;
|
|
86
|
+
let service;
|
|
87
|
+
beforeEach(() => { temp = new TempDir('notemd-workflow-'); service = new NotemdService_1.NotemdService(); });
|
|
88
|
+
afterEach(() => { temp.cleanup(); });
|
|
89
|
+
test('runWorkflow executes all 3 stages (extract → generate → mermaid) on a simple file', async () => {
|
|
90
|
+
const src = temp.file('notes/topic.md', '# Graph Theory\n\nGraph theory studies **nodes** and **edges**.\n\n```mermaid\ngraph TD\nA-->B\n```');
|
|
91
|
+
const result = await service.runWorkflow({ filePath: src }, defaultSettings());
|
|
92
|
+
expect(result.sourceFilePath).toBe(src);
|
|
93
|
+
expect(result.outputFolderPath).toContain('topic_notemd_output');
|
|
94
|
+
expect(fs.existsSync(result.outputFolderPath)).toBe(true);
|
|
95
|
+
// All 3 stages should be present (no wiki-links)
|
|
96
|
+
expect(result.stages.length).toBeGreaterThanOrEqual(3);
|
|
97
|
+
const stageNames = result.stages.map(s => s.stage);
|
|
98
|
+
expect(stageNames).toContain('extract-concepts');
|
|
99
|
+
expect(stageNames).toContain('generate-titles');
|
|
100
|
+
expect(stageNames).toContain('mermaid-fix');
|
|
101
|
+
// Extract stage should have completed
|
|
102
|
+
const extractStage = result.stages.find(s => s.stage === 'extract-concepts');
|
|
103
|
+
expect(['completed', 'error']).toContain(extractStage.status);
|
|
104
|
+
// Summary should have valid numbers
|
|
105
|
+
expect(typeof result.summary.conceptsExtracted).toBe('number');
|
|
106
|
+
expect(typeof result.summary.totalElapsedMs).toBe('number');
|
|
107
|
+
expect(result.summary.totalElapsedMs).toBeGreaterThan(0);
|
|
108
|
+
});
|
|
109
|
+
test('runWorkflow with addWikiLinks injects wiki-links and writes _wikified file', async () => {
|
|
110
|
+
const src = temp.file('notes/test.md', '# Machine Learning\n\nMachine learning uses neural networks and deep learning.');
|
|
111
|
+
const result = await service.runWorkflow({ filePath: src, addWikiLinks: true, skipGenerate: true, skipMermaidFix: true }, defaultSettings());
|
|
112
|
+
// Wiki-links stage should be present and completed
|
|
113
|
+
const wikiStage = result.stages.find(s => s.stage === 'add-wikilinks');
|
|
114
|
+
expect(wikiStage).toBeDefined();
|
|
115
|
+
// May be 'completed' with links or 'skipped' if LLM returned no concepts
|
|
116
|
+
expect(['completed', 'skipped']).toContain(wikiStage.status);
|
|
117
|
+
// Add stage names check
|
|
118
|
+
const stageNames = result.stages.map(s => s.stage);
|
|
119
|
+
expect(stageNames).toContain('add-wikilinks');
|
|
120
|
+
expect(stageNames).not.toContain('generate-titles');
|
|
121
|
+
expect(stageNames).not.toContain('mermaid-fix');
|
|
122
|
+
});
|
|
123
|
+
test('runWorkflow with skipGenerate and skipMermaidFix only runs extract', async () => {
|
|
124
|
+
const src = temp.file('notes/simple.md', '# Simple\n\nJust a simple note.');
|
|
125
|
+
const result = await service.runWorkflow({ filePath: src, skipGenerate: true, skipMermaidFix: true }, defaultSettings());
|
|
126
|
+
// Only 1 stage should run (extract) since generate + mermaid are skipped
|
|
127
|
+
const runningStages = result.stages.filter(s => s.status !== 'skipped');
|
|
128
|
+
expect(runningStages.length).toBeGreaterThanOrEqual(1);
|
|
129
|
+
const stageNames = result.stages.map(s => s.stage);
|
|
130
|
+
expect(stageNames).toContain('extract-concepts');
|
|
131
|
+
});
|
|
132
|
+
test('runWorkflow with wikiLinksInPlace modifies source file directly', async () => {
|
|
133
|
+
const src = temp.file('notes/inplace.md', '# AI\n\nArtificial intelligence uses machine learning and deep learning algorithms.');
|
|
134
|
+
const originalContent = fs.readFileSync(src, 'utf8');
|
|
135
|
+
const result = await service.runWorkflow({ filePath: src, addWikiLinks: true, wikiLinksInPlace: true, skipGenerate: true, skipMermaidFix: true }, defaultSettings());
|
|
136
|
+
const wikiStage = result.stages.find(s => s.stage === 'add-wikilinks');
|
|
137
|
+
expect(wikiStage).toBeDefined();
|
|
138
|
+
// If wikilinks were injected in-place, file content should differ
|
|
139
|
+
if (wikiStage.status === 'completed') {
|
|
140
|
+
const newContent = fs.readFileSync(src, 'utf8');
|
|
141
|
+
// Either content changed or no concepts were found
|
|
142
|
+
expect(typeof newContent).toBe('string');
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
test('runWorkflow errors are collected per-stage without blocking other stages', async () => {
|
|
146
|
+
const src = temp.file('notes/errors.md', 'test content');
|
|
147
|
+
const result = await service.runWorkflow({ filePath: src }, defaultSettings());
|
|
148
|
+
// Each stage should report its status
|
|
149
|
+
result.stages.forEach(stage => {
|
|
150
|
+
expect(['completed', 'error', 'skipped', 'running']).toContain(stage.status);
|
|
151
|
+
});
|
|
152
|
+
// Result should always have a valid structure even on errors
|
|
153
|
+
expect(result.sourceFilePath).toBe(src);
|
|
154
|
+
expect(Array.isArray(result.errors)).toBe(true);
|
|
155
|
+
expect(typeof result.summary.totalElapsedMs).toBe('number');
|
|
156
|
+
});
|
|
157
|
+
test('runWorkflow output folder is created', async () => {
|
|
158
|
+
const src = temp.file('notes/output.md', '# Test\n\nJust testing.');
|
|
159
|
+
const result = await service.runWorkflow({ filePath: src }, defaultSettings());
|
|
160
|
+
expect(fs.existsSync(result.outputFolderPath)).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
});
|