@donartcha/openlag 0.1.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 (99) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +82 -0
  3. package/bin/openlag.js +2 -0
  4. package/dist/assets/arc-4YUHkXo3.js +1 -0
  5. package/dist/assets/architectureDiagram-3BPJPVTR-WeGmL7HM.js +36 -0
  6. package/dist/assets/blockDiagram-GPEHLZMM-CtV7ubAx.js +132 -0
  7. package/dist/assets/c4Diagram-AAUBKEIU-DqYDW5c3.js +10 -0
  8. package/dist/assets/channel-Tsel3-MK.js +1 -0
  9. package/dist/assets/chunk-2J33WTMH-BE8P9tjh.js +1 -0
  10. package/dist/assets/chunk-4BX2VUAB-Bi7oLGF5.js +1 -0
  11. package/dist/assets/chunk-55IACEB6-D9Xhxp_r.js +1 -0
  12. package/dist/assets/chunk-727SXJPM-Dz8jKE60.js +206 -0
  13. package/dist/assets/chunk-AQP2D5EJ-BzmM0IeH.js +231 -0
  14. package/dist/assets/chunk-FMBD7UC4-Cvl5dpcx.js +15 -0
  15. package/dist/assets/chunk-ND2GUHAM-Dz2efqnq.js +1 -0
  16. package/dist/assets/chunk-QZHKN3VN-CwblgSnQ.js +1 -0
  17. package/dist/assets/classDiagram-4FO5ZUOK-Bgm-_cW8.js +1 -0
  18. package/dist/assets/classDiagram-v2-Q7XG4LA2-Bgm-_cW8.js +1 -0
  19. package/dist/assets/cose-bilkent-S5V4N54A-h_A3nZUx.js +1 -0
  20. package/dist/assets/cytoscape.esm-D_LviqZs.js +331 -0
  21. package/dist/assets/dagre-BM42HDAG-CN_B2Doz.js +4 -0
  22. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  23. package/dist/assets/diagram-2AECGRRQ-C9TAFwjG.js +43 -0
  24. package/dist/assets/diagram-5GNKFQAL-BThljQLo.js +10 -0
  25. package/dist/assets/diagram-KO2AKTUF-bRPq25Se.js +3 -0
  26. package/dist/assets/diagram-LMA3HP47-BubLCIus.js +24 -0
  27. package/dist/assets/diagram-OG6HWLK6-CJpfhIsS.js +24 -0
  28. package/dist/assets/erDiagram-TEJ5UH35-6Xkza9wL.js +85 -0
  29. package/dist/assets/flowDiagram-I6XJVG4X-Bq_to3hX.js +162 -0
  30. package/dist/assets/ganttDiagram-6RSMTGT7-C3CmvYl7.js +292 -0
  31. package/dist/assets/gitGraphDiagram-PVQCEYII-C93LTfrl.js +106 -0
  32. package/dist/assets/graph-CAnANduQ.js +1 -0
  33. package/dist/assets/index-0RMQQ34p.css +1 -0
  34. package/dist/assets/index-ByxguSZe.js +729 -0
  35. package/dist/assets/infoDiagram-5YYISTIA-CMfuwygl.js +2 -0
  36. package/dist/assets/init-Gi6I4Gst.js +1 -0
  37. package/dist/assets/ishikawaDiagram-YF4QCWOH-CbJ5ojDF.js +70 -0
  38. package/dist/assets/journeyDiagram-JHISSGLW-C_Xz8YyT.js +139 -0
  39. package/dist/assets/kanban-definition-UN3LZRKU-GVv_iRMq.js +89 -0
  40. package/dist/assets/katex-DkKDou_j.js +257 -0
  41. package/dist/assets/layout-DGIYPm2g.js +1 -0
  42. package/dist/assets/linear-BNEtUH2J.js +1 -0
  43. package/dist/assets/mindmap-definition-RKZ34NQL-DIsL0XSF.js +96 -0
  44. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  45. package/dist/assets/pieDiagram-4H26LBE5-CSCTSOjk.js +30 -0
  46. package/dist/assets/quadrantDiagram-W4KKPZXB-CQQ9OaFY.js +7 -0
  47. package/dist/assets/requirementDiagram-4Y6WPE33-Cjn3la_S.js +84 -0
  48. package/dist/assets/sankeyDiagram-5OEKKPKP-DoVspvVc.js +40 -0
  49. package/dist/assets/sequenceDiagram-3UESZ5HK-UsoGmL4w.js +162 -0
  50. package/dist/assets/stateDiagram-AJRCARHV-DLmf7Dc8.js +1 -0
  51. package/dist/assets/stateDiagram-v2-BHNVJYJU-jkiDZ_3u.js +1 -0
  52. package/dist/assets/timeline-definition-PNZ67QCA-HfyRxZ8p.js +120 -0
  53. package/dist/assets/vennDiagram-CIIHVFJN-B6pM3L33.js +34 -0
  54. package/dist/assets/wardley-L42UT6IY-B-LdKtrI.js +173 -0
  55. package/dist/assets/wardleyDiagram-YWT4CUSO-BD45zhOu.js +78 -0
  56. package/dist/assets/xychartDiagram-2RQKCTM6-zsDMbUiS.js +7 -0
  57. package/dist/cli/openlag.js +1793 -0
  58. package/dist/index.html +14 -0
  59. package/index.html +13 -0
  60. package/package.json +84 -0
  61. package/scripts/cli/build.ts +34 -0
  62. package/scripts/cli/dev.ts +35 -0
  63. package/scripts/cli/generate.ts +92 -0
  64. package/scripts/cli/init.ts +427 -0
  65. package/scripts/cli/lint.ts +29 -0
  66. package/scripts/cli/openlag.ts +110 -0
  67. package/scripts/cli/vite-bin.ts +8 -0
  68. package/scripts/core/parser/diagnostic.ts +34 -0
  69. package/scripts/core/parser/normalizer.ts +27 -0
  70. package/scripts/core/parser/scanner.ts +30 -0
  71. package/scripts/core/parser/schemas.ts +23 -0
  72. package/scripts/core/parser/types.ts +30 -0
  73. package/scripts/core/parser.ts +127 -0
  74. package/scripts/generate-relations.ts +53 -0
  75. package/scripts/lint/lint-engine.ts +85 -0
  76. package/scripts/lint/lint-profiles.ts +49 -0
  77. package/scripts/lint/lint-rules.ts +174 -0
  78. package/scripts/lint/lint-types.ts +43 -0
  79. package/src/App.tsx +164 -0
  80. package/src/components/DocumentationView.tsx +905 -0
  81. package/src/components/GraphView.tsx +529 -0
  82. package/src/components/GuideView.tsx +535 -0
  83. package/src/components/ImpactView.tsx +365 -0
  84. package/src/components/MarkdownRenderer.tsx +120 -0
  85. package/src/components/OrphansView.tsx +360 -0
  86. package/src/components/SettingsView.tsx +146 -0
  87. package/src/core/generated/relation-definitions.ts +622 -0
  88. package/src/core/graph/GraphQueryLayer.ts +194 -0
  89. package/src/core/registry/ArtifactRegistry.ts +19 -0
  90. package/src/core/registry/RelationRegistry.ts +27 -0
  91. package/src/core/semantic/artifact-layers.ts +43 -0
  92. package/src/core/semantic/ownership-rules.ts +13 -0
  93. package/src/core/semantic/types.ts +11 -0
  94. package/src/index.css +121 -0
  95. package/src/lib/reportUtils.ts +59 -0
  96. package/src/main.tsx +10 -0
  97. package/src/store.ts +146 -0
  98. package/src/types.ts +77 -0
  99. package/vite.config.ts +31 -0
@@ -0,0 +1,194 @@
1
+ import { GraphSnapshot, Artifact, Relation } from "../../types.js";
2
+
3
+ export interface GraphIndex {
4
+ artifactsById: Map<string, Artifact>;
5
+ relationsBySource: Map<string, Relation[]>;
6
+ relationsByTarget: Map<string, Relation[]>;
7
+ artifactsByType: Map<string, Artifact[]>;
8
+ artifactsByLayer: Map<string, Artifact[]>;
9
+ artifactsByStatus: Map<string, Artifact[]>;
10
+ artifactsByOwner: Map<string, Artifact[]>;
11
+ artifactsByTeam: Map<string, Artifact[]>;
12
+ }
13
+
14
+ export interface GraphQueryOptions {
15
+ focusId?: string;
16
+ depth?: number;
17
+ maxNodes?: number;
18
+ showWeakRelations?: boolean;
19
+ filterLayer?: string;
20
+ filterOwner?: string;
21
+ filterTeam?: string;
22
+ }
23
+
24
+ export const MAX_RENDER_NODES = 150;
25
+ export const MAX_RENDER_EDGES = 300;
26
+ export const DEFAULT_NEIGHBORHOOD_DEPTH = 1;
27
+ export const MAX_EXPANSION_DEPTH = 3;
28
+ export const HUB_COLLAPSE_THRESHOLD = 25;
29
+ export const WEAK_RELATIONS_VISIBLE_BY_DEFAULT = false;
30
+
31
+ const WEAK_CATEGORIES = ['SEMANTIC', 'TRACEABILITY', 'DEPENDENCY'];
32
+
33
+ export function buildGraphIndex(graph: GraphSnapshot): GraphIndex {
34
+ const index: GraphIndex = {
35
+ artifactsById: new Map(),
36
+ relationsBySource: new Map(),
37
+ relationsByTarget: new Map(),
38
+ artifactsByType: new Map(),
39
+ artifactsByLayer: new Map(),
40
+ artifactsByStatus: new Map(),
41
+ artifactsByOwner: new Map(),
42
+ artifactsByTeam: new Map(),
43
+ };
44
+
45
+ graph.artifacts.forEach(a => {
46
+ index.artifactsById.set(a.id, a);
47
+
48
+ if (a.type) {
49
+ if (!index.artifactsByType.has(a.type)) index.artifactsByType.set(a.type, []);
50
+ index.artifactsByType.get(a.type)!.push(a);
51
+ }
52
+
53
+ if (a.layer) {
54
+ if (!index.artifactsByLayer.has(a.layer)) index.artifactsByLayer.set(a.layer, []);
55
+ index.artifactsByLayer.get(a.layer)!.push(a);
56
+ }
57
+
58
+ if (a.status) {
59
+ if (!index.artifactsByStatus.has(a.status)) index.artifactsByStatus.set(a.status, []);
60
+ index.artifactsByStatus.get(a.status)!.push(a);
61
+ }
62
+
63
+ if (a.ownership?.owner) {
64
+ if (!index.artifactsByOwner.has(a.ownership.owner)) index.artifactsByOwner.set(a.ownership.owner, []);
65
+ index.artifactsByOwner.get(a.ownership.owner)!.push(a);
66
+ }
67
+
68
+ if (a.ownership?.team) {
69
+ if (!index.artifactsByTeam.has(a.ownership.team)) index.artifactsByTeam.set(a.ownership.team, []);
70
+ index.artifactsByTeam.get(a.ownership.team)!.push(a);
71
+ }
72
+ });
73
+
74
+ graph.relations.forEach(r => {
75
+ if (!index.relationsBySource.has(r.from)) index.relationsBySource.set(r.from, []);
76
+ index.relationsBySource.get(r.from)!.push(r);
77
+
78
+ if (!index.relationsByTarget.has(r.to)) index.relationsByTarget.set(r.to, []);
79
+ index.relationsByTarget.get(r.to)!.push(r);
80
+ });
81
+
82
+ return index;
83
+ }
84
+
85
+ function isRelationAllowed(rel: Relation, options: GraphQueryOptions): boolean {
86
+ if (!options.showWeakRelations && WEAK_CATEGORIES.includes(rel.strength || (rel.category === 'SEMANTIC' ? 'WEAK' : 'STRONG'))) {
87
+ // we need to rely on rel config here, wait rel has only type?
88
+ // we should use a registry maybe, but for now let's just make it generic
89
+ // wait relation has type, category is not always present in data, but it might be.
90
+ // Assuming strength is mapped somehow or we check relation type
91
+ if (['RELATES_TO', 'DOCUMENTS'].includes(rel.type)) {
92
+ return false;
93
+ }
94
+ }
95
+ return true;
96
+ }
97
+
98
+ export function projectSubgraph(graph: GraphSnapshot, index: GraphIndex, options: GraphQueryOptions): GraphSnapshot {
99
+ const {
100
+ focusId,
101
+ depth = DEFAULT_NEIGHBORHOOD_DEPTH,
102
+ filterLayer = 'ALL',
103
+ filterOwner = 'ALL',
104
+ filterTeam = 'ALL'
105
+ } = options;
106
+
107
+ let artifacts = new Set<Artifact>();
108
+ let relations = new Set<Relation>();
109
+
110
+ const isArtifactAllowed = (a: Artifact) => {
111
+ if (filterLayer !== 'ALL' && a.layer !== filterLayer) return false;
112
+ if (filterOwner !== 'ALL' && a.ownership?.owner !== filterOwner) return false;
113
+ if (filterTeam !== 'ALL' && a.ownership?.team !== filterTeam) return false;
114
+ return true;
115
+ };
116
+
117
+ if (focusId) {
118
+ // Neighborhood exploration
119
+ const root = index.artifactsById.get(focusId);
120
+ if (!root || !isArtifactAllowed(root)) return { artifacts: [], relations: [] };
121
+
122
+ artifacts.add(root);
123
+ let queue: { id: string, currentDepth: number }[] = [{ id: focusId, currentDepth: 0 }];
124
+ const visited = new Set<string>();
125
+
126
+ while (queue.length > 0) {
127
+ const { id, currentDepth } = queue.shift()!;
128
+ if (visited.has(id)) continue;
129
+ visited.add(id);
130
+
131
+ if (currentDepth >= depth) continue;
132
+
133
+ const outgoing = index.relationsBySource.get(id) || [];
134
+ const incoming = index.relationsByTarget.get(id) || [];
135
+
136
+ [...outgoing, ...incoming].forEach(rel => {
137
+ if (!isRelationAllowed(rel, options)) return;
138
+
139
+ const otherId = rel.from === id ? rel.to : rel.from;
140
+ const otherArt = index.artifactsById.get(otherId);
141
+
142
+ if (otherArt && isArtifactAllowed(otherArt)) {
143
+ relations.add(rel);
144
+ artifacts.add(otherArt);
145
+ if (!visited.has(otherId)) {
146
+ queue.push({ id: otherId, currentDepth: currentDepth + 1 });
147
+ }
148
+ }
149
+ });
150
+ }
151
+
152
+ } else {
153
+ // If no focus, return all (subject to limits and filters layer)
154
+ // and hide weak relations if flag is off
155
+ graph.artifacts.forEach(a => {
156
+ if (isArtifactAllowed(a)) artifacts.add(a);
157
+ });
158
+ graph.relations.forEach(r => {
159
+ const from = index.artifactsById.get(r.from);
160
+ const to = index.artifactsById.get(r.to);
161
+ if (from && to && isArtifactAllowed(from) && isArtifactAllowed(to) && isRelationAllowed(r, options)) {
162
+ relations.add(r);
163
+ }
164
+ });
165
+
166
+ // If still too large, we might truncate to prevent crash, though graph limits are handled later or gracefully
167
+ if (artifacts.size > MAX_RENDER_NODES && !focusId) {
168
+ const allArtifacts = Array.from(artifacts).slice(0, MAX_RENDER_NODES);
169
+ const allowedIds = new Set(allArtifacts.map(a => a.id));
170
+ const filteredRelations = Array.from(relations).filter(r => allowedIds.has(r.from) && allowedIds.has(r.to));
171
+
172
+ return {
173
+ artifacts: allArtifacts,
174
+ relations: filteredRelations
175
+ };
176
+ }
177
+ }
178
+
179
+ // Hub suppression or capping
180
+ if (artifacts.size > MAX_RENDER_NODES) {
181
+ const allArtifacts = Array.from(artifacts).slice(0, MAX_RENDER_NODES);
182
+ const allowedIds = new Set(allArtifacts.map(a => a.id));
183
+ const filteredRelations = Array.from(relations).filter(r => allowedIds.has(r.from) && allowedIds.has(r.to));
184
+ return {
185
+ artifacts: allArtifacts,
186
+ relations: filteredRelations
187
+ };
188
+ }
189
+
190
+ return {
191
+ artifacts: Array.from(artifacts),
192
+ relations: Array.from(relations)
193
+ };
194
+ }
@@ -0,0 +1,19 @@
1
+ export const CORE_ARTIFACT_TYPES = [
2
+ 'PROJECT', 'EPIC', 'FEATURE', 'REQUIREMENT', 'BUSINESS_RULE', 'USE_CASE',
3
+ 'DESIGN', 'DECISION', 'CODE_ENTITY', 'TEST_CASE', 'CHANGE', 'BUG', 'RISK',
4
+ 'GLOSSARY_TERM', 'COMPONENT', 'API', 'DATABASE_ENTITY', 'TEST',
5
+ 'DOCUMENTATION', 'INCIDENT', 'INFRASTRUCTURE', 'DEPLOYMENT', 'MONITORING',
6
+ 'MAINTENANCE', 'SYSTEM_VERSION', 'VERSION', 'LIBRARY', 'ENVIRONMENT', 'CHECK', 'PROCESS', 'PIPELINE'
7
+ ] as const;
8
+
9
+ export type ArtifactType = typeof CORE_ARTIFACT_TYPES[number];
10
+
11
+ export class ArtifactRegistry {
12
+ static getValidTypes(): readonly string[] {
13
+ return CORE_ARTIFACT_TYPES;
14
+ }
15
+
16
+ static isValid(type: string): type is ArtifactType {
17
+ return CORE_ARTIFACT_TYPES.includes(type as ArtifactType);
18
+ }
19
+ }
@@ -0,0 +1,27 @@
1
+ import { RelationContract, GENERATED_RELATIONS } from '../generated/relation-definitions.js';
2
+
3
+ export class RelationRegistry {
4
+ private static relationsMap = new Map<string, RelationContract>(
5
+ GENERATED_RELATIONS.map(rel => [rel.type, rel])
6
+ );
7
+
8
+ static getValidTypes(): string[] {
9
+ return Array.from(this.relationsMap.keys());
10
+ }
11
+
12
+ static getContract(type: string): RelationContract | undefined {
13
+ return this.relationsMap.get(type);
14
+ }
15
+
16
+ static isValid(type: string): boolean {
17
+ return this.relationsMap.has(type);
18
+ }
19
+
20
+ static getRelationsByCategory(category: string): RelationContract[] {
21
+ return GENERATED_RELATIONS.filter(rel => rel.category === category);
22
+ }
23
+
24
+ static getAllContracts(): RelationContract[] {
25
+ return GENERATED_RELATIONS;
26
+ }
27
+ }
@@ -0,0 +1,43 @@
1
+ import { ArtifactType, ArtifactLayer } from '../../types.js';
2
+
3
+ export const LayerInferenceRules: Record<ArtifactType, ArtifactLayer> = {
4
+ PROJECT: 'BUSINESS',
5
+ EPIC: 'BUSINESS',
6
+ FEATURE: 'BUSINESS',
7
+ REQUIREMENT: 'BUSINESS',
8
+ BUSINESS_RULE: 'BUSINESS',
9
+ USE_CASE: 'BUSINESS',
10
+
11
+ DESIGN: 'ARCHITECTURE',
12
+ DECISION: 'ARCHITECTURE',
13
+ COMPONENT: 'ARCHITECTURE',
14
+ API: 'ARCHITECTURE',
15
+
16
+ CODE_ENTITY: 'IMPLEMENTATION',
17
+ DATABASE_ENTITY: 'IMPLEMENTATION',
18
+ TEST_CASE: 'IMPLEMENTATION',
19
+ TEST: 'IMPLEMENTATION',
20
+ CHANGE: 'IMPLEMENTATION',
21
+ BUG: 'IMPLEMENTATION',
22
+ RISK: 'IMPLEMENTATION',
23
+
24
+ INCIDENT: 'OPERATIONS',
25
+ INFRASTRUCTURE: 'OPERATIONS',
26
+ DEPLOYMENT: 'OPERATIONS',
27
+ MONITORING: 'OPERATIONS',
28
+ MAINTENANCE: 'OPERATIONS',
29
+ SYSTEM_VERSION: 'OPERATIONS',
30
+
31
+ GLOSSARY_TERM: 'DOCUMENTATION',
32
+ DOCUMENTATION: 'DOCUMENTATION',
33
+ VERSION: 'DOCUMENTATION',
34
+ LIBRARY: 'IMPLEMENTATION',
35
+ ENVIRONMENT: 'OPERATIONS',
36
+ CHECK: 'OPERATIONS',
37
+ PROCESS: 'OPERATIONS',
38
+ PIPELINE: 'OPERATIONS',
39
+ };
40
+
41
+ export function inferLayer(artifactType: ArtifactType): ArtifactLayer | undefined {
42
+ return LayerInferenceRules[artifactType];
43
+ }
@@ -0,0 +1,13 @@
1
+ import { Ownership } from '../../types.js';
2
+
3
+ export function resolveArtifactOwnership(localOwnership?: Ownership, parentOwnership?: Ownership): Ownership {
4
+ if (!localOwnership && !parentOwnership) return {};
5
+
6
+ return {
7
+ owner: localOwnership?.owner || parentOwnership?.owner,
8
+ team: localOwnership?.team || parentOwnership?.team,
9
+ maintainers: localOwnership?.maintainers || parentOwnership?.maintainers || [],
10
+ reviewers: localOwnership?.reviewers || parentOwnership?.reviewers || [],
11
+ steward: localOwnership?.steward || parentOwnership?.steward,
12
+ };
13
+ }
@@ -0,0 +1,11 @@
1
+ export type ArtifactLayer = 'BUSINESS' | 'ARCHITECTURE' | 'IMPLEMENTATION' | 'OPERATIONS' | 'DOCUMENTATION';
2
+ export type RelationCategory = 'STRUCTURAL' | 'BEHAVIORAL' | 'OPERATIONAL' | 'TRACEABILITY';
3
+ export type RelationStrength = 'STRONG' | 'MEDIUM' | 'WEAK';
4
+
5
+ export interface Ownership {
6
+ owner?: string;
7
+ team?: string;
8
+ maintainers?: string[];
9
+ reviewers?: string[];
10
+ steward?: string;
11
+ }
package/src/index.css ADDED
@@ -0,0 +1,121 @@
1
+ @import "tailwindcss";
2
+
3
+ @layer base {
4
+ ::-webkit-scrollbar {
5
+ width: 6px;
6
+ height: 6px;
7
+ }
8
+
9
+ ::-webkit-scrollbar-track {
10
+ background: #0a0a0a;
11
+ }
12
+
13
+ ::-webkit-scrollbar-thumb {
14
+ background: #222;
15
+ border-radius: 10px;
16
+ }
17
+
18
+ ::-webkit-scrollbar-thumb:hover {
19
+ background: #10b981;
20
+ }
21
+
22
+ * {
23
+ scrollbar-width: thin;
24
+ scrollbar-color: #222 #0a0a0a;
25
+ }
26
+ }
27
+
28
+ @media print {
29
+ @page {
30
+ size: auto;
31
+ margin: 15mm 10mm;
32
+ }
33
+
34
+ /* Force light theme for printing to save ink */
35
+ *, *::before, *::after {
36
+ background: transparent !important;
37
+ background-color: transparent !important;
38
+ color: #000 !important;
39
+ box-shadow: none !important;
40
+ text-shadow: none !important;
41
+ backdrop-filter: none !important;
42
+ -webkit-backdrop-filter: none !important;
43
+ }
44
+
45
+ body {
46
+ -webkit-print-color-adjust: exact !important;
47
+ print-color-adjust: exact !important;
48
+ background-color: white !important;
49
+ }
50
+
51
+ [class*="border"],
52
+ [class*="divide"] > * {
53
+ border-color: #aaa !important;
54
+ }
55
+
56
+ /* Hide scrollbars and allow full height for printing */
57
+ html, body, #root, .h-full, .h-screen, .overflow-hidden, .overflow-y-auto, .flex-1, .print-block {
58
+ height: auto !important;
59
+ min-height: auto !important;
60
+ overflow: visible !important;
61
+ position: static !important;
62
+ }
63
+
64
+ /* Change specific flex containers to block to prevent print cutoff */
65
+ .print-block {
66
+ display: block !important;
67
+ }
68
+
69
+ /* Prevent page breaks inside sections and artifacts */
70
+ section, tr, .group {
71
+ page-break-inside: avoid;
72
+ break-inside: avoid;
73
+ }
74
+
75
+ /* Hide UI elements */
76
+ .print-hidden {
77
+ display: none !important;
78
+ }
79
+
80
+ /* Fix for Mermaid diagrams printing as black blocks */
81
+ .mermaid-wrapper svg rect,
82
+ .mermaid-wrapper svg circle,
83
+ .mermaid-wrapper svg ellipse,
84
+ .mermaid-wrapper svg polygon,
85
+ .mermaid-wrapper svg path {
86
+ fill: white !important;
87
+ stroke: #555 !important;
88
+ stroke-width: 1px !important;
89
+ }
90
+
91
+ .mermaid-wrapper svg .edgePath path,
92
+ .mermaid-wrapper svg .flowchart-link {
93
+ fill: none !important;
94
+ }
95
+
96
+ /* Keep arrows filled */
97
+ .mermaid-wrapper svg [id*="arrow"] path,
98
+ .mermaid-wrapper svg marker path,
99
+ .mermaid-wrapper svg marker polygon {
100
+ fill: #555 !important;
101
+ stroke: none !important;
102
+ }
103
+
104
+ .mermaid-wrapper svg text,
105
+ .mermaid-wrapper svg tspan {
106
+ fill: black !important;
107
+ stroke: none !important;
108
+ color: black !important;
109
+ }
110
+
111
+ .mermaid-wrapper svg .cluster rect {
112
+ fill: transparent !important;
113
+ stroke: #999 !important;
114
+ stroke-dasharray: 4 !important;
115
+ }
116
+
117
+ .mermaid-wrapper svg .edgeLabel rect {
118
+ fill: white !important;
119
+ stroke: none !important;
120
+ }
121
+ }
@@ -0,0 +1,59 @@
1
+ export interface GapIssue {
2
+ artifact: any;
3
+ type: string;
4
+ message: string;
5
+ severity: 'HIGH' | 'MEDIUM' | 'LOW';
6
+ }
7
+
8
+ export function generateGapsReport(gaps: GapIssue[], isFiltered: boolean): string {
9
+ const total = gaps.length;
10
+ const critical = gaps.filter(g => g.severity === 'HIGH').length;
11
+ const warning = gaps.filter(g => g.severity === 'MEDIUM').length;
12
+ const info = gaps.filter(g => g.severity === 'LOW').length;
13
+
14
+ let content = `# OpenLAG GAPs Report
15
+
16
+ Generated at: ${new Date().toISOString()}
17
+
18
+ Scope: ${isFiltered ? 'filtered' : 'all'}
19
+
20
+ ## Summary
21
+
22
+ - Total issues: ${total}
23
+ - Critical: ${critical}
24
+ - Warning: ${warning}
25
+ - Info: ${info}
26
+
27
+ ## Issues
28
+
29
+ `;
30
+
31
+ if (total === 0) {
32
+ content += `*No GAPs found for the current exact filters. Great job!*\n`;
33
+ return content;
34
+ }
35
+
36
+ gaps.forEach((gap, index) => {
37
+ const art = gap.artifact;
38
+ content += `### ${index + 1}. ${art.id} — ${gap.severity}\n\n`;
39
+ content += `- Artifact: ${art.title || 'Untitled'}\n`;
40
+ content += `- Type: ${art.type}\n`;
41
+ if (art.status) content += `- Status: ${art.status}\n`;
42
+ content += `- Relation: N/A\n`;
43
+ content += `- Reason: ${gap.message}\n\n`;
44
+ });
45
+
46
+ return content;
47
+ }
48
+
49
+ export function downloadTextFile(filename: string, content: string) {
50
+ const blob = new Blob([content], { type: 'text/markdown;charset=utf-8;' });
51
+ const url = URL.createObjectURL(blob);
52
+ const link = document.createElement('a');
53
+ link.href = url;
54
+ link.setAttribute('download', filename);
55
+ document.body.appendChild(link);
56
+ link.click();
57
+ document.body.removeChild(link);
58
+ URL.revokeObjectURL(url);
59
+ }
package/src/main.tsx ADDED
@@ -0,0 +1,10 @@
1
+ import {StrictMode} from 'react';
2
+ import {createRoot} from 'react-dom/client';
3
+ import App from './App.tsx';
4
+ import './index.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
package/src/store.ts ADDED
@@ -0,0 +1,146 @@
1
+ import { create } from 'zustand';
2
+ import { Version, Change, GraphSnapshot, Artifact, Relation, SystemVersion } from './types';
3
+ import { GraphIndex, buildGraphIndex, projectSubgraph, GraphQueryOptions, DEFAULT_NEIGHBORHOOD_DEPTH } from './core/graph/GraphQueryLayer';
4
+
5
+ interface Settings {
6
+ graphFocusDepth: number;
7
+ docsFocusDepth: number;
8
+ defaultDocsFocusMode: boolean;
9
+ showWeakRelations: boolean;
10
+ language: 'EN' | 'ES';
11
+ }
12
+
13
+ interface StoreState {
14
+ versions: Version[];
15
+ systemVersions: SystemVersion[];
16
+ changes: Change[];
17
+ currentVersionId: string | null;
18
+ fullGraph: GraphSnapshot | null;
19
+ graphIndex: GraphIndex | null;
20
+ currentSubgraph: GraphSnapshot | null;
21
+ activeView: 'graph' | 'docs' | 'impact' | 'orphans' | 'guide' | 'settings';
22
+ selectedArtifactId: string | null;
23
+ isLoading: boolean;
24
+ settings: Settings;
25
+ globalFilters: {
26
+ layer: string;
27
+ owner: string;
28
+ team: string;
29
+ };
30
+
31
+ initializeStore: () => Promise<void>;
32
+ setVersion: (versionId: string) => void;
33
+ setView: (view: 'graph' | 'docs' | 'impact' | 'orphans' | 'guide' | 'settings') => void;
34
+ setSelectedArtifact: (id: string | null) => void;
35
+ updateSettings: (newSettings: Partial<Settings>) => void;
36
+ setGlobalFilter: (filterType: 'layer' | 'owner' | 'team', value: string) => void;
37
+ refreshSubgraph: () => void;
38
+ }
39
+
40
+ // Global variable to store fetched data
41
+ let cachedData: {
42
+ versions: Version[];
43
+ systemVersions: SystemVersion[];
44
+ changes: Change[];
45
+ graphs: Record<string, GraphSnapshot>;
46
+ } | null = null;
47
+
48
+ export const useStore = create<StoreState>((set, get) => ({
49
+ versions: [],
50
+ systemVersions: [],
51
+ changes: [],
52
+ currentVersionId: null,
53
+ fullGraph: null,
54
+ graphIndex: null,
55
+ currentSubgraph: null,
56
+ activeView: 'graph',
57
+ selectedArtifactId: null,
58
+ isLoading: false,
59
+ settings: {
60
+ graphFocusDepth: DEFAULT_NEIGHBORHOOD_DEPTH,
61
+ docsFocusDepth: 1,
62
+ defaultDocsFocusMode: true,
63
+ showWeakRelations: false,
64
+ language: 'EN',
65
+ },
66
+ globalFilters: {
67
+ layer: 'ALL',
68
+ owner: 'ALL',
69
+ team: 'ALL',
70
+ },
71
+ updateSettings: (newSettings) => {
72
+ set((state) => ({ settings: { ...state.settings, ...newSettings } }));
73
+ get().refreshSubgraph();
74
+ },
75
+ setGlobalFilter: (filterType, value) => {
76
+ set((state) => ({
77
+ globalFilters: {
78
+ ...state.globalFilters,
79
+ [filterType]: value
80
+ }
81
+ }));
82
+ get().refreshSubgraph();
83
+ },
84
+
85
+ refreshSubgraph: () => {
86
+ const { fullGraph, graphIndex, selectedArtifactId, settings, globalFilters } = get();
87
+ if (!fullGraph || !graphIndex) return;
88
+
89
+ const options: GraphQueryOptions = {
90
+ focusId: selectedArtifactId || undefined,
91
+ depth: settings.graphFocusDepth,
92
+ showWeakRelations: settings.showWeakRelations,
93
+ filterLayer: globalFilters.layer,
94
+ filterOwner: globalFilters.owner,
95
+ filterTeam: globalFilters.team
96
+ };
97
+
98
+ const subgraph = projectSubgraph(fullGraph, graphIndex, options);
99
+ set({ currentSubgraph: subgraph });
100
+ },
101
+
102
+ initializeStore: async () => {
103
+
104
+ set({ isLoading: true });
105
+ try {
106
+ const response = await fetch('/graph-data.json');
107
+ if (!response.ok) throw new Error("Failed to load graph data");
108
+
109
+ cachedData = await response.json();
110
+
111
+ if (cachedData) {
112
+ const { versions, systemVersions, changes } = cachedData;
113
+ set({
114
+ versions,
115
+ systemVersions: systemVersions || [],
116
+ changes,
117
+ isLoading: false
118
+ });
119
+
120
+ if (versions.length > 0 && !get().currentVersionId) {
121
+ get().setVersion(versions[versions.length - 1].id);
122
+ }
123
+ }
124
+ } catch (e) {
125
+ console.error("OpenLAG Error: Could not fetch static data.", e);
126
+ set({ isLoading: false });
127
+ }
128
+ },
129
+
130
+ setVersion: (versionId) => {
131
+ if (!cachedData) return;
132
+ const fullGraph = cachedData.graphs[versionId] || null;
133
+ let graphIndex = null;
134
+ if (fullGraph) {
135
+ graphIndex = buildGraphIndex(fullGraph);
136
+ }
137
+ set({ currentVersionId: versionId, selectedArtifactId: null, fullGraph, graphIndex });
138
+ get().refreshSubgraph();
139
+ },
140
+
141
+ setView: (view) => set({ activeView: view }),
142
+ setSelectedArtifact: (id) => {
143
+ set({ selectedArtifactId: id });
144
+ get().refreshSubgraph();
145
+ }
146
+ }));