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,382 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vectorAccelerationAdapter_1 = require("./vectorAccelerationAdapter");
4
+ describe('vector acceleration adapter', () => {
5
+ const originalFetch = globalThis.fetch;
6
+ const baseSelectionInput = {
7
+ atomCount: 180,
8
+ queryTokens: ['retrieval', 'mastery'],
9
+ queryWeights: new Map([
10
+ ['retrieval', 1.2],
11
+ ['mastery', 0.8],
12
+ ]),
13
+ topK: 4,
14
+ tokenToAtomIds: new Map([
15
+ ['retrieval', Array.from({ length: 60 }, (_v, index) => `atom_${index}`)],
16
+ ['mastery', Array.from({ length: 40 }, (_v, index) => `atom_${index + 20}`)],
17
+ ]),
18
+ signatureBuckets: new Map(),
19
+ annPrefilterEnabled: true,
20
+ representationVersion: 'local-vector-representation-v1',
21
+ embeddingModelId: 'local-semantic-tfidf-v1',
22
+ embeddingDimension: 512,
23
+ indexSignature: 'idx_sig_abc123',
24
+ };
25
+ afterEach(() => {
26
+ globalThis.fetch = originalFetch;
27
+ jest.restoreAllMocks();
28
+ });
29
+ test('normalizes acceleration adapter provider aliases', () => {
30
+ expect((0, vectorAccelerationAdapter_1.normalizeVectorAccelerationAdapterProvider)('local')).toBe('local');
31
+ expect((0, vectorAccelerationAdapter_1.normalizeVectorAccelerationAdapterProvider)('external_stub')).toBe('external_stub');
32
+ expect((0, vectorAccelerationAdapter_1.normalizeVectorAccelerationAdapterProvider)('external-stub')).toBe('external_stub');
33
+ expect((0, vectorAccelerationAdapter_1.normalizeVectorAccelerationAdapterProvider)('stub')).toBe('external_stub');
34
+ expect((0, vectorAccelerationAdapter_1.normalizeVectorAccelerationAdapterProvider)('external_http')).toBe('external_http');
35
+ expect((0, vectorAccelerationAdapter_1.normalizeVectorAccelerationAdapterProvider)('external-http')).toBe('external_http');
36
+ expect((0, vectorAccelerationAdapter_1.normalizeVectorAccelerationAdapterProvider)('http')).toBe('external_http');
37
+ expect((0, vectorAccelerationAdapter_1.normalizeVectorAccelerationAdapterProvider)('unknown')).toBe('local');
38
+ });
39
+ test('returns undefined adapter for local provider', () => {
40
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('local');
41
+ expect(adapter).toBeUndefined();
42
+ });
43
+ test('external stub adapter selects token-based candidates for large corpus', async () => {
44
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_stub');
45
+ expect(adapter).toBeDefined();
46
+ expect(adapter?.id).toBe('external-stub-vector-acceleration-v1');
47
+ const selection = await adapter?.selectCandidates(baseSelectionInput);
48
+ expect(selection?.used).toBe(true);
49
+ expect(selection?.mode).toBe('token_prefilter');
50
+ expect((selection?.candidateAtomIds || []).length).toBeGreaterThan(0);
51
+ });
52
+ test('external stub adapter returns full scan when prefilter is disabled', async () => {
53
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_stub');
54
+ const selection = await adapter?.selectCandidates({
55
+ ...baseSelectionInput,
56
+ queryTokens: ['retrieval'],
57
+ queryWeights: new Map([['retrieval', 1.0]]),
58
+ tokenToAtomIds: new Map([['retrieval', ['atom_a', 'atom_b']]]),
59
+ annPrefilterEnabled: false,
60
+ });
61
+ expect(selection?.used).toBe(false);
62
+ expect(selection?.mode).toBe('full_scan');
63
+ expect(selection?.candidateAtomIds).toEqual([]);
64
+ });
65
+ test('external http adapter throws endpoint-missing error when endpoint is not configured', async () => {
66
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http');
67
+ await expect(adapter?.selectCandidates(baseSelectionInput)).rejects.toThrow('external_http_endpoint_missing');
68
+ expect(adapter?.getHealth?.().status).toBe('unavailable');
69
+ expect(String(adapter?.getHealth?.().message || '')).toContain('external_http_endpoint_missing');
70
+ expect(Number(adapter?.getHealth?.().failureCount || 0)).toBeGreaterThanOrEqual(1);
71
+ expect(adapter?.getHealth?.().representationVersion).toBe('local-vector-representation-v1');
72
+ expect(adapter?.getHealth?.().embeddingModelId).toBe('local-semantic-tfidf-v1');
73
+ expect(adapter?.getHealth?.().representationStatus).toBe('unknown');
74
+ });
75
+ test('external http adapter forwards selection request and normalizes connector response', async () => {
76
+ const fetchMock = jest.fn().mockResolvedValue({
77
+ ok: true,
78
+ status: 200,
79
+ json: async () => ({
80
+ used: true,
81
+ mode: 'token_prefilter',
82
+ candidateAtomIds: ['atom_10', 'atom_21'],
83
+ representationVersion: 'local-vector-representation-v1',
84
+ embeddingModelId: 'local-semantic-tfidf-v1',
85
+ embeddingDimension: 512,
86
+ indexSignature: 'idx_sig_abc123',
87
+ representationStatus: 'aligned',
88
+ }),
89
+ });
90
+ globalThis.fetch = fetchMock;
91
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
92
+ externalHttp: {
93
+ endpoint: 'http://127.0.0.1:18080',
94
+ timeoutMs: 450,
95
+ },
96
+ });
97
+ const selection = await adapter?.selectCandidates(baseSelectionInput);
98
+ expect(fetchMock).toHaveBeenCalledTimes(1);
99
+ expect(String(fetchMock.mock.calls[0]?.[0] || '')).toContain('/select-candidates');
100
+ const requestOptions = fetchMock.mock.calls[0]?.[1];
101
+ const requestHeaders = requestOptions?.headers || {};
102
+ const requestBody = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body || '{}'));
103
+ expect(String(requestHeaders['x-request-id'] || '')).toContain('nc-vector-accel-');
104
+ expect(String(requestHeaders['x-correlation-id'] || '')).toBe(String(requestHeaders['x-request-id'] || ''));
105
+ expect(String(requestBody.representationVersion || '')).toBe('local-vector-representation-v1');
106
+ expect(String(requestBody.embeddingModelId || '')).toBe('local-semantic-tfidf-v1');
107
+ expect(Number(requestBody.embeddingDimension || 0)).toBe(512);
108
+ expect(String(requestBody.indexSignature || '')).toBe('idx_sig_abc123');
109
+ expect(selection?.used).toBe(true);
110
+ expect(selection?.mode).toBe('token_prefilter');
111
+ expect(selection?.candidateAtomIds).toEqual(['atom_10', 'atom_21']);
112
+ expect(selection?.representation?.validated).toBe(true);
113
+ expect(selection?.prefilterMetrics?.candidatesReturned).toBe(2);
114
+ expect(Number(selection?.prefilterMetrics?.totalAtomsInScope || 0)).toBeGreaterThan(0);
115
+ expect(adapter?.getHealth?.().status).toBe('ready');
116
+ expect(String(adapter?.getHealth?.().lastRequestId || '')).toContain('nc-vector-accel-');
117
+ expect(adapter?.getHealth?.().representationStatus).toBe('aligned');
118
+ expect(adapter?.getHealth?.().representationVersion).toBe('local-vector-representation-v1');
119
+ expect(adapter?.getHealth?.().embeddingModelId).toBe('local-semantic-tfidf-v1');
120
+ expect(Number(adapter?.getHealth?.().embeddingDimension || 0)).toBe(512);
121
+ });
122
+ test('external http adapter syncs remote index and caches matching signatures', async () => {
123
+ const fetchMock = jest.fn().mockResolvedValue({
124
+ ok: true,
125
+ status: 200,
126
+ headers: {
127
+ get: (name) => {
128
+ if (String(name || '').toLowerCase() === 'x-request-id') {
129
+ return 'connector-sync-success';
130
+ }
131
+ return '';
132
+ },
133
+ },
134
+ json: async () => ({
135
+ synced: true,
136
+ atomCount: 180,
137
+ indexSignature: 'idx_sig_abc123',
138
+ representationVersion: 'local-vector-representation-v1',
139
+ embeddingModelId: 'local-semantic-tfidf-v1',
140
+ embeddingDimension: 512,
141
+ representationStatus: 'aligned',
142
+ }),
143
+ });
144
+ globalThis.fetch = fetchMock;
145
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
146
+ externalHttp: {
147
+ endpoint: 'http://127.0.0.1:18080',
148
+ },
149
+ });
150
+ const syncResult = await adapter?.syncIndex?.(baseSelectionInput);
151
+ expect(fetchMock).toHaveBeenCalledTimes(1);
152
+ expect(String(fetchMock.mock.calls[0]?.[0] || '')).toContain('/sync-index');
153
+ const requestBody = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body || '{}'));
154
+ expect(Number(requestBody.atomCount || 0)).toBe(180);
155
+ expect(String(requestBody.indexSignature || '')).toBe('idx_sig_abc123');
156
+ expect(Array.isArray(requestBody.tokenToAtomIds)).toBe(true);
157
+ expect(Array.isArray(requestBody.signatureBuckets)).toBe(true);
158
+ expect(syncResult?.synced).toBe(true);
159
+ expect(syncResult?.indexSignature).toBe('idx_sig_abc123');
160
+ const healthAfterSync = adapter?.getHealth?.();
161
+ expect(healthAfterSync?.status).toBe('ready');
162
+ expect(healthAfterSync?.indexSyncStatus).toBe('ready');
163
+ expect(healthAfterSync?.syncRequestCount).toBe(1);
164
+ expect(healthAfterSync?.syncSuccessCount).toBe(1);
165
+ expect(healthAfterSync?.syncFailureCount).toBe(0);
166
+ expect(healthAfterSync?.syncedIndexSignature).toBe('idx_sig_abc123');
167
+ expect(healthAfterSync?.syncedAtomCount).toBe(180);
168
+ const cachedSyncResult = await adapter?.syncIndex?.(baseSelectionInput);
169
+ expect(fetchMock).toHaveBeenCalledTimes(1);
170
+ expect(cachedSyncResult?.synced).toBe(true);
171
+ expect(adapter?.getHealth?.().indexSyncStatus).toBe('ready');
172
+ });
173
+ test('external http adapter filters out-of-scope candidate ids and keeps only valid ids', async () => {
174
+ const fetchMock = jest.fn().mockResolvedValue({
175
+ ok: true,
176
+ status: 200,
177
+ json: async () => ({
178
+ used: true,
179
+ mode: 'token_prefilter',
180
+ candidateAtomIds: ['atom_999', 'atom_10', 'atom_10'],
181
+ }),
182
+ });
183
+ globalThis.fetch = fetchMock;
184
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
185
+ externalHttp: {
186
+ endpoint: 'http://127.0.0.1:18080',
187
+ },
188
+ });
189
+ const selection = await adapter?.selectCandidates(baseSelectionInput);
190
+ expect(selection?.used).toBe(true);
191
+ expect(selection?.candidateAtomIds).toEqual(['atom_10']);
192
+ expect(selection?.prefilterMetrics?.candidatesReturned).toBe(1);
193
+ expect(selection?.prefilterMetrics?.prefilterRatio).toBeGreaterThan(0);
194
+ expect(String(adapter?.getHealth?.().representationStatusReason || '')).toContain('candidate_ids_out_of_scope');
195
+ });
196
+ test('external http adapter infers representation mismatch when connector metadata drifts', async () => {
197
+ const fetchMock = jest.fn().mockResolvedValue({
198
+ ok: true,
199
+ status: 200,
200
+ json: async () => ({
201
+ used: true,
202
+ mode: 'token_prefilter',
203
+ candidateAtomIds: ['atom_10'],
204
+ representationVersion: 'remote-vector-representation-v2',
205
+ embeddingModelId: 'local-semantic-tfidf-v1',
206
+ embeddingDimension: 512,
207
+ indexSignature: 'idx_sig_abc123',
208
+ }),
209
+ });
210
+ globalThis.fetch = fetchMock;
211
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
212
+ externalHttp: {
213
+ endpoint: 'http://127.0.0.1:18080',
214
+ },
215
+ });
216
+ const selection = await adapter?.selectCandidates(baseSelectionInput);
217
+ expect(selection?.used).toBe(true);
218
+ expect(adapter?.getHealth?.().representationStatus).toBe('mismatch');
219
+ expect(String(adapter?.getHealth?.().representationStatusReason || '')).toContain('representation_version');
220
+ });
221
+ test('external http adapter retries transient status failures and recovers', async () => {
222
+ const fetchMock = jest
223
+ .fn()
224
+ .mockResolvedValueOnce({
225
+ ok: false,
226
+ status: 503,
227
+ headers: {
228
+ get: (name) => {
229
+ const normalized = String(name || '').toLowerCase();
230
+ if (normalized === 'retry-after') {
231
+ return '0.03';
232
+ }
233
+ if (normalized === 'x-request-id') {
234
+ return 'connector-retry-503';
235
+ }
236
+ return '';
237
+ },
238
+ },
239
+ json: async () => ({}),
240
+ })
241
+ .mockResolvedValueOnce({
242
+ ok: true,
243
+ status: 200,
244
+ headers: {
245
+ get: (name) => {
246
+ if (String(name || '').toLowerCase() === 'x-request-id') {
247
+ return 'connector-retry-success';
248
+ }
249
+ return '';
250
+ },
251
+ },
252
+ json: async () => ({
253
+ used: true,
254
+ mode: 'token_prefilter',
255
+ candidateAtomIds: ['atom_22'],
256
+ }),
257
+ });
258
+ globalThis.fetch = fetchMock;
259
+ const originalSetTimeout = global.setTimeout;
260
+ const recordedTimeoutDelays = [];
261
+ const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((handler, timeout, ...args) => {
262
+ recordedTimeoutDelays.push(Math.floor(Number(timeout || 0)));
263
+ return originalSetTimeout(handler, timeout, ...args);
264
+ }));
265
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
266
+ externalHttp: {
267
+ endpoint: 'http://127.0.0.1:18080',
268
+ maxRetries: 2,
269
+ retryDelayMs: 0,
270
+ },
271
+ });
272
+ const selection = await adapter?.selectCandidates(baseSelectionInput);
273
+ setTimeoutSpy.mockRestore();
274
+ expect(fetchMock).toHaveBeenCalledTimes(2);
275
+ expect(selection?.used).toBe(true);
276
+ expect(selection?.candidateAtomIds).toEqual(['atom_22']);
277
+ expect(adapter?.getHealth?.().status).toBe('ready');
278
+ expect(recordedTimeoutDelays.some((delay) => delay >= 30)).toBe(true);
279
+ expect(String(adapter?.getHealth?.().lastRequestId || '')).toBe('connector-retry-success');
280
+ expect(String(adapter?.getHealth?.().lastErrorCode || '')).toBe('');
281
+ expect(Number(adapter?.getHealth?.().lastRetryAfterMs || 0)).toBe(30);
282
+ });
283
+ test('external http adapter does not retry non-transient status failures', async () => {
284
+ const fetchMock = jest.fn().mockResolvedValue({
285
+ ok: false,
286
+ status: 400,
287
+ json: async () => ({}),
288
+ });
289
+ globalThis.fetch = fetchMock;
290
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
291
+ externalHttp: {
292
+ endpoint: 'http://127.0.0.1:18080',
293
+ maxRetries: 3,
294
+ retryDelayMs: 0,
295
+ },
296
+ });
297
+ await expect(adapter?.selectCandidates(baseSelectionInput)).rejects.toThrow('external_http_status_400');
298
+ expect(fetchMock).toHaveBeenCalledTimes(1);
299
+ expect(adapter?.getHealth?.().status).toBe('unavailable');
300
+ });
301
+ test('external http adapter retries transient network failures up to max retries', async () => {
302
+ const fetchMock = jest.fn().mockRejectedValue(new Error('socket_hang_up'));
303
+ globalThis.fetch = fetchMock;
304
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
305
+ externalHttp: {
306
+ endpoint: 'http://127.0.0.1:18080',
307
+ maxRetries: 2,
308
+ retryDelayMs: 0,
309
+ },
310
+ });
311
+ await expect(adapter?.selectCandidates(baseSelectionInput)).rejects.toThrow('socket_hang_up');
312
+ expect(fetchMock).toHaveBeenCalledTimes(3);
313
+ expect(adapter?.getHealth?.().status).toBe('degraded');
314
+ });
315
+ test('external http adapter propagates connector failures and marks degraded health', async () => {
316
+ const fetchMock = jest.fn().mockRejectedValue(new Error('socket_hang_up'));
317
+ globalThis.fetch = fetchMock;
318
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
319
+ externalHttp: {
320
+ endpoint: 'http://127.0.0.1:18080',
321
+ },
322
+ });
323
+ await expect(adapter?.selectCandidates(baseSelectionInput)).rejects.toThrow('socket_hang_up');
324
+ expect(adapter?.getHealth?.().status).toBe('degraded');
325
+ expect(String(adapter?.getHealth?.().message || '')).toContain('socket_hang_up');
326
+ });
327
+ test('external http adapter opens circuit after consecutive failures and short-circuits requests', async () => {
328
+ const fetchMock = jest.fn().mockRejectedValue(new Error('socket_hang_up'));
329
+ globalThis.fetch = fetchMock;
330
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
331
+ externalHttp: {
332
+ endpoint: 'http://127.0.0.1:18080',
333
+ maxRetries: 0,
334
+ retryDelayMs: 0,
335
+ circuitFailureThreshold: 2,
336
+ circuitCooldownMs: 10000,
337
+ },
338
+ });
339
+ await expect(adapter?.selectCandidates(baseSelectionInput)).rejects.toThrow('socket_hang_up');
340
+ await expect(adapter?.selectCandidates(baseSelectionInput)).rejects.toThrow('socket_hang_up');
341
+ await expect(adapter?.selectCandidates(baseSelectionInput)).rejects.toThrow('external_http_circuit_open');
342
+ expect(fetchMock).toHaveBeenCalledTimes(2);
343
+ expect(adapter?.getHealth?.().status).toBe('unavailable');
344
+ expect(String(adapter?.getHealth?.().message || '')).toContain('external_http_circuit_open');
345
+ });
346
+ test('external http adapter enters half-open after cooldown and closes on successful probe', async () => {
347
+ const fetchMock = jest
348
+ .fn()
349
+ .mockRejectedValueOnce(new Error('socket_hang_up'))
350
+ .mockResolvedValueOnce({
351
+ ok: true,
352
+ status: 200,
353
+ json: async () => ({
354
+ used: true,
355
+ mode: 'token_prefilter',
356
+ candidateAtomIds: ['atom_23'],
357
+ }),
358
+ });
359
+ globalThis.fetch = fetchMock;
360
+ let nowMs = 1000;
361
+ jest.spyOn(Date, 'now').mockImplementation(() => nowMs);
362
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
363
+ externalHttp: {
364
+ endpoint: 'http://127.0.0.1:18080',
365
+ maxRetries: 0,
366
+ retryDelayMs: 0,
367
+ circuitFailureThreshold: 1,
368
+ circuitCooldownMs: 500,
369
+ },
370
+ });
371
+ await expect(adapter?.selectCandidates(baseSelectionInput)).rejects.toThrow('socket_hang_up');
372
+ await expect(adapter?.selectCandidates(baseSelectionInput)).rejects.toThrow('external_http_circuit_open');
373
+ expect(fetchMock).toHaveBeenCalledTimes(1);
374
+ nowMs += 600;
375
+ const recovered = await adapter?.selectCandidates(baseSelectionInput);
376
+ expect(fetchMock).toHaveBeenCalledTimes(2);
377
+ expect(recovered?.used).toBe(true);
378
+ expect(recovered?.candidateAtomIds).toEqual(['atom_23']);
379
+ expect(adapter?.getHealth?.().status).toBe('ready');
380
+ expect(String(adapter?.getHealth?.().message || '')).toContain('circuit=closed');
381
+ });
382
+ });
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fs = __importStar(require("fs"));
37
+ const path = __importStar(require("path"));
38
+ describe('LFS asset policy contracts', () => {
39
+ const repoRoot = path.resolve(__dirname, '..');
40
+ const packageJsonPath = path.join(repoRoot, 'package.json');
41
+ const gitattributesPath = path.join(repoRoot, '.gitattributes');
42
+ const deadNodeSidecarPath = path.join(repoRoot, 'src-tauri', 'bin', 'node-x86_64-pc-windows-msvc.exe');
43
+ const deadGraphPayloadPaths = [
44
+ path.join(repoRoot, 'src', 'frontend', 'data.js'),
45
+ path.join(repoRoot, 'src', 'frontend', 'graph_data.json'),
46
+ ];
47
+ const policyUtilsPath = path.join(repoRoot, 'scripts', 'lfs-asset-policy-utils.js');
48
+ let policyUtils;
49
+ function readJson(filePath) {
50
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
51
+ }
52
+ beforeAll(() => {
53
+ policyUtils = require(policyUtilsPath);
54
+ });
55
+ test('repository head no longer keeps removed LFS residues for dead node sidecar or bundled graph payloads', () => {
56
+ const gitattributes = fs.readFileSync(gitattributesPath, 'utf8');
57
+ expect(gitattributes).not.toContain('src-tauri/bin/node-x86_64-pc-windows-msvc.exe');
58
+ expect(gitattributes).not.toContain('src/frontend/data.js');
59
+ expect(gitattributes).not.toContain('src/frontend/graph_data.json');
60
+ expect(fs.existsSync(deadNodeSidecarPath)).toBe(false);
61
+ deadGraphPayloadPaths.forEach((filePath) => {
62
+ expect(fs.existsSync(filePath)).toBe(false);
63
+ });
64
+ });
65
+ test('unexpected-only mode tolerates current legacy protected entries while keeping protected roots explicit', () => {
66
+ const result = policyUtils.evaluateLfsAssetPolicy({
67
+ gitattributesText: [
68
+ 'src-tauri/bin/server-x86_64-pc-windows-msvc.exe filter=lfs diff=lfs merge=lfs -text',
69
+ 'src-tauri/bin/server-x86_64-unknown-linux-gnu filter=lfs diff=lfs merge=lfs -text',
70
+ 'src-tauri/bin/server-aarch64-apple-darwin filter=lfs diff=lfs merge=lfs -text',
71
+ 'src-tauri/bin/godot-x86_64-pc-windows-msvc.exe filter=lfs diff=lfs merge=lfs -text',
72
+ 'assets/demo-large.zip filter=lfs diff=lfs merge=lfs -text',
73
+ ].join('\n'),
74
+ lfsLsFilesText: [
75
+ 'bbbbbbbbbb - src-tauri/bin/server-x86_64-unknown-linux-gnu',
76
+ ].join('\n'),
77
+ mode: 'unexpected-only',
78
+ });
79
+ expect(policyUtils.PROTECTED_LFS_ROOTS).toEqual(['src/frontend/', 'src-tauri/bin/']);
80
+ expect(result.unexpectedProtectedPaths).toEqual([]);
81
+ expect(result.legacyProtectedPaths).toEqual([
82
+ 'src-tauri/bin/godot-x86_64-pc-windows-msvc.exe',
83
+ 'src-tauri/bin/server-aarch64-apple-darwin',
84
+ 'src-tauri/bin/server-x86_64-pc-windows-msvc.exe',
85
+ 'src-tauri/bin/server-x86_64-unknown-linux-gnu',
86
+ ]);
87
+ });
88
+ test('unexpected-only mode flags new protected LFS paths outside the approved legacy allowlist', () => {
89
+ const result = policyUtils.evaluateLfsAssetPolicy({
90
+ gitattributesText: [
91
+ 'src-tauri/bin/new-helper.exe filter=lfs diff=lfs merge=lfs -text',
92
+ ].join('\n'),
93
+ mode: 'unexpected-only',
94
+ });
95
+ expect(result.unexpectedProtectedPaths).toEqual(['src-tauri/bin/new-helper.exe']);
96
+ });
97
+ test('unexpected-only mode now treats bundled graph payload LFS paths as drift to be removed', () => {
98
+ const result = policyUtils.evaluateLfsAssetPolicy({
99
+ gitattributesText: [
100
+ 'src/frontend/data.js filter=lfs diff=lfs merge=lfs -text',
101
+ 'src/frontend/graph_data.json filter=lfs diff=lfs merge=lfs -text',
102
+ ].join('\n'),
103
+ mode: 'unexpected-only',
104
+ });
105
+ expect(result.unexpectedProtectedPaths).toEqual([
106
+ 'src/frontend/data.js',
107
+ 'src/frontend/graph_data.json',
108
+ ]);
109
+ });
110
+ test('deleted working-tree residues listed by git lfs ls-files are ignored once the rule and file are gone', () => {
111
+ const result = policyUtils.evaluateLfsAssetPolicy({
112
+ gitattributesText: '',
113
+ lfsLsFilesText: 'aaaaaaaaaa - src-tauri/bin/node-x86_64-pc-windows-msvc.exe',
114
+ existingRepoPaths: [],
115
+ mode: 'unexpected-only',
116
+ });
117
+ expect(result.protectedTrackedPaths).toEqual([]);
118
+ expect(result.unexpectedProtectedPaths).toEqual([]);
119
+ });
120
+ test('strict mode is ready for the future no-LFS end state', () => {
121
+ const result = policyUtils.evaluateLfsAssetPolicy({
122
+ gitattributesText: 'src/frontend/data.js filter=lfs diff=lfs merge=lfs -text',
123
+ mode: 'strict',
124
+ });
125
+ expect(result.strictViolations).toEqual(['src/frontend/data.js']);
126
+ });
127
+ test('package scripts expose local policy verification and future strict mode', () => {
128
+ const packageJson = readJson(packageJsonPath);
129
+ const scripts = packageJson.scripts || {};
130
+ expect(scripts['verify:lfs:policy']).toBe('node scripts/verify-lfs-asset-policy.js');
131
+ expect(scripts['verify:lfs:policy:strict']).toBe('node scripts/verify-lfs-asset-policy.js --strict');
132
+ });
133
+ test('legacy allowlist no longer preserves the unused node sidecar path', () => {
134
+ expect(policyUtils.LEGACY_ALLOWED_PROTECTED_LFS_PATHS).not.toContain('src-tauri/bin/node-x86_64-pc-windows-msvc.exe');
135
+ expect(policyUtils.LEGACY_ALLOWED_PROTECTED_LFS_PATHS).not.toContain('src/frontend/data.js');
136
+ expect(policyUtils.LEGACY_ALLOWED_PROTECTED_LFS_PATHS).not.toContain('src/frontend/graph_data.json');
137
+ });
138
+ test('current legacy LFS scope is limited to desktop binary placeholders rather than markdown-worker or bridge source', () => {
139
+ const gitattributes = fs.readFileSync(gitattributesPath, 'utf8');
140
+ const tracked = policyUtils.evaluateLfsAssetPolicy({
141
+ gitattributesText: gitattributes,
142
+ mode: 'unexpected-only',
143
+ });
144
+ expect(tracked.legacyProtectedPaths).toEqual([
145
+ 'src-tauri/bin/godot-x86_64-pc-windows-msvc.exe',
146
+ 'src-tauri/bin/server-aarch64-apple-darwin',
147
+ 'src-tauri/bin/server-x86_64-pc-windows-msvc.exe',
148
+ 'src-tauri/bin/server-x86_64-unknown-linux-gnu',
149
+ ]);
150
+ expect(gitattributes).not.toContain('src-tauri/bin/markdown-worker');
151
+ expect(gitattributes).not.toContain('src/core/PathBridge.ts');
152
+ });
153
+ });
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fs = __importStar(require("fs"));
37
+ const path = __importStar(require("path"));
38
+ function readJson(filePath) {
39
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
40
+ }
41
+ describe('license policy contract', () => {
42
+ const repoRoot = path.resolve(__dirname, '..');
43
+ const packageJsonPath = path.join(repoRoot, 'package.json');
44
+ const tauriCargoTomlPath = path.join(repoRoot, 'src-tauri', 'Cargo.toml');
45
+ const licensePath = path.join(repoRoot, 'LICENSE');
46
+ const readmePath = path.join(repoRoot, 'README.md');
47
+ test('root package manifest is pinned to GPL-3.0-only', () => {
48
+ const pkg = readJson(packageJsonPath);
49
+ expect(pkg.license).toBe('GPL-3.0-only');
50
+ });
51
+ test('tauri crate manifest is pinned to GPL-3.0-only', () => {
52
+ const cargoToml = fs.readFileSync(tauriCargoTomlPath, 'utf8');
53
+ expect(cargoToml).toContain('license = "GPL-3.0-only"');
54
+ });
55
+ test('repository license artifact ships GNU GPL v3 text', () => {
56
+ const license = fs.readFileSync(licensePath, 'utf8');
57
+ expect(license).toContain('GNU GENERAL PUBLIC LICENSE');
58
+ expect(license).toContain('Version 3, 29 June 2007');
59
+ });
60
+ test('README license section advertises GPL-3.0-only in both locales', () => {
61
+ const readme = fs.readFileSync(readmePath, 'utf8');
62
+ expect(readme).toContain('License / 开源许可');
63
+ expect(readme).toContain('GNU General Public License v3.0 (GPL-3.0-only)');
64
+ expect(readme).toContain('GNU General Public License v3.0(GPL-3.0-only)');
65
+ });
66
+ });