@ltm-blueverse/alpha-semantic-hub 0.7.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 (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1674 -0
  3. package/explorer/README.md +282 -0
  4. package/explorer/src/App.css +0 -0
  5. package/explorer/src/App.tsx +2035 -0
  6. package/explorer/src/ErrorBoundary.tsx +112 -0
  7. package/explorer/src/ExploreWorkspaceTabs.tsx +29 -0
  8. package/explorer/src/assets/hero.png +0 -0
  9. package/explorer/src/assets/react.svg +1 -0
  10. package/explorer/src/assets/vite.svg +1 -0
  11. package/explorer/src/explorerCapabilities.ts +24 -0
  12. package/explorer/src/index.css +71 -0
  13. package/explorer/src/main.tsx +7 -0
  14. package/explorer/src/store/edgePairKeys.d.ts +2 -0
  15. package/explorer/src/store/edgePairKeys.js +7 -0
  16. package/explorer/src/store/graphStore.ts +183 -0
  17. package/explorer/src/store/registryStore.ts +78 -0
  18. package/explorer/src/types.d.ts +16 -0
  19. package/explorer/src/ui/primitives.css +414 -0
  20. package/explorer/src/ui/primitives.tsx +170 -0
  21. package/explorer/src/workspaces/DecisionWorkspace/DecisionWorkspace.tsx +279 -0
  22. package/explorer/src/workspaces/DiffMergeWorkspace/DiffMergeWorkspace.tsx +121 -0
  23. package/explorer/src/workspaces/EnrichWorkspace/EntityResolutionTab.tsx +454 -0
  24. package/explorer/src/workspaces/EnrichWorkspace/RegistryTab.tsx +285 -0
  25. package/explorer/src/workspaces/GraphWorkspace/GraphCanvas.tsx +2673 -0
  26. package/explorer/src/workspaces/GraphWorkspace/GraphInspectorPanel.tsx +771 -0
  27. package/explorer/src/workspaces/GraphWorkspace/GraphLoadingOverlay.tsx +410 -0
  28. package/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx +3584 -0
  29. package/explorer/src/workspaces/GraphWorkspace/MarkdownContentViewer.tsx +705 -0
  30. package/explorer/src/workspaces/GraphWorkspace/SigmaSceneAdapter.tsx +55 -0
  31. package/explorer/src/workspaces/GraphWorkspace/TimelinePanel.tsx +193 -0
  32. package/explorer/src/workspaces/GraphWorkspace/behaviors/clickSelectionBehavior.ts +29 -0
  33. package/explorer/src/workspaces/GraphWorkspace/behaviors/fitViewBehavior.ts +15 -0
  34. package/explorer/src/workspaces/GraphWorkspace/behaviors/focusCameraBehavior.ts +25 -0
  35. package/explorer/src/workspaces/GraphWorkspace/behaviors/hoverActivationBehavior.ts +15 -0
  36. package/explorer/src/workspaces/GraphWorkspace/behaviors/pathHighlightBehavior.ts +52 -0
  37. package/explorer/src/workspaces/GraphWorkspace/behaviors/searchFocusBehavior.ts +36 -0
  38. package/explorer/src/workspaces/GraphWorkspace/behaviors/types.ts +41 -0
  39. package/explorer/src/workspaces/GraphWorkspace/behaviors/viewModeSwitchBehavior.ts +29 -0
  40. package/explorer/src/workspaces/GraphWorkspace/graphAnalytics.ts +779 -0
  41. package/explorer/src/workspaces/GraphWorkspace/graphColorLegend.ts +36 -0
  42. package/explorer/src/workspaces/GraphWorkspace/graphConfig.ts +102 -0
  43. package/explorer/src/workspaces/GraphWorkspace/graphEntityShape.ts +34 -0
  44. package/explorer/src/workspaces/GraphWorkspace/graphLoading.ts +84 -0
  45. package/explorer/src/workspaces/GraphWorkspace/graphSceneLayers.ts +775 -0
  46. package/explorer/src/workspaces/GraphWorkspace/graphSceneState.ts +2753 -0
  47. package/explorer/src/workspaces/GraphWorkspace/graphStructureLayer.ts +335 -0
  48. package/explorer/src/workspaces/GraphWorkspace/graphTheme.ts +1078 -0
  49. package/explorer/src/workspaces/GraphWorkspace/graphViewCopy.ts +41 -0
  50. package/explorer/src/workspaces/GraphWorkspace/localGraphTransition.ts +9 -0
  51. package/explorer/src/workspaces/GraphWorkspace/markdownEditorState.ts +114 -0
  52. package/explorer/src/workspaces/GraphWorkspace/markdownResourceClient.ts +103 -0
  53. package/explorer/src/workspaces/GraphWorkspace/markdownUrlSafety.ts +29 -0
  54. package/explorer/src/workspaces/GraphWorkspace/nodeMarkdownSync.ts +81 -0
  55. package/explorer/src/workspaces/GraphWorkspace/pluginRegistryPredicates.ts +24 -0
  56. package/explorer/src/workspaces/GraphWorkspace/plugins/explorationEffectsPlugin.tsx +353 -0
  57. package/explorer/src/workspaces/GraphWorkspace/plugins/explorationEffectsPluginPhaseC.tsx +522 -0
  58. package/explorer/src/workspaces/GraphWorkspace/plugins/index.ts +16 -0
  59. package/explorer/src/workspaces/GraphWorkspace/plugins/legendPlugin.tsx +120 -0
  60. package/explorer/src/workspaces/GraphWorkspace/plugins/neighborhoodPanelPlugin.tsx +231 -0
  61. package/explorer/src/workspaces/GraphWorkspace/plugins/temporalOverlayPlugin.tsx +142 -0
  62. package/explorer/src/workspaces/GraphWorkspace/plugins/types.ts +106 -0
  63. package/explorer/src/workspaces/GraphWorkspace/realtimeGraphAttributes.ts +50 -0
  64. package/explorer/src/workspaces/GraphWorkspace/scene.ts +79 -0
  65. package/explorer/src/workspaces/GraphWorkspace/sigmaNativeRendering.ts +421 -0
  66. package/explorer/src/workspaces/GraphWorkspace/smallGraphLayout.ts +135 -0
  67. package/explorer/src/workspaces/GraphWorkspace/temporalLifecyclePredicates.ts +31 -0
  68. package/explorer/src/workspaces/GraphWorkspace/temporalScrubberBounds.ts +46 -0
  69. package/explorer/src/workspaces/GraphWorkspace/temporalSnapshotGuards.ts +113 -0
  70. package/explorer/src/workspaces/GraphWorkspace/types.ts +395 -0
  71. package/explorer/src/workspaces/GraphWorkspace/useLoadGraph.ts +746 -0
  72. package/explorer/src/workspaces/GraphWorkspace/useMarkdownEditor.ts +177 -0
  73. package/explorer/src/workspaces/ImportExportWorkspace/ImportExportWorkspace.tsx +196 -0
  74. package/explorer/src/workspaces/LineageWorkspace/LineageDiagram.tsx +177 -0
  75. package/explorer/src/workspaces/ManageWorkspace/KGOverviewTab.tsx +291 -0
  76. package/explorer/src/workspaces/ManageWorkspace/OntologySummaryTab.tsx +346 -0
  77. package/explorer/src/workspaces/MemoryWorkspace.tsx +464 -0
  78. package/explorer/src/workspaces/OntologyWorkspace/AlignmentsTab.tsx +437 -0
  79. package/explorer/src/workspaces/OntologyWorkspace/HealthTab.tsx +214 -0
  80. package/explorer/src/workspaces/OntologyWorkspace/OntologyEditor.tsx +770 -0
  81. package/explorer/src/workspaces/OntologyWorkspace/OntologyLoader.tsx +913 -0
  82. package/explorer/src/workspaces/OntologyWorkspace/OntologyManager.tsx +930 -0
  83. package/explorer/src/workspaces/OntologyWorkspace/OntologySearch.tsx +590 -0
  84. package/explorer/src/workspaces/OntologyWorkspace/ProposalReview.tsx +429 -0
  85. package/explorer/src/workspaces/OntologyWorkspace/SKOSVocabularyManager.tsx +696 -0
  86. package/explorer/src/workspaces/OntologyWorkspace/ShaclStudio.tsx +363 -0
  87. package/explorer/src/workspaces/OntologyWorkspace/VersionsTab.tsx +546 -0
  88. package/explorer/src/workspaces/OntologyWorkspace/api.ts +164 -0
  89. package/explorer/src/workspaces/OntologyWorkspace/index.tsx +101 -0
  90. package/explorer/src/workspaces/OntologyWorkspace/ontologyEditorModel.ts +87 -0
  91. package/explorer/src/workspaces/OntologyWorkspace/ontologyUrlState.ts +94 -0
  92. package/explorer/src/workspaces/OntologyWorkspace/types.ts +119 -0
  93. package/explorer/src/workspaces/ReasoningWorkspace.tsx +243 -0
  94. package/explorer/src/workspaces/SparqlWorkspace/SparqlWorkspace.tsx +271 -0
  95. package/explorer/src/workspaces/VocabularyWorkspace/ConceptTree.tsx +97 -0
  96. package/explorer/src/workspaces/VocabularyWorkspace/ImportDropzone.tsx +95 -0
  97. package/explorer/src/workspaces/VocabularyWorkspace/PropertyPanel.tsx +116 -0
  98. package/explorer/src/workspaces/VocabularyWorkspace/Sidebar.tsx +112 -0
  99. package/explorer/src/workspaces/VocabularyWorkspace/VocabularyWorkspace.tsx +70 -0
  100. package/explorer/src/workspaces/VocabularyWorkspace/queries.ts +52 -0
  101. package/explorer/src/workspaces/VocabularyWorkspace/types.ts +24 -0
  102. package/package.json +31 -0
  103. package/plugins/.claude-plugin/README.md +262 -0
  104. package/plugins/.claude-plugin/marketplace.json +20 -0
  105. package/plugins/.claude-plugin/plugin.json +33 -0
  106. package/plugins/.cline-plugin/README.md +32 -0
  107. package/plugins/.cline-plugin/marketplace.json +17 -0
  108. package/plugins/.cline-plugin/plugin.json +35 -0
  109. package/plugins/.codex-plugin/marketplace.json +21 -0
  110. package/plugins/.codex-plugin/plugin.json +35 -0
  111. package/plugins/.continue-plugin/README.md +34 -0
  112. package/plugins/.continue-plugin/marketplace.json +17 -0
  113. package/plugins/.continue-plugin/plugin.json +33 -0
  114. package/plugins/.cursor-plugin/marketplace.json +18 -0
  115. package/plugins/.cursor-plugin/plugin.json +32 -0
  116. package/plugins/.openclaw-plugin/README.md +62 -0
  117. package/plugins/.openclaw-plugin/marketplace.json +18 -0
  118. package/plugins/.openclaw-plugin/plugin.json +46 -0
  119. package/plugins/.pi-plugin/README.md +35 -0
  120. package/plugins/.vscode-plugin/README.md +48 -0
  121. package/plugins/.vscode-plugin/marketplace.json +17 -0
  122. package/plugins/.vscode-plugin/plugin.json +35 -0
  123. package/plugins/.windsurf-plugin/README.md +39 -0
  124. package/plugins/.windsurf-plugin/marketplace.json +17 -0
  125. package/plugins/.windsurf-plugin/plugin.json +35 -0
  126. package/plugins/agents/decision-advisor.md +126 -0
  127. package/plugins/agents/explainability.md +129 -0
  128. package/plugins/agents/kg-assistant.md +73 -0
  129. package/plugins/hooks/hooks.json +12 -0
  130. package/plugins/skills/causal/SKILL.md +74 -0
  131. package/plugins/skills/change/SKILL.md +80 -0
  132. package/plugins/skills/decision/SKILL.md +197 -0
  133. package/plugins/skills/deduplicate/SKILL.md +41 -0
  134. package/plugins/skills/embed/SKILL.md +230 -0
  135. package/plugins/skills/explain/SKILL.md +44 -0
  136. package/plugins/skills/export/SKILL.md +67 -0
  137. package/plugins/skills/extract/SKILL.md +93 -0
  138. package/plugins/skills/ingest/SKILL.md +38 -0
  139. package/plugins/skills/ontology/SKILL.md +89 -0
  140. package/plugins/skills/policy/SKILL.md +66 -0
  141. package/plugins/skills/provenance/SKILL.md +70 -0
  142. package/plugins/skills/query/SKILL.md +84 -0
  143. package/plugins/skills/reason/SKILL.md +201 -0
  144. package/plugins/skills/temporal/SKILL.md +164 -0
  145. package/plugins/skills/validate/SKILL.md +228 -0
  146. package/plugins/skills/visualize/SKILL.md +249 -0
@@ -0,0 +1,164 @@
1
+ import type {
2
+ AlignmentRelation,
3
+ AlignmentSuggestion,
4
+ OntologyAlignment,
5
+ OntologyEntry,
6
+ OntologyHealthResponse,
7
+ ShaclGenerateResponse,
8
+ ShaclShapesResponse,
9
+ ShaclValidationResponse,
10
+ } from "./types";
11
+
12
+ export type OntologyGraphNode = {
13
+ id: string;
14
+ type: string;
15
+ // Required: /graph always classifies. Optional here would make a producer
16
+ // that forgets read as a graph of read-only nodes rather than a type error.
17
+ entity_type: string;
18
+ content?: string;
19
+ properties?: Record<string, unknown>;
20
+ };
21
+
22
+ export type OntologyGraphEdge = {
23
+ id?: string;
24
+ source: string;
25
+ target: string;
26
+ type: string;
27
+ weight?: number;
28
+ properties?: Record<string, unknown>;
29
+ };
30
+
31
+ export type OntologyGraphResponse = {
32
+ uri: string;
33
+ nodes: OntologyGraphNode[];
34
+ edges: OntologyGraphEdge[];
35
+ };
36
+
37
+ export type OntologyEntityOwner = {
38
+ // Optional on purpose, unlike OntologyGraphNode.entity_type. There, a missing
39
+ // field degrades to a read-only node — benign. Here it would be read as an
40
+ // authoritative "nothing owns this entity", which now suppresses selection
41
+ // outright, so presence has to be checked rather than assumed.
42
+ owning_ontology?: string | null;
43
+ };
44
+
45
+ async function parseResponse<T>(response: Response): Promise<T> {
46
+ if (!response.ok) {
47
+ let detail = `Request failed with status ${response.status}`;
48
+ try {
49
+ const body = await response.json();
50
+ detail = body.detail || detail;
51
+ } catch {
52
+ // Keep the generic HTTP detail.
53
+ }
54
+ throw new Error(detail);
55
+ }
56
+ const data = await response.json();
57
+ if (response.status === 207) {
58
+ console.warn("Partial Success:", data.message || "Warning: 207 Multi-Status");
59
+ }
60
+ return data as T;
61
+ }
62
+
63
+ export async function loadOntologyRegistry(): Promise<OntologyEntry[]> {
64
+ return parseResponse<OntologyEntry[]>(await fetch("/api/ontology/registry"));
65
+ }
66
+
67
+ export async function loadOntologyGraph(uri: string, signal?: AbortSignal): Promise<OntologyGraphResponse> {
68
+ return parseResponse<OntologyGraphResponse>(
69
+ await fetch(`/api/ontology/graph?uri=${encodeURIComponent(uri)}`, { signal }),
70
+ );
71
+ }
72
+
73
+ // Three-state verdict: a string names the owner, null is the backend's
74
+ // authoritative "no known ontology owns this entity", and undefined means the
75
+ // request failed so there is no verdict to act on.
76
+ export type OntologyOwnerVerdict = string | null | undefined;
77
+
78
+ export async function loadOntologyEntityOwner(uri: string): Promise<OntologyOwnerVerdict> {
79
+ const response = await fetch(`/api/ontology/entity/${encodeURIComponent(uri)}`);
80
+ if (!response.ok) return undefined;
81
+ const owner = await response.json() as OntologyEntityOwner | null;
82
+ // Only a field that is actually there carries the verdict. Coercing an absent
83
+ // field to null would assert the strongest available claim — "nothing owns
84
+ // this" — on the weakest possible evidence, and that claim now stops the
85
+ // editor selecting an ontology at all.
86
+ const verdict = owner?.owning_ontology;
87
+ return verdict === undefined ? undefined : verdict;
88
+ }
89
+
90
+ export async function loadAlignments(uri?: string): Promise<OntologyAlignment[]> {
91
+ const query = uri ? `?uri=${encodeURIComponent(uri)}` : "";
92
+ return parseResponse<OntologyAlignment[]>(await fetch(`/api/ontology/alignments${query}`));
93
+ }
94
+
95
+ export async function saveAlignment(payload: {
96
+ source_uri: string;
97
+ target_uri: string;
98
+ relation: AlignmentRelation;
99
+ confidence: number;
100
+ provenance?: string;
101
+ source?: string;
102
+ reviewer?: string;
103
+ }): Promise<OntologyAlignment> {
104
+ return parseResponse<OntologyAlignment>(
105
+ await fetch("/api/ontology/alignments", {
106
+ method: "POST",
107
+ headers: { "Content-Type": "application/json" },
108
+ body: JSON.stringify(payload),
109
+ }),
110
+ );
111
+ }
112
+
113
+ export async function removeAlignment(id: string): Promise<void> {
114
+ await parseResponse<{ status: string }>(
115
+ await fetch(`/api/ontology/alignments?id=${encodeURIComponent(id)}`, { method: "DELETE" }),
116
+ );
117
+ }
118
+
119
+ export async function suggestAlignments(payload: {
120
+ source_ontology_uri?: string;
121
+ target_ontology_uri?: string;
122
+ threshold: number;
123
+ limit: number;
124
+ }): Promise<AlignmentSuggestion[]> {
125
+ return parseResponse<AlignmentSuggestion[]>(
126
+ await fetch("/api/ontology/suggest-alignments", {
127
+ method: "POST",
128
+ headers: { "Content-Type": "application/json" },
129
+ body: JSON.stringify(payload),
130
+ }),
131
+ );
132
+ }
133
+
134
+ export async function loadOntologyHealth(uri: string): Promise<OntologyHealthResponse> {
135
+ return parseResponse<OntologyHealthResponse>(
136
+ await fetch(`/api/ontology/health?uri=${encodeURIComponent(uri)}`),
137
+ );
138
+ }
139
+
140
+ export async function generateShacl(uri: string, qualityTier: "standard" | "strict" = "strict"): Promise<ShaclGenerateResponse> {
141
+ return parseResponse<ShaclGenerateResponse>(
142
+ await fetch("/api/ontology/shacl/generate", {
143
+ method: "POST",
144
+ headers: { "Content-Type": "application/json" },
145
+ body: JSON.stringify({ uri, quality_tier: qualityTier }),
146
+ }),
147
+ );
148
+ }
149
+
150
+ export async function loadShaclShapes(uri: string): Promise<ShaclShapesResponse> {
151
+ return parseResponse<ShaclShapesResponse>(
152
+ await fetch(`/api/ontology/shacl/shapes?uri=${encodeURIComponent(uri)}`),
153
+ );
154
+ }
155
+
156
+ export async function validateShacl(uri: string, shaclTurtle: string): Promise<ShaclValidationResponse> {
157
+ return parseResponse<ShaclValidationResponse>(
158
+ await fetch("/api/ontology/shacl/validate", {
159
+ method: "POST",
160
+ headers: { "Content-Type": "application/json" },
161
+ body: JSON.stringify({ uri, shacl_turtle: shaclTurtle }),
162
+ }),
163
+ );
164
+ }
@@ -0,0 +1,101 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import {
3
+ BookMarked,
4
+ GitMerge,
5
+ HeartPulse,
6
+ Layers,
7
+ Shield,
8
+ Sliders,
9
+ } from "lucide-react";
10
+ import { AlignmentsTab } from "./AlignmentsTab";
11
+ import { HealthTab } from "./HealthTab";
12
+ import { OntologyManager } from "./OntologyManager";
13
+ import { OntologyEditor } from "./OntologyEditor";
14
+ import { ShaclStudio } from "./ShaclStudio";
15
+ import { VersionsTab } from "./VersionsTab";
16
+ import { readOntologyUrlState, writeEntitySelection, writeTab } from "./ontologyUrlState";
17
+
18
+ export type OntologyHubTab =
19
+ | "registry"
20
+ | "editor"
21
+ | "versions"
22
+ | "alignments"
23
+ | "health"
24
+ | "shacl";
25
+
26
+ const TABS: { id: OntologyHubTab; label: string; icon: typeof GitMerge }[] = [
27
+ { id: "registry", label: "Registry", icon: BookMarked },
28
+ { id: "editor", label: "Editor", icon: Sliders },
29
+ { id: "versions", label: "Versions", icon: Layers },
30
+ { id: "alignments", label: "Alignments", icon: GitMerge },
31
+ { id: "health", label: "Health", icon: HeartPulse },
32
+ { id: "shacl", label: "SHACL", icon: Shield },
33
+ ];
34
+
35
+ function readInitialTab(): OntologyHubTab {
36
+ const { tab, entityUri } = readOntologyUrlState();
37
+ const requested = TABS.find((candidate) => candidate.id === tab);
38
+ if (requested) return requested.id;
39
+ if (entityUri) return "editor";
40
+ return "registry";
41
+ }
42
+
43
+ interface OntologyWorkspaceProps {
44
+ onJumpToGraphNode?: (nodeId: string) => void;
45
+ }
46
+
47
+ export function OntologyWorkspace({ onJumpToGraphNode }: OntologyWorkspaceProps) {
48
+ const [activeTab, setActiveTab] = useState<OntologyHubTab>(readInitialTab);
49
+
50
+ useEffect(() => {
51
+ writeTab(activeTab);
52
+ }, [activeTab]);
53
+
54
+ const handleTabChange = useCallback((tab: OntologyHubTab) => {
55
+ setActiveTab(tab);
56
+ }, []);
57
+
58
+ const handleFixInEditor = useCallback((entityUri: string) => {
59
+ writeEntitySelection(entityUri);
60
+ setActiveTab("editor");
61
+ }, []);
62
+
63
+ const renderTab = () => {
64
+ switch (activeTab) {
65
+ case "registry":
66
+ return <OntologyManager />;
67
+ case "editor":
68
+ return <OntologyEditor />;
69
+ case "versions":
70
+ return <VersionsTab />;
71
+ case "alignments":
72
+ return <AlignmentsTab />;
73
+ case "health":
74
+ return <HealthTab onFixInEditor={handleFixInEditor} />;
75
+ case "shacl":
76
+ return <ShaclStudio onJumpToNode={onJumpToGraphNode} />;
77
+ }
78
+ };
79
+
80
+ return (
81
+ <div className="ws-page">
82
+ {/* Internal sub-tab bar */}
83
+ <div style={{ display: "flex", gap: 4, padding: "8px 16px", borderBottom: "1px solid var(--ws-border)", background: "rgba(0,0,0,0.18)", flexShrink: 0, flexWrap: "wrap" }}>
84
+ {TABS.map(({ id, label, icon: Icon }) => {
85
+ const active = activeTab === id;
86
+ return (
87
+ <button
88
+ key={id}
89
+ onClick={() => handleTabChange(id)}
90
+ style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "6px 13px", borderRadius: 999, border: `1px solid ${active ? "var(--ws-border-strong)" : "transparent"}`, background: active ? "var(--ws-accent-soft)" : "transparent", color: active ? "var(--ws-text)" : "var(--ws-text-muted)", fontSize: 12, fontWeight: 600, cursor: "pointer", transition: "160ms ease" }}
91
+ >
92
+ <Icon size={13} />
93
+ {label}
94
+ </button>
95
+ );
96
+ })}
97
+ </div>
98
+ <div style={{ flex: 1, minHeight: 0, overflow: "hidden" }}>{renderTab()}</div>
99
+ </div>
100
+ );
101
+ }
@@ -0,0 +1,87 @@
1
+ export type EditorEntityType = "ontology" | "class" | "property" | "external";
2
+
3
+ export type RegistryEntry = {
4
+ uri: string;
5
+ name: string;
6
+ };
7
+
8
+ export const ONTOLOGY_MINIMAP_THEME = {
9
+ bgColor: "#0b1625",
10
+ maskColor: "rgba(11, 5, 8, 0.72)",
11
+ maskStrokeColor: "#5faeff",
12
+ maskStrokeWidth: 2,
13
+ nodeColor: "#2d7fd3",
14
+ nodeStrokeColor: "#9acbff",
15
+ nodeStrokeWidth: 1,
16
+ style: {
17
+ border: "1px solid #29435c",
18
+ borderRadius: 6,
19
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.32)",
20
+ },
21
+ } as const;
22
+
23
+ // Last-resort guess, reached only when the backend gave no verdict: it has no
24
+ // notion of nested vocabularies, so it can name a parent that does not contain
25
+ // the entity. Authority is owning_ontology from /api/ontology/entity
26
+ // (_resolve_owning_ontology in alphasemantichub/explorer/routes/ontology.py).
27
+ function ownsByNamespace(entityUri: string, ontologyUri: string): boolean {
28
+ const stem = ontologyUri.replace(/[/#]+$/, "");
29
+ return entityUri === ontologyUri
30
+ || entityUri.startsWith(`${stem}#`)
31
+ || entityUri.startsWith(`${stem}/`);
32
+ }
33
+
34
+ /**
35
+ * Three outcomes, deliberately not collapsed into `string | undefined`.
36
+ *
37
+ * `unowned` and `unresolved` both yield "no ontology to open", but they must
38
+ * not be treated alike: a caller that writes `resolve(...) || entries[0]` turns
39
+ * the backend's authoritative "nothing owns this entity" into "open an
40
+ * arbitrary ontology", which reintroduces the parent-selection bug this whole
41
+ * verdict exists to prevent. The union makes that collapse a type error.
42
+ */
43
+ export type EditorOntologyResolution =
44
+ | { status: "resolved"; uri: string }
45
+ | { status: "unowned"; entityUri: string }
46
+ | { status: "unresolved" };
47
+
48
+ // Picks the registered ontology to open for a deep-linked entity. A null
49
+ // verdict is the backend's authoritative "nothing owns this entity": the
50
+ // namespace guess must stay suppressed, or an unregistered nested namespace
51
+ // would select its registered parent again. Only an unavailable verdict
52
+ // (undefined) may fall back to inference.
53
+ export function resolveEditorOntology(
54
+ entries: RegistryEntry[],
55
+ entityUri: string,
56
+ ownerVerdict: string | null | undefined,
57
+ ): EditorOntologyResolution {
58
+ if (ownerVerdict === null) {
59
+ return { status: "unowned", entityUri };
60
+ }
61
+ const uri = inferOntologyUri(entries, entityUri, ownerVerdict);
62
+ return uri === undefined ? { status: "unresolved" } : { status: "resolved", uri };
63
+ }
64
+
65
+ // Picks the registered ontology to open for an entity: the backend-resolved
66
+ // explicitOwner wins outright, the namespace guess is only the fallback.
67
+ export function inferOntologyUri(
68
+ entries: RegistryEntry[],
69
+ entityUri: string,
70
+ explicitOwner?: string,
71
+ ): string | undefined {
72
+ // Trusted even when the registry does not list it. Falling through to the
73
+ // namespace guess here would answer a question nobody asked — the backend
74
+ // named this entity's owner, and silently opening a *different* ontology is
75
+ // worse than opening one the registry has not been told about yet, which
76
+ // surfaces as an explicit error from /api/ontology/graph.
77
+ if (explicitOwner) {
78
+ return explicitOwner;
79
+ }
80
+ return [...entries]
81
+ .filter((entry) => ownsByNamespace(entityUri, entry.uri))
82
+ .sort((left, right) => right.uri.length - left.uri.length)[0]?.uri;
83
+ }
84
+
85
+ export function isEditableEntityType(entityType?: EditorEntityType): boolean {
86
+ return entityType === "class" || entityType === "property";
87
+ }
@@ -0,0 +1,94 @@
1
+ // Sole owner of the Ontology Hub deep-link query parameters: the names below must not be
2
+ // spelled out anywhere else, so that the protocol can change in one place.
3
+ const TAB_PARAM = "ontologyTab";
4
+ const ENTITY_PARAM = "ontologyEntity";
5
+ const EDITOR_TAB = "editor";
6
+
7
+ export interface OntologyUrlState {
8
+ /** Raw parameter value; the set of legal tab ids belongs to the workspace, not this module. */
9
+ tab?: string;
10
+ entityUri?: string;
11
+ }
12
+
13
+ /** `undefined` means the parameter is absent; an empty string means it is present but blank. */
14
+ export function parseOntologyUrlState(search: string): OntologyUrlState {
15
+ const params = new URLSearchParams(search);
16
+ return {
17
+ tab: params.get(TAB_PARAM) ?? undefined,
18
+ entityUri: params.get(ENTITY_PARAM) ?? undefined,
19
+ };
20
+ }
21
+
22
+ export function applyTab(search: string, tab: string): string {
23
+ const params = new URLSearchParams(search);
24
+ params.set(TAB_PARAM, tab);
25
+ return `?${params.toString()}`;
26
+ }
27
+
28
+ // A selected entity is only addressable from the editor, so the tab moves with it.
29
+ export function applyEntitySelection(search: string, entityUri: string): string {
30
+ const params = new URLSearchParams(search);
31
+ params.set(TAB_PARAM, EDITOR_TAB);
32
+ params.set(ENTITY_PARAM, entityUri);
33
+ return `?${params.toString()}`;
34
+ }
35
+
36
+ // Pairs with applyEntitySelection: an entity URI is resolved back to its owning ontology on
37
+ // load, so leaving a stale one behind when the active ontology changes reopens the old ontology.
38
+ export function removeEntitySelection(search: string): string {
39
+ const params = new URLSearchParams(search);
40
+ params.delete(ENTITY_PARAM);
41
+ return `?${params.toString()}`;
42
+ }
43
+
44
+ /**
45
+ * Deliberately dual-role, and the argument is what selects the role: given a
46
+ * `search` string this is pure and total, delegating straight to
47
+ * `parseOntologyUrlState`; called with no argument it reads live `window`
48
+ * state and yields empty state if the URL is unreadable. Callers in render or
49
+ * effect paths use the no-argument form; tests and any caller that already
50
+ * holds a search string pass it, which is the only form that is testable.
51
+ */
52
+ export function readOntologyUrlState(search?: string): OntologyUrlState {
53
+ if (search !== undefined) {
54
+ return parseOntologyUrlState(search);
55
+ }
56
+ try {
57
+ return parseOntologyUrlState(window.location.search);
58
+ } catch {
59
+ return {};
60
+ }
61
+ }
62
+
63
+ /** True when the URL addresses the Ontology Hub at all, even with blank parameter values. */
64
+ export function hasOntologyUrlState(search?: string): boolean {
65
+ const { tab, entityUri } = readOntologyUrlState(search);
66
+ return tab !== undefined || entityUri !== undefined;
67
+ }
68
+
69
+ // The transform returns a query string only, so the fragment has to be carried
70
+ // across explicitly: replaceState with a bare "?..." drops it. This is the one
71
+ // place that knows how the URL is written, so it is the only place that can.
72
+ function updateSearch(transform: (search: string) => string): void {
73
+ try {
74
+ window.history.replaceState(
75
+ null,
76
+ "",
77
+ `${transform(window.location.search)}${window.location.hash}`,
78
+ );
79
+ } catch {
80
+ // Deep-link state is a convenience; every caller stays correct without it.
81
+ }
82
+ }
83
+
84
+ export function writeTab(tab: string): void {
85
+ updateSearch((search) => applyTab(search, tab));
86
+ }
87
+
88
+ export function writeEntitySelection(entityUri: string): void {
89
+ updateSearch((search) => applyEntitySelection(search, entityUri));
90
+ }
91
+
92
+ export function clearEntitySelection(): void {
93
+ updateSearch(removeEntitySelection);
94
+ }
@@ -0,0 +1,119 @@
1
+ export interface OntologyEntry {
2
+ uri: string;
3
+ name: string;
4
+ description?: string;
5
+ format: string;
6
+ status: "published" | "draft" | "external";
7
+ source_url?: string;
8
+ version?: string;
9
+ class_count: number;
10
+ concept_count: number;
11
+ property_count: number;
12
+ loaded_at: string;
13
+ enabled: boolean;
14
+ tags: string[];
15
+ }
16
+
17
+ export type AlignmentRelation =
18
+ | "owl:equivalentClass"
19
+ | "owl:equivalentProperty"
20
+ | "skos:exactMatch"
21
+ | "skos:closeMatch"
22
+ | "skos:broadMatch"
23
+ | "skos:narrowMatch"
24
+ | "skos:relatedMatch";
25
+
26
+ export interface OntologyAlignment {
27
+ id: string;
28
+ source_uri: string;
29
+ source_label: string;
30
+ target_uri: string;
31
+ target_label: string;
32
+ relation: AlignmentRelation;
33
+ predicate_uri: string;
34
+ confidence: number;
35
+ provenance?: string;
36
+ source?: string;
37
+ reviewer?: string;
38
+ created_at: string;
39
+ updated_at: string;
40
+ }
41
+
42
+ export interface AlignmentSuggestion {
43
+ source_uri: string;
44
+ source_label: string;
45
+ target_uri: string;
46
+ target_label: string;
47
+ relation: AlignmentRelation;
48
+ score: number;
49
+ label_similarity: number;
50
+ embedding_similarity?: number | null;
51
+ reason: string;
52
+ }
53
+
54
+ export interface HealthDimension {
55
+ key: string;
56
+ label: string;
57
+ score: number;
58
+ status: "ok" | "warning" | "critical" | "unavailable";
59
+ detail: string;
60
+ }
61
+
62
+ export interface HealthIssue {
63
+ id: string;
64
+ severity: "info" | "warning" | "critical";
65
+ category: string;
66
+ entity_uri?: string;
67
+ entity_label?: string;
68
+ message: string;
69
+ action?: string;
70
+ }
71
+
72
+ export interface OntologyHealthResponse {
73
+ uri: string;
74
+ name: string;
75
+ total_score: number;
76
+ dimensions: HealthDimension[];
77
+ issues: HealthIssue[];
78
+ generated_at: string;
79
+ }
80
+
81
+ export interface ShaclShapeSummary {
82
+ id: string;
83
+ target_class?: string;
84
+ constraint_count: number;
85
+ constraints: string[];
86
+ violation_count: number;
87
+ }
88
+
89
+ export interface ShaclViolation {
90
+ node?: string;
91
+ path?: string;
92
+ severity: string;
93
+ message: string;
94
+ focus_node?: string;
95
+ source_shape?: string;
96
+ }
97
+
98
+ export interface ShaclGenerateResponse {
99
+ uri: string;
100
+ shacl_turtle: string;
101
+ shape_count: number;
102
+ generated_at: string;
103
+ }
104
+
105
+ export interface ShaclShapesResponse {
106
+ uri: string;
107
+ shapes: ShaclShapeSummary[];
108
+ shacl_turtle: string;
109
+ generated_at: string;
110
+ }
111
+
112
+ export interface ShaclValidationResponse {
113
+ uri?: string;
114
+ conforms: boolean;
115
+ status: "success" | "unavailable" | "error";
116
+ message: string;
117
+ violations: ShaclViolation[];
118
+ report_text?: string;
119
+ }