@chengxianglibra/dsh-data-analysis 0.1.1 → 0.2.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 (303) hide show
  1. package/README.md +139 -53
  2. package/cordis.patch.yml +1 -1
  3. package/lib/bin/environment.js +9 -7
  4. package/lib/bin/presentation-lint.js +21 -0
  5. package/lib/bridges.js +2 -2
  6. package/lib/client/credentials/changes.js +74 -0
  7. package/lib/client/credentials/create-datasource.js +171 -0
  8. package/lib/client/credentials/credential-draft.js +42 -0
  9. package/lib/client/credentials/defaults.js +11 -0
  10. package/lib/client/credentials/descriptions.js +46 -0
  11. package/lib/client/credentials/install.js +319 -0
  12. package/lib/client/credentials/model.js +766 -0
  13. package/lib/client/credentials/report-publishing.js +96 -0
  14. package/lib/client/credentials/styles.js +21 -0
  15. package/lib/client/i18n/context.js +22 -0
  16. package/lib/client/i18n/copy.js +57 -0
  17. package/lib/client/i18n/credentials.js +475 -0
  18. package/lib/client/i18n/host.js +27 -0
  19. package/lib/client/i18n/navigation.js +41 -0
  20. package/lib/client/i18n/presentation.js +577 -0
  21. package/lib/client/i18n/python.js +36 -0
  22. package/lib/client/i18n/semantic.js +441 -0
  23. package/lib/client/input-context.js +21 -0
  24. package/lib/client/python-tool/card.js +35 -0
  25. package/lib/client/python-tool/execution-error.js +35 -0
  26. package/lib/client/python-tool/install.js +9 -0
  27. package/lib/client/python-tool/model.js +101 -0
  28. package/lib/client/right-tabs/install.js +535 -0
  29. package/lib/client/right-tabs/live-delivery.js +58 -0
  30. package/lib/client/right-tabs/navigation.js +47 -0
  31. package/lib/client/right-tabs/page.js +178 -0
  32. package/lib/client/right-tabs/styles.js +11 -0
  33. package/lib/client/rpc.js +5 -0
  34. package/lib/client/semantic-reference.js +10 -0
  35. package/lib/client/workspace-header-action.js +24 -0
  36. package/lib/client/workspace-shortcuts.js +26 -0
  37. package/lib/client.js +1250 -730
  38. package/lib/compatibility.js +2 -2
  39. package/lib/datasource/authoring.js +109 -0
  40. package/lib/datasource/bridge-programs.js +42 -27
  41. package/lib/datasource/bridge.js +144 -9
  42. package/lib/datasource/changes-contract.js +41 -0
  43. package/lib/datasource/changes.js +49 -0
  44. package/lib/datasource/configure.js +39 -0
  45. package/lib/datasource/defaults.js +25 -0
  46. package/lib/datasource/index.js +4 -2
  47. package/lib/datasource/python-options.js +21 -0
  48. package/lib/datasource/python.js +206 -0
  49. package/lib/datasource/resolver-program.js +112 -0
  50. package/lib/datasource/rpc.js +150 -0
  51. package/lib/datasource/service.js +962 -0
  52. package/lib/datasource/shell-env.js +2 -333
  53. package/lib/datasource/test.js +25 -51
  54. package/lib/disclosure/activation.js +19 -7
  55. package/lib/disclosure/execution-guidance.js +52 -0
  56. package/lib/disclosure/help.js +4 -1
  57. package/lib/environment/binding.js +54 -9
  58. package/lib/environment/runtime.js +120 -108
  59. package/lib/environment/subprocess.js +6 -1
  60. package/lib/environment/summary.js +3 -3
  61. package/lib/environment/workspace.js +23 -1
  62. package/lib/index.js +1 -1
  63. package/lib/lifecycle.js +33 -0
  64. package/lib/plugin-agents.js +154 -0
  65. package/lib/plugin.js +172 -133
  66. package/lib/presentation/asset.js +34 -0
  67. package/lib/presentation/assets/portable.js +606 -0
  68. package/lib/presentation/assets/static.js +1216 -0
  69. package/lib/presentation/build/index.js +72 -0
  70. package/lib/presentation/commit.js +163 -0
  71. package/lib/presentation/contracts/asset-path.js +9 -0
  72. package/lib/presentation/contracts/catalog.js +62 -0
  73. package/lib/presentation/contracts/charts.js +453 -0
  74. package/lib/presentation/contracts/editing.js +58 -0
  75. package/lib/presentation/contracts/index.js +663 -0
  76. package/lib/presentation/contracts/interaction.js +62 -0
  77. package/lib/presentation/contracts/types.js +15 -0
  78. package/lib/presentation/contracts/values.js +28 -0
  79. package/lib/presentation/delivery.js +71 -0
  80. package/lib/presentation/export-html.js +214 -0
  81. package/lib/presentation/files.js +82 -0
  82. package/lib/presentation/index.js +6 -0
  83. package/lib/presentation/lint.js +46 -0
  84. package/lib/presentation/projection/files.js +85 -0
  85. package/lib/presentation/projection/index.js +196 -0
  86. package/lib/presentation/projection/program.js +137 -0
  87. package/lib/presentation/projection/sql-code.js +79 -0
  88. package/lib/presentation/receipt.js +34 -0
  89. package/lib/presentation/reports.js +219 -0
  90. package/lib/presentation/rpc.js +186 -0
  91. package/lib/presentation/tool.js +118 -0
  92. package/lib/python-execution-contracts.js +1 -0
  93. package/lib/python-execution.js +232 -0
  94. package/lib/report-publishing/adapters.js +111 -0
  95. package/lib/report-publishing/config.js +69 -0
  96. package/lib/report-publishing/service.js +180 -0
  97. package/lib/rpc.js +49 -0
  98. package/lib/semantic-browser/contracts.js +97 -0
  99. package/lib/semantic-browser/definition.js +64 -0
  100. package/lib/semantic-browser/program.js +129 -0
  101. package/lib/semantic-browser/service.js +106 -0
  102. package/lib/semantic-reference/bridge.js +127 -0
  103. package/lib/semantic-reference/contracts.js +127 -0
  104. package/lib/semantic-reference/environment.js +56 -0
  105. package/lib/semantic-reference/index.js +3 -0
  106. package/lib/semantic-reference/labels.js +16 -0
  107. package/lib/semantic-reference/rpc.js +143 -0
  108. package/lib/semantic-reference/search.js +95 -0
  109. package/lib/semantic-reference/usage.js +152 -0
  110. package/lib/tool-lifecycle.js +22 -0
  111. package/lib/types/bridges.d.ts +2 -2
  112. package/lib/types/client/credentials/changes.d.ts +9 -0
  113. package/lib/types/client/credentials/create-datasource.d.ts +7 -0
  114. package/lib/types/client/credentials/credential-draft.d.ts +14 -0
  115. package/lib/types/client/credentials/defaults.d.ts +2 -0
  116. package/lib/types/client/credentials/descriptions.d.ts +1 -0
  117. package/lib/types/client/credentials/install.d.ts +19 -0
  118. package/lib/types/client/credentials/model.d.ts +79 -0
  119. package/lib/types/client/credentials/report-publishing.d.ts +5 -0
  120. package/lib/types/client/credentials/styles.d.ts +1 -0
  121. package/lib/types/client/i18n/context.d.ts +17 -0
  122. package/lib/types/client/i18n/copy.d.ts +806 -0
  123. package/lib/types/client/i18n/credentials.d.ts +475 -0
  124. package/lib/types/client/i18n/host.d.ts +16 -0
  125. package/lib/types/client/i18n/navigation.d.ts +41 -0
  126. package/lib/types/client/i18n/presentation.d.ts +577 -0
  127. package/lib/types/client/i18n/python.d.ts +19 -0
  128. package/lib/types/client/i18n/semantic.d.ts +441 -0
  129. package/lib/types/client/input-context.d.ts +19 -0
  130. package/lib/types/client/presentation/ask-dsh.d.ts +5 -0
  131. package/lib/types/client/presentation/catalog-model.d.ts +24 -0
  132. package/lib/types/client/presentation/catalog.d.ts +20 -0
  133. package/lib/types/client/presentation/chart-explorer.d.ts +12 -0
  134. package/lib/types/client/presentation/chart-geometry.d.ts +30 -0
  135. package/lib/types/client/presentation/chart-renderer.d.ts +10 -0
  136. package/lib/types/client/presentation/chart-specials.d.ts +10 -0
  137. package/lib/types/client/presentation/chart-styles.d.ts +1 -0
  138. package/lib/types/client/presentation/chart-tooltip.d.ts +13 -0
  139. package/lib/types/client/presentation/chart-view.d.ts +23 -0
  140. package/lib/types/client/presentation/context-reference.d.ts +12 -0
  141. package/lib/types/client/presentation/copy-context.d.ts +5 -0
  142. package/lib/types/client/presentation/delivery-model.d.ts +75 -0
  143. package/lib/types/client/presentation/delivery.d.ts +78 -0
  144. package/lib/types/client/presentation/download.d.ts +2 -0
  145. package/lib/types/client/presentation/editor-controls.d.ts +12 -0
  146. package/lib/types/client/presentation/export-menu.d.ts +26 -0
  147. package/lib/types/client/presentation/export-view.d.ts +63 -0
  148. package/lib/types/client/presentation/global-filters.d.ts +6 -0
  149. package/lib/types/client/presentation/host-entry.d.ts +18 -0
  150. package/lib/types/client/presentation/icons.d.ts +4 -0
  151. package/lib/types/client/presentation/markdown.d.ts +5 -0
  152. package/lib/types/client/presentation/model.d.ts +61 -0
  153. package/lib/types/client/presentation/reader.d.ts +24 -0
  154. package/lib/types/client/presentation/reference-source.d.ts +9 -0
  155. package/lib/types/client/presentation/source-code-model.d.ts +17 -0
  156. package/lib/types/client/presentation/source-code.d.ts +6 -0
  157. package/lib/types/client/presentation/source-dialog.d.ts +12 -0
  158. package/lib/types/client/presentation/source-facts.d.ts +20 -0
  159. package/lib/types/client/presentation/source-format.d.ts +5 -0
  160. package/lib/types/client/presentation/source-highlight.d.ts +7 -0
  161. package/lib/types/client/presentation/sources.d.ts +13 -0
  162. package/lib/types/client/presentation/styles.d.ts +1 -0
  163. package/lib/types/client/presentation/table.d.ts +16 -0
  164. package/lib/types/client/presentation/use-element-width.d.ts +5 -0
  165. package/lib/types/client/python-tool/card.d.ts +2 -0
  166. package/lib/types/client/python-tool/execution-error.d.ts +3 -0
  167. package/lib/types/client/python-tool/install.d.ts +2 -0
  168. package/lib/types/client/python-tool/model.d.ts +20 -0
  169. package/lib/types/client/right-tabs/install.d.ts +18 -0
  170. package/lib/types/client/right-tabs/live-delivery.d.ts +8 -0
  171. package/lib/types/client/right-tabs/navigation.d.ts +17 -0
  172. package/lib/types/client/right-tabs/page.d.ts +36 -0
  173. package/lib/types/client/right-tabs/styles.d.ts +2 -0
  174. package/lib/types/client/rpc.d.ts +3 -0
  175. package/lib/types/client/semantic-reference.d.ts +4 -0
  176. package/lib/types/client/workspace-header-action.d.ts +7 -0
  177. package/lib/types/client/workspace-shortcuts.d.ts +15 -0
  178. package/lib/types/client.d.ts +9 -124
  179. package/lib/types/compatibility.d.ts +3 -3
  180. package/lib/types/datasource/authoring.d.ts +35 -0
  181. package/lib/types/datasource/bridge.d.ts +43 -4
  182. package/lib/types/datasource/changes-contract.d.ts +18 -0
  183. package/lib/types/datasource/changes.d.ts +15 -0
  184. package/lib/types/datasource/configure.d.ts +7 -0
  185. package/lib/types/datasource/defaults.d.ts +7 -0
  186. package/lib/types/datasource/index.d.ts +8 -5
  187. package/lib/types/datasource/python-options.d.ts +9 -0
  188. package/lib/types/datasource/python.d.ts +19 -0
  189. package/lib/types/datasource/resolver-program.d.ts +6 -0
  190. package/lib/types/datasource/rpc.d.ts +5 -0
  191. package/lib/types/datasource/service.d.ts +155 -0
  192. package/lib/types/datasource/shell-env.d.ts +0 -29
  193. package/lib/types/datasource/test.d.ts +6 -29
  194. package/lib/types/disclosure/activation.d.ts +2 -1
  195. package/lib/types/disclosure/execution-guidance.d.ts +6 -0
  196. package/lib/types/disclosure/help.d.ts +1 -1
  197. package/lib/types/environment/binding.d.ts +5 -1
  198. package/lib/types/environment/errors.d.ts +1 -1
  199. package/lib/types/environment/runtime.d.ts +5 -5
  200. package/lib/types/environment/types.d.ts +15 -3
  201. package/lib/types/environment/workspace.d.ts +1 -0
  202. package/lib/types/index.d.ts +1 -1
  203. package/lib/types/lifecycle.d.ts +8 -0
  204. package/lib/types/plugin-agents.d.ts +27 -0
  205. package/lib/types/plugin.d.ts +14 -24
  206. package/lib/types/presentation/asset.d.ts +6 -0
  207. package/lib/types/presentation/build/index.d.ts +16 -0
  208. package/lib/types/presentation/commit.d.ts +4 -0
  209. package/lib/types/presentation/contracts/asset-path.d.ts +2 -0
  210. package/lib/types/presentation/contracts/catalog.d.ts +27 -0
  211. package/lib/types/presentation/contracts/charts.d.ts +11 -0
  212. package/lib/types/presentation/contracts/editing.d.ts +8 -0
  213. package/lib/types/presentation/contracts/index.d.ts +11 -0
  214. package/lib/types/presentation/contracts/interaction.d.ts +8 -0
  215. package/lib/types/presentation/contracts/types.d.ts +255 -0
  216. package/lib/types/presentation/contracts/values.d.ts +9 -0
  217. package/lib/types/presentation/delivery.d.ts +3 -0
  218. package/lib/types/presentation/export-html.d.ts +13 -0
  219. package/lib/types/presentation/files.d.ts +7 -0
  220. package/lib/types/presentation/index.d.ts +6 -0
  221. package/lib/types/presentation/lint.d.ts +13 -0
  222. package/lib/types/presentation/projection/files.d.ts +2 -0
  223. package/lib/types/presentation/projection/index.d.ts +20 -0
  224. package/lib/types/presentation/projection/program.d.ts +2 -0
  225. package/lib/types/presentation/projection/sql-code.d.ts +2 -0
  226. package/lib/types/presentation/receipt.d.ts +14 -0
  227. package/lib/types/presentation/reports.d.ts +10 -0
  228. package/lib/types/presentation/rpc.d.ts +25 -0
  229. package/lib/types/presentation/tool.d.ts +12 -0
  230. package/lib/types/python-execution-contracts.d.ts +10 -0
  231. package/lib/types/python-execution.d.ts +10 -0
  232. package/lib/types/report-publishing/adapters.d.ts +6 -0
  233. package/lib/types/report-publishing/config.d.ts +22 -0
  234. package/lib/types/report-publishing/service.d.ts +68 -0
  235. package/lib/types/rpc.d.ts +3 -0
  236. package/lib/types/semantic-browser/contracts.d.ts +47 -0
  237. package/lib/types/semantic-browser/definition.d.ts +20 -0
  238. package/lib/types/semantic-browser/program.d.ts +2 -0
  239. package/lib/types/semantic-browser/service.d.ts +26 -0
  240. package/lib/types/semantic-reference/bridge.d.ts +12 -0
  241. package/lib/types/semantic-reference/contracts.d.ts +54 -0
  242. package/lib/types/semantic-reference/environment.d.ts +22 -0
  243. package/lib/types/semantic-reference/index.d.ts +3 -0
  244. package/lib/types/semantic-reference/labels.d.ts +16 -0
  245. package/lib/types/semantic-reference/rpc.d.ts +15 -0
  246. package/lib/types/semantic-reference/search.d.ts +10 -0
  247. package/lib/types/semantic-reference/usage.d.ts +43 -0
  248. package/lib/types/tool-lifecycle.d.ts +4 -0
  249. package/lib/types/workspace-identity.d.ts +6 -0
  250. package/lib/workspace-identity.js +9 -0
  251. package/package.json +99 -53
  252. package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
  253. package/skills/dsh-data-analysis-files/SKILL.md +32 -0
  254. package/skills/dsh-data-analysis-files/references/examples.md +93 -0
  255. package/skills/dsh-data-analysis-presentation/SKILL.md +28 -0
  256. package/skills/dsh-data-analysis-presentation/references/chart-examples.md +18 -0
  257. package/skills/dsh-data-analysis-presentation/references/charts.md +55 -0
  258. package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +36 -0
  259. package/skills/dsh-data-analysis-presentation/references/examples/charts-bins.dataset.json +33 -0
  260. package/skills/dsh-data-analysis-presentation/references/examples/charts-boxes.dataset.json +45 -0
  261. package/skills/dsh-data-analysis-presentation/references/examples/charts-composition.dataset.json +33 -0
  262. package/skills/dsh-data-analysis-presentation/references/examples/charts-funnel.dataset.json +27 -0
  263. package/skills/dsh-data-analysis-presentation/references/examples/charts-precision.dataset.json +21 -0
  264. package/skills/dsh-data-analysis-presentation/references/examples/charts-series.dataset.json +58 -0
  265. package/skills/dsh-data-analysis-presentation/references/examples/charts-shares.dataset.json +33 -0
  266. package/skills/dsh-data-analysis-presentation/references/examples/charts-waterfall.dataset.json +44 -0
  267. package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +393 -0
  268. package/skills/dsh-data-analysis-presentation/references/examples/comparison-selected.dataset.json +24 -0
  269. package/skills/dsh-data-analysis-presentation/references/examples/comparison-summary.dataset.json +22 -0
  270. package/skills/dsh-data-analysis-presentation/references/examples/comparison.dataset.json +18 -0
  271. package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +80 -0
  272. package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +42 -0
  273. package/skills/dsh-data-analysis-presentation/references/examples/computed.dataset.json +12 -0
  274. package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +58 -0
  275. package/skills/dsh-data-analysis-presentation/references/examples/interaction-detail.dataset.json +30 -0
  276. package/skills/dsh-data-analysis-presentation/references/examples/interaction-summary.dataset.json +22 -0
  277. package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +279 -0
  278. package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +27 -0
  279. package/skills/dsh-data-analysis-presentation/references/examples/write-charts.py +73 -0
  280. package/skills/dsh-data-analysis-presentation/references/examples/write-comparison.py +42 -0
  281. package/skills/dsh-data-analysis-presentation/references/examples/write-computed.py +21 -0
  282. package/skills/dsh-data-analysis-presentation/references/examples/write-interaction.py +33 -0
  283. package/skills/dsh-data-analysis-presentation/references/examples.md +44 -0
  284. package/skills/dsh-data-analysis-presentation/references/export-html.md +7 -0
  285. package/skills/dsh-data-analysis-presentation/references/interaction.md +56 -0
  286. package/skills/dsh-data-analysis-presentation/references/narrative.md +37 -0
  287. package/skills/dsh-data-analysis-presentation/references/publishing.md +3 -0
  288. package/skills/dsh-data-analysis-presentation/references/schema.md +162 -0
  289. package/lib/datasource/access.js +0 -60
  290. package/lib/datasource/credentials.js +0 -24
  291. package/lib/evidence/bridge-program.js +0 -153
  292. package/lib/evidence/bridge.js +0 -166
  293. package/lib/evidence/index.js +0 -2
  294. package/lib/evidence/sources.js +0 -263
  295. package/lib/types/datasource/access.d.ts +0 -27
  296. package/lib/types/evidence/bridge.d.ts +0 -40
  297. package/lib/types/evidence/index.d.ts +0 -4
  298. package/lib/types/evidence/sources.d.ts +0 -57
  299. package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
  300. package/skills/dsh-data-analysis-report/SKILL.md +0 -57
  301. package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +0 -113
  302. package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +0 -1201
  303. package/skills/dsh-data-analysis-report/assets/report-data.js +0 -449
@@ -0,0 +1,106 @@
1
+ import { realpath } from 'node:fs/promises';
2
+ import { finishCleanup, PendingTasks } from "../lifecycle.js";
3
+ import { boundedText, closed } from "../semantic-reference/contracts.js";
4
+ import { semanticEnvironmentFingerprint } from "../semantic-reference/environment.js";
5
+ import { abortable } from "../semantic-reference/rpc.js";
6
+ import { CATALOG_MAX_BYTES, parseCatalogFailure, parseCatalogProjection, } from "./contracts.js";
7
+ import { BROWSER_CATALOG_PROGRAM } from "./program.js";
8
+ export class SemanticCatalogLoadError extends Error {
9
+ code = 'catalog-load-failed';
10
+ diagnostic;
11
+ constructor(diagnostic) {
12
+ super(diagnostic);
13
+ this.diagnostic = diagnostic;
14
+ this.name = 'SemanticCatalogLoadError';
15
+ }
16
+ }
17
+ /** Workspace-addressed read service; intentionally has no Agent or credential dependency. */
18
+ export class SemanticBrowserService {
19
+ #host;
20
+ #lifetime = new AbortController();
21
+ #tasks = new PendingTasks();
22
+ #closing;
23
+ constructor(host) {
24
+ this.#host = host;
25
+ }
26
+ #assertOwner(id, workspacePath, root) {
27
+ const current = this.#host.getWorkspace(id);
28
+ if (!current || current.path !== workspacePath || this.#host.projectRoot(current) !== root)
29
+ throw new Error('workspace-changed');
30
+ }
31
+ read(payload, caller) {
32
+ return this.#tasks.track(this.#read(payload, caller));
33
+ }
34
+ async #read(payload, caller) {
35
+ const signal = AbortSignal.any([caller, this.#lifetime.signal]);
36
+ signal.throwIfAborted();
37
+ const request = closed(payload, ['workspaceId']);
38
+ const id = boundedText(request.workspaceId, 256);
39
+ const workspace = this.#host.getWorkspace(id);
40
+ if (!workspace)
41
+ throw new Error('workspace-unavailable');
42
+ const root = this.#host.projectRoot(workspace);
43
+ const runner = await abortable(this.#tasks.track(this.#host.resolve(root)), signal);
44
+ this.#assertOwner(id, workspace.path, root);
45
+ if (runner.status !== 'ready')
46
+ throw new Error('environment-failed');
47
+ const result = await runner.runChecked({
48
+ program: BROWSER_CATALOG_PROGRAM,
49
+ args: [runner.binding.projectRoot],
50
+ signal,
51
+ environmentOverlay: { MARIVO_TELEMETRY: 'off', PYTHONDONTWRITEBYTECODE: '1' },
52
+ limits: { timeoutMs: 30_000, stdoutMaxBytes: CATALOG_MAX_BYTES, stderrMaxBytes: 8192 },
53
+ });
54
+ signal.throwIfAborted();
55
+ if ((await realpath(root)) !== runner.binding.projectRoot)
56
+ throw new Error('workspace-changed');
57
+ this.#assertOwner(id, workspace.path, root);
58
+ signal.throwIfAborted();
59
+ if (result.exitCode !== 0) {
60
+ const failure = (() => {
61
+ try {
62
+ return parseCatalogFailure(JSON.parse(result.stdout.toString('utf8')));
63
+ }
64
+ catch {
65
+ return undefined;
66
+ }
67
+ })();
68
+ if (failure)
69
+ throw new SemanticCatalogLoadError(failure.message);
70
+ throw new Error('catalog-load-failed');
71
+ }
72
+ if (result.stdout.byteLength > CATALOG_MAX_BYTES)
73
+ throw new Error('catalog-too-large');
74
+ const projection = parseCatalogProjection(JSON.parse(result.stdout.toString('utf8')));
75
+ return {
76
+ ...projection,
77
+ workspaceId: id,
78
+ projectRoot: runner.binding.projectRoot,
79
+ environmentFingerprint: semanticEnvironmentFingerprint(id, runner.binding.fingerprint),
80
+ loadedAt: new Date().toISOString(),
81
+ };
82
+ }
83
+ close() {
84
+ this.dispose();
85
+ this.#closing ??= finishCleanup([() => this.#tasks.drain()]);
86
+ return this.#closing;
87
+ }
88
+ dispose() {
89
+ this.#lifetime.abort();
90
+ }
91
+ }
92
+ /** Preserve Marivo semantic diagnostics; keep unknown Python failures behind a fixed fallback. */
93
+ export function browserFailure(error) {
94
+ const code = error instanceof Error && 'code' in error ? error.code : undefined;
95
+ if (error instanceof SemanticCatalogLoadError)
96
+ return `语义层模型加载失败:\n${error.diagnostic}`;
97
+ if (code === 'subprocess-timeout')
98
+ return '语义层加载超时,请检查模型后重试。';
99
+ if (code === 'subprocess-output-limit' ||
100
+ (error instanceof Error && error.message === 'catalog-too-large'))
101
+ return '语义层超过读取大小上限,未加载不完整目录。';
102
+ if (error instanceof Error &&
103
+ ['workspace-unavailable', 'workspace-changed'].includes(error.message))
104
+ return 'Workspace 已不可用或绑定发生变化,请重新选择。';
105
+ return '无法加载语义层,请检查项目模型与 Marivo Runtime 后重试。';
106
+ }
@@ -0,0 +1,127 @@
1
+ import { finishCleanup, PendingTasks } from "../lifecycle.js";
2
+ import { parseProjection } from "./contracts.js";
3
+ export const CATALOG_PROGRAM = String.raw `
4
+ import contextlib, io, json, sys
5
+ with contextlib.redirect_stdout(io.StringIO()):
6
+ import marivo.semantic as ms
7
+ from marivo.refs import RefPayloadV1, SemanticKind
8
+ catalog = ms.load(workspace_dir=sys.argv[1])
9
+ rows = []
10
+ for kind in SemanticKind:
11
+ for entry in catalog.items(kind).items:
12
+ definition = entry.details().context.business_definition
13
+ rows.append({"ref": RefPayloadV1.from_ref(entry.ref).to_dict(), "refKey": entry.ref.key,
14
+ "name": entry.name, "businessDefinition": definition[:240] if definition else None})
15
+ result = {"kinds": [kind.value for kind in SemanticKind], "items": rows}
16
+ print(json.dumps(result, ensure_ascii=True))
17
+ `.trim();
18
+ /** One plugin-owned cache, keyed by the exact Environment fingerprint. */
19
+ export class SemanticReferenceBridge {
20
+ #slots = new Map();
21
+ #clock;
22
+ #disposed = false;
23
+ #tasks = new PendingTasks();
24
+ #closing;
25
+ constructor(clock = Date.now) {
26
+ this.#clock = clock;
27
+ }
28
+ async candidates(runner, signal) {
29
+ signal.throwIfAborted();
30
+ if (this.#disposed)
31
+ throw new Error('disposed');
32
+ const key = runner.binding.fingerprint;
33
+ if (runner.status !== 'ready') {
34
+ this.invalidate(key);
35
+ throw new Error('environment-failed');
36
+ }
37
+ let slot = this.#slots.get(key);
38
+ if (!slot) {
39
+ slot = {};
40
+ this.#slots.set(key, slot);
41
+ }
42
+ if (slot.snapshot && this.#clock() < slot.expires)
43
+ return slot.snapshot;
44
+ if (!slot.flight) {
45
+ const current = slot;
46
+ const controller = new AbortController();
47
+ const flight = {
48
+ controller,
49
+ waiters: 0,
50
+ promise: Promise.resolve({ kinds: [], items: [] }),
51
+ };
52
+ current.flight = flight;
53
+ flight.promise = this.#tasks
54
+ .track(this.#load(runner, controller.signal))
55
+ .then((projection) => {
56
+ if (!controller.signal.aborted &&
57
+ !this.#disposed &&
58
+ current.flight === flight &&
59
+ runner.status === 'ready') {
60
+ current.snapshot = projection;
61
+ current.expires = this.#clock() + 30_000;
62
+ }
63
+ return projection;
64
+ })
65
+ .finally(() => {
66
+ if (current.flight === flight)
67
+ current.flight = undefined;
68
+ });
69
+ }
70
+ const current = slot, flight = slot.flight;
71
+ flight.waiters++;
72
+ return new Promise((resolve, reject) => {
73
+ let settled = false;
74
+ const finish = (value, error) => {
75
+ if (settled)
76
+ return;
77
+ settled = true;
78
+ signal.removeEventListener('abort', abort);
79
+ flight.controller.signal.removeEventListener('abort', loadAbort);
80
+ flight.waiters--;
81
+ if (!flight.waiters && current.flight === flight) {
82
+ current.flight = undefined;
83
+ flight.controller.abort();
84
+ }
85
+ if (error !== undefined)
86
+ reject(error);
87
+ else
88
+ resolve(value);
89
+ };
90
+ const abort = () => finish(undefined, new Error('cancelled'));
91
+ const loadAbort = () => finish(undefined, new Error('cancelled'));
92
+ signal.addEventListener('abort', abort, { once: true });
93
+ flight.controller.signal.addEventListener('abort', loadAbort, { once: true });
94
+ flight.promise.then((value) => finish(value), (error) => finish(undefined, error));
95
+ if (signal.aborted || flight.controller.signal.aborted)
96
+ abort();
97
+ });
98
+ }
99
+ async #load(runner, signal) {
100
+ const result = await runner.runChecked({
101
+ program: CATALOG_PROGRAM,
102
+ args: [runner.binding.projectRoot],
103
+ signal,
104
+ environmentOverlay: { MARIVO_TELEMETRY: 'off', PYTHONDONTWRITEBYTECODE: '1' },
105
+ limits: { timeoutMs: 30_000, stdoutMaxBytes: 32 * 1024 * 1024, stderrMaxBytes: 8192 },
106
+ });
107
+ signal.throwIfAborted();
108
+ if (result.exitCode !== 0)
109
+ throw new Error('catalog-load-failed');
110
+ return parseProjection(JSON.parse(result.stdout.toString('utf8')));
111
+ }
112
+ close() {
113
+ this.dispose();
114
+ this.#closing ??= finishCleanup([() => this.#tasks.drain()]);
115
+ return this.#closing;
116
+ }
117
+ invalidate(key) {
118
+ const slot = this.#slots.get(key);
119
+ this.#slots.delete(key);
120
+ slot?.flight?.controller.abort();
121
+ }
122
+ dispose() {
123
+ this.#disposed = true;
124
+ for (const key of this.#slots.keys())
125
+ this.invalidate(key);
126
+ }
127
+ }
@@ -0,0 +1,127 @@
1
+ /** Browser-safe wire contracts. These bounds do not reimplement Marivo's domain grammar. */
2
+ export const CHANNEL = '/dsh-data-analysis';
3
+ export const SOURCE = 'marivo-semantic';
4
+ export const MAX_WIRE_BYTES = 1024 * 1024;
5
+ export function closed(value, keys) {
6
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
7
+ throw new Error('invalid-request');
8
+ const record = value;
9
+ if (Object.keys(record).length !== keys.length || keys.some((key) => !Object.hasOwn(record, key)))
10
+ throw new Error('invalid-request');
11
+ return record;
12
+ }
13
+ export function boundedText(value, max, empty = false) {
14
+ if (typeof value !== 'string' ||
15
+ value.length > max * 2 ||
16
+ (!empty && value.length === 0) ||
17
+ Array.from(value).length > max)
18
+ throw new Error('invalid-request');
19
+ return value;
20
+ }
21
+ export function parseJson(value, max = 16_384) {
22
+ const text = boundedText(value, max);
23
+ return JSON.parse(text);
24
+ }
25
+ export function parseRef(value) {
26
+ const r = closed(value, ['schema', 'kind', 'path']);
27
+ if (r.schema !== 'marivo.semantic_ref/v1')
28
+ throw new Error('invalid-request');
29
+ return Object.freeze({
30
+ schema: r.schema,
31
+ kind: boundedText(r.kind, 128),
32
+ path: boundedText(r.path, 2048),
33
+ });
34
+ }
35
+ export function refKey(ref) {
36
+ return `${ref.kind}:${ref.path}`;
37
+ }
38
+ export function parseEnvelope(value) {
39
+ const r = closed(value, ['schema', 'sessionId', 'environmentFingerprint', 'ref']);
40
+ if (r.schema !== 'dsh-data-analysis-semantic-reference/v1')
41
+ throw new Error('invalid-request');
42
+ return Object.freeze({
43
+ schema: r.schema,
44
+ sessionId: boundedText(r.sessionId, 256),
45
+ environmentFingerprint: boundedText(r.environmentFingerprint, 256),
46
+ ref: parseRef(r.ref),
47
+ });
48
+ }
49
+ export function envelopeJson(envelope) {
50
+ return JSON.stringify(parseEnvelope(envelope));
51
+ }
52
+ export function modelMarker(envelope) {
53
+ // Keep JSON inside one marker even when a valid domain path contains markup characters.
54
+ const json = JSON.stringify(parseRef(envelope.ref))
55
+ .replaceAll('<', '\\u003c')
56
+ .replaceAll('>', '\\u003e')
57
+ .replaceAll('&', '\\u0026');
58
+ return `<marivo-semantic-ref>${json}</marivo-semantic-ref>`;
59
+ }
60
+ export function parseCandidate(value) {
61
+ const r = closed(value, ['ref', 'refKey', 'name', 'businessDefinition']);
62
+ const ref = parseRef(r.ref);
63
+ if (r.refKey !== refKey(ref))
64
+ throw new Error('invalid-request');
65
+ return Object.freeze({
66
+ ref,
67
+ refKey: r.refKey,
68
+ name: boundedText(r.name, 2048),
69
+ businessDefinition: r.businessDefinition === null ? null : boundedText(r.businessDefinition, 240, true),
70
+ });
71
+ }
72
+ export function parseProjection(value) {
73
+ const r = closed(value, ['kinds', 'items']);
74
+ if (!Array.isArray(r.kinds) ||
75
+ r.kinds.length > 128 ||
76
+ !Array.isArray(r.items) ||
77
+ r.items.length > 100_000)
78
+ throw new Error('invalid-request');
79
+ const kinds = r.kinds.map((kind) => boundedText(kind, 128));
80
+ if (new Set(kinds).size !== kinds.length)
81
+ throw new Error('invalid-request');
82
+ const items = r.items.map(parseCandidate);
83
+ if (new Set(items.map((item) => item.refKey)).size !== items.length ||
84
+ items.some((item) => !kinds.includes(item.ref.kind)))
85
+ throw new Error('invalid-request');
86
+ return Object.freeze({ kinds: Object.freeze(kinds), items: Object.freeze(items) });
87
+ }
88
+ export function parseCandidatesRequest(value) {
89
+ const r = closed(value, ['version', 'sessionId', 'query', 'quoted']);
90
+ if (r.version !== 1 || typeof r.quoted !== 'boolean')
91
+ throw new Error('invalid-request');
92
+ return {
93
+ version: 1,
94
+ sessionId: boundedText(r.sessionId, 256),
95
+ query: boundedText(r.query, 128, true),
96
+ quoted: r.quoted,
97
+ };
98
+ }
99
+ export function parseCandidatesResponse(value) {
100
+ const r = closed(value, ['environmentFingerprint', 'items']);
101
+ if (!Array.isArray(r.items))
102
+ throw new Error('invalid-response');
103
+ const items = r.items.map((raw) => {
104
+ const item = closed(raw, ['ref', 'refKey', 'name', 'businessDefinition', 'section']);
105
+ const { section, ...candidate } = item;
106
+ if (section !== 'recent' && section !== 'kind' && section !== 'strict' && section !== 'fuzzy')
107
+ throw new Error('invalid-response');
108
+ return { ...parseCandidate(candidate), section };
109
+ });
110
+ return {
111
+ environmentFingerprint: boundedText(r.environmentFingerprint, 256),
112
+ items,
113
+ };
114
+ }
115
+ /** Browser selection identity only; preparation never reloads semantic content. */
116
+ export function parsePrepareRequest(value) {
117
+ const r = closed(value, ['sessionId', 'workspaceId', 'environmentFingerprint', 'ref']);
118
+ return {
119
+ workspaceId: boundedText(r.workspaceId, 256),
120
+ envelope: parseEnvelope({
121
+ schema: 'dsh-data-analysis-semantic-reference/v1',
122
+ sessionId: r.sessionId,
123
+ environmentFingerprint: r.environmentFingerprint,
124
+ ref: r.ref,
125
+ }),
126
+ };
127
+ }
@@ -0,0 +1,56 @@
1
+ import { createHash } from 'node:crypto';
2
+ /** Reference identity includes Harness ownership; the underlying Runtime stays unchanged. */
3
+ export function semanticEnvironmentFingerprint(workspaceId, runtimeFingerprint) {
4
+ return createHash('sha256')
5
+ .update(JSON.stringify(['marivo-semantic-workspace/v1', workspaceId, runtimeFingerprint]))
6
+ .digest('hex');
7
+ }
8
+ /** Explicit selection establishes ownership; serialization never repairs it. */
9
+ export function referenceEnvironmentResolver(host) {
10
+ const owners = new WeakMap();
11
+ return async (sessionId, purpose, workspaceId) => {
12
+ const agent = host.agent(sessionId);
13
+ if (!agent)
14
+ throw new Error('unknown-session');
15
+ const priorOwner = owners.get(agent);
16
+ const owner = purpose === 'reference' ? priorOwner?.workspace : host.workspace(sessionId);
17
+ if (purpose === 'prepare' && (!workspaceId || owner?.id !== workspaceId))
18
+ throw new Error('workspace-mismatch');
19
+ const checkOwner = () => {
20
+ if (!owner)
21
+ return;
22
+ const current = host.workspace(sessionId);
23
+ if (current.id !== owner.id || current.path !== owner.path)
24
+ throw new Error('workspace-changed');
25
+ };
26
+ checkOwner();
27
+ const bound = host.binding(agent);
28
+ if (purpose === 'reference' &&
29
+ (!owner || !bound || bound !== priorOwner?.binding || bound.root !== host.projectRoot(agent)))
30
+ throw new Error('environment-unbound');
31
+ const pending = purpose === 'reference' ? bound.environment : host.resolve(agent);
32
+ const expected = host.binding(agent);
33
+ const environment = await pending;
34
+ if (host.agent(sessionId) !== agent ||
35
+ host.binding(agent) !== expected ||
36
+ expected?.root !== host.projectRoot(agent))
37
+ throw new Error('environment-changed');
38
+ checkOwner();
39
+ if (!owner || !expected)
40
+ throw new Error('environment-unbound');
41
+ if (purpose !== 'reference')
42
+ owners.set(agent, { workspace: owner, binding: expected });
43
+ // Only the semantic surface uses the ownership fingerprint. Execution is delegated
44
+ // to the original runner, preserving its Runtime identity and dynamic health.
45
+ return {
46
+ binding: {
47
+ ...environment.binding,
48
+ fingerprint: semanticEnvironmentFingerprint(owner.id, environment.binding.fingerprint),
49
+ },
50
+ get status() {
51
+ return environment.status;
52
+ },
53
+ runChecked: (request) => environment.runChecked(request),
54
+ };
55
+ };
56
+ }
@@ -0,0 +1,3 @@
1
+ export { SemanticReferenceBridge } from "./bridge.js";
2
+ export { registerSemanticReferenceRpc, SemanticReferenceService } from "./rpc.js";
3
+ export { SemanticReferenceUsage } from "./usage.js";
@@ -0,0 +1,16 @@
1
+ /** Shared display labels and search aliases; object kinds still come from the live Catalog. */
2
+ export const semanticKindLabels = {
3
+ domain: '业务域',
4
+ datasource: '数据源',
5
+ entity: '实体',
6
+ dimension: '维度',
7
+ measure: '度量',
8
+ time_dimension: '时间维度',
9
+ metric: '指标',
10
+ relationship: '关系',
11
+ event: '事件',
12
+ state_model: '状态模型',
13
+ period_calendar: '周期日历',
14
+ temporal_set: '时间集合',
15
+ work_schedule: '工作日程',
16
+ };
@@ -0,0 +1,143 @@
1
+ import { finishCleanup, PendingTasks } from "../lifecycle.js";
2
+ import { registerPluginRpc } from "../rpc.js";
3
+ import { SemanticReferenceBridge } from "./bridge.js";
4
+ import { CHANNEL, closed, MAX_WIRE_BYTES, modelMarker, parseCandidatesRequest, parseEnvelope, parsePrepareRequest, } from "./contracts.js";
5
+ import { search } from "./search.js";
6
+ import { workspaceKey } from "./usage.js";
7
+ /** Abort the caller's wait, without pretending to cancel the shared Environment resolver. */
8
+ export async function abortable(promise, signal) {
9
+ return new Promise((resolve, reject) => {
10
+ const abort = () => {
11
+ signal.removeEventListener('abort', abort);
12
+ reject(new Error('cancelled'));
13
+ };
14
+ signal.addEventListener('abort', abort, { once: true });
15
+ promise.then((value) => {
16
+ signal.removeEventListener('abort', abort);
17
+ if (signal.aborted)
18
+ reject(new Error('cancelled'));
19
+ else
20
+ resolve(value);
21
+ }, (error) => {
22
+ signal.removeEventListener('abort', abort);
23
+ reject(error);
24
+ });
25
+ if (signal.aborted)
26
+ abort();
27
+ });
28
+ }
29
+ export class SemanticReferenceService {
30
+ #resolve;
31
+ #usage;
32
+ #bridge;
33
+ #controller = new AbortController();
34
+ #closed = false;
35
+ #tasks = new PendingTasks();
36
+ #closing;
37
+ constructor(resolve, usage, bridge = new SemanticReferenceBridge()) {
38
+ this.#resolve = resolve;
39
+ this.#usage = usage;
40
+ this.#bridge = bridge;
41
+ }
42
+ async #environment(sessionId, signal, purpose = 'reference', workspaceId) {
43
+ const runner = await abortable(this.#tasks.track(this.#resolve(sessionId, purpose, workspaceId)), signal);
44
+ if (runner.status !== 'ready') {
45
+ this.#bridge.invalidate(runner.binding.fingerprint);
46
+ throw new Error('environment-failed');
47
+ }
48
+ return runner;
49
+ }
50
+ async #owner(envelope, signal) {
51
+ const runner = await this.#environment(envelope.sessionId, signal);
52
+ if (runner.binding.fingerprint !== envelope.environmentFingerprint)
53
+ throw new Error('environment-mismatch');
54
+ return runner;
55
+ }
56
+ handle(endpoint, payload, caller) {
57
+ return this.#tasks.track(this.#handle(endpoint, payload, caller));
58
+ }
59
+ async #handle(endpoint, payload, caller) {
60
+ if (this.#closed)
61
+ throw new Error('disposed');
62
+ const signal = AbortSignal.any([caller, this.#controller.signal]);
63
+ signal.throwIfAborted();
64
+ if (Buffer.byteLength(JSON.stringify(payload) ?? '') > MAX_WIRE_BYTES)
65
+ throw new Error('invalid-request');
66
+ if (endpoint === 'semantic-references/prepare') {
67
+ const { workspaceId, envelope } = parsePrepareRequest(payload);
68
+ const runner = await this.#environment(envelope.sessionId, signal, 'prepare', workspaceId);
69
+ if (runner.binding.fingerprint !== envelope.environmentFingerprint)
70
+ throw new Error('environment-mismatch');
71
+ signal.throwIfAborted();
72
+ return { envelope };
73
+ }
74
+ if (endpoint === 'semantic-references/candidates') {
75
+ const request = parseCandidatesRequest(payload);
76
+ const environment = await this.#environment(request.sessionId, signal, 'candidates');
77
+ const projection = await this.#bridge.candidates(environment, signal);
78
+ const scores = await abortable(this.#tasks.track(this.#usage.scores(workspaceKey(environment.binding.projectRoot))), signal);
79
+ // A live Agent may disappear or change binding while Catalog/storage work awaits.
80
+ const current = await this.#environment(request.sessionId, signal);
81
+ if (current.binding.fingerprint !== environment.binding.fingerprint)
82
+ throw new Error('environment-mismatch');
83
+ return {
84
+ environmentFingerprint: environment.binding.fingerprint,
85
+ items: search(projection, request.query, scores),
86
+ };
87
+ }
88
+ if (endpoint === 'semantic-references/selected' ||
89
+ endpoint === 'semantic-references/serialize') {
90
+ const request = closed(payload, ['envelope']), envelope = parseEnvelope(request.envelope);
91
+ const environment = await this.#owner(envelope, signal);
92
+ if (endpoint === 'semantic-references/serialize')
93
+ return { text: modelMarker(envelope) };
94
+ await this.#usage.selected(workspaceKey(environment.binding.projectRoot), envelope.ref, signal);
95
+ return { selected: true };
96
+ }
97
+ throw new Error('unknown-endpoint');
98
+ }
99
+ stop() {
100
+ this.#closed = true;
101
+ this.#controller.abort();
102
+ this.#bridge.dispose();
103
+ }
104
+ close() {
105
+ if (this.#closing)
106
+ return this.#closing;
107
+ this.stop();
108
+ this.#closing = finishCleanup([
109
+ () => this.#bridge.close(),
110
+ () => this.#tasks.drain(),
111
+ () => this.#usage.close(),
112
+ ]);
113
+ return this.#closing;
114
+ }
115
+ }
116
+ export function registerSemanticReferenceRpc(connection, service, browser) {
117
+ const unregister = registerPluginRpc(connection, CHANNEL, [
118
+ 'semantic-references/prepare',
119
+ 'semantic-references/candidates',
120
+ 'semantic-references/selected',
121
+ 'semantic-references/serialize',
122
+ 'semantic-browser/catalog',
123
+ ], async (endpoint, payload, signal) => {
124
+ if (browser && endpoint === 'semantic-browser/catalog') {
125
+ return browser(endpoint, payload, signal);
126
+ }
127
+ try {
128
+ return { ok: true, value: await service.handle(endpoint, payload, signal) };
129
+ }
130
+ catch {
131
+ return {
132
+ ok: false,
133
+ error: {
134
+ code: 'internal',
135
+ message: 'Marivo semantic reference request failed',
136
+ details: {},
137
+ },
138
+ };
139
+ }
140
+ });
141
+ let closing;
142
+ return () => (closing ??= finishCleanup([() => service.stop(), unregister, () => service.close()]));
143
+ }
@@ -0,0 +1,95 @@
1
+ import { semanticKindLabels } from "./labels.js";
2
+ export function normalize(text) {
3
+ return text.normalize('NFKC').toLowerCase().trim().replace(/\s+/gu, ' ');
4
+ }
5
+ function tokens(text) {
6
+ return text.split(/[\s.:_-]+/u).filter(Boolean);
7
+ }
8
+ export function compareText(a, b) {
9
+ return a < b ? -1 : a > b ? 1 : 0;
10
+ }
11
+ function length(text) {
12
+ return Array.from(text).length;
13
+ }
14
+ export function dice(a, b) {
15
+ const grams = (text) => {
16
+ const chars = Array.from(text);
17
+ return new Set(chars.slice(0, -2).map((_, i) => chars.slice(i, i + 3).join('')));
18
+ };
19
+ const left = grams(a), right = grams(b);
20
+ if (!left.size || !right.size)
21
+ return 0;
22
+ return (2 * [...left].filter((gram) => right.has(gram)).length) / (left.size + right.size);
23
+ }
24
+ function match(item, query) {
25
+ const name = normalize(item.name), path = normalize(item.ref.path), key = normalize(item.refKey);
26
+ const fields = [name, path, key];
27
+ const label = Object.hasOwn(semanticKindLabels, item.ref.kind)
28
+ ? semanticKindLabels[item.ref.kind]
29
+ : '';
30
+ const all = [...fields, normalize(item.ref.kind), normalize(label)];
31
+ const searchTokens = all.flatMap(tokens);
32
+ if (fields.includes(query))
33
+ return { item, tier: 0, score: 1, field: 0 };
34
+ const prefixes = [...fields, ...searchTokens]
35
+ .map((value, field) => ({ value, field }))
36
+ .filter(({ value }) => value.startsWith(query));
37
+ if (prefixes.length) {
38
+ prefixes.sort((a, b) => length(a.value) - length(b.value) || a.field - b.field);
39
+ const best = prefixes[0];
40
+ return {
41
+ item,
42
+ tier: 1,
43
+ score: length(query) / length(best.value),
44
+ field: best.field < 3 ? best.field : 3,
45
+ };
46
+ }
47
+ const document = [...all, normalize(item.businessDefinition ?? '')].join(' ');
48
+ const qt = tokens(query);
49
+ if (qt.length && qt.every((token) => document.includes(token)))
50
+ return {
51
+ item,
52
+ tier: 2,
53
+ score: qt.reduce((sum, token) => sum + length(token), 0) / Math.max(1, length(document)),
54
+ field: 0,
55
+ };
56
+ return {
57
+ item,
58
+ tier: 3,
59
+ score: length(query) < 3
60
+ ? 0
61
+ : Math.max(...[name, ...tokens(path), path].map((value) => dice(query, value))),
62
+ field: 0,
63
+ };
64
+ }
65
+ export function search(projection, rawQuery, usage = new Map()) {
66
+ const query = normalize(rawQuery);
67
+ const heat = (a, b) => (usage.get(b.refKey)?.count ?? 0) - (usage.get(a.refKey)?.count ?? 0) ||
68
+ (usage.get(b.refKey)?.last ?? 0) - (usage.get(a.refKey)?.last ?? 0);
69
+ const fallback = (a, b) => projection.kinds.indexOf(a.ref.kind) - projection.kinds.indexOf(b.ref.kind) ||
70
+ compareText(a.refKey, b.refKey);
71
+ if (!query) {
72
+ const recent = projection.items
73
+ .filter((item) => (usage.get(item.refKey)?.count ?? 0) > 0)
74
+ .sort((a, b) => heat(a, b) || fallback(a, b))
75
+ .slice(0, 10);
76
+ const keys = new Set(recent.map((item) => item.refKey));
77
+ return [
78
+ ...recent.map((item) => ({ ...item, section: 'recent' })),
79
+ ...projection.items
80
+ .filter((item) => !keys.has(item.refKey))
81
+ .sort(fallback)
82
+ .map((item) => ({ ...item, section: 'kind' })),
83
+ ];
84
+ }
85
+ const matches = projection.items.map((item) => match(item, query));
86
+ const strictCount = matches.filter((item) => item.tier < 3).length;
87
+ return matches
88
+ .filter((m) => m.tier < 3 || (strictCount < 12 && m.score >= 0.42))
89
+ .sort((a, b) => a.tier - b.tier ||
90
+ b.score - a.score ||
91
+ a.field - b.field ||
92
+ heat(a.item, b.item) ||
93
+ compareText(a.item.refKey, b.item.refKey))
94
+ .map((m) => ({ ...m.item, section: m.tier === 3 ? 'fuzzy' : 'strict' }));
95
+ }