@donartcha/openlag 0.1.4 → 0.1.5

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 (59) hide show
  1. package/dist/assets/{arc-5hssXHF1.js → arc-Cg8uAUBB.js} +1 -1
  2. package/dist/assets/{architectureDiagram-3BPJPVTR-CoCQ7I8T.js → architectureDiagram-3BPJPVTR-BS-jlSix.js} +1 -1
  3. package/dist/assets/{blockDiagram-GPEHLZMM-Ce3SBFaK.js → blockDiagram-GPEHLZMM-piJhj2Kl.js} +1 -1
  4. package/dist/assets/{c4Diagram-AAUBKEIU-Cxiak8OI.js → c4Diagram-AAUBKEIU-CUkupHTt.js} +1 -1
  5. package/dist/assets/channel-Dg6EjY0N.js +1 -0
  6. package/dist/assets/{chunk-2J33WTMH-CI2-2ImK.js → chunk-2J33WTMH-DK1RvYbJ.js} +1 -1
  7. package/dist/assets/{chunk-4BX2VUAB-CK7HADOu.js → chunk-4BX2VUAB-DkbrtaN7.js} +1 -1
  8. package/dist/assets/{chunk-55IACEB6-DhMUe2Z8.js → chunk-55IACEB6-DeeqmVSw.js} +1 -1
  9. package/dist/assets/{chunk-727SXJPM-Ceirad8r.js → chunk-727SXJPM-BuaIQz3A.js} +1 -1
  10. package/dist/assets/{chunk-AQP2D5EJ-5f7TUd6D.js → chunk-AQP2D5EJ-BXTLYgxd.js} +1 -1
  11. package/dist/assets/{chunk-FMBD7UC4-DGLw7-i0.js → chunk-FMBD7UC4-hrXWkcdb.js} +1 -1
  12. package/dist/assets/{chunk-ND2GUHAM-bdCVW2vW.js → chunk-ND2GUHAM-D6pYSJIp.js} +1 -1
  13. package/dist/assets/{chunk-QZHKN3VN-Cvl6Nxaw.js → chunk-QZHKN3VN-DC-A3Aq8.js} +1 -1
  14. package/dist/assets/classDiagram-4FO5ZUOK-C_u0dh5t.js +1 -0
  15. package/dist/assets/classDiagram-v2-Q7XG4LA2-C_u0dh5t.js +1 -0
  16. package/dist/assets/{cose-bilkent-S5V4N54A-D5hPTlJh.js → cose-bilkent-S5V4N54A-DsxxO59f.js} +1 -1
  17. package/dist/assets/{dagre-BM42HDAG-EaLHJBg9.js → dagre-BM42HDAG-BfiDIYHL.js} +1 -1
  18. package/dist/assets/{diagram-2AECGRRQ-B03rLzzC.js → diagram-2AECGRRQ-DtT3XImB.js} +1 -1
  19. package/dist/assets/{diagram-5GNKFQAL-BMGFDeYz.js → diagram-5GNKFQAL-DJFNnWg9.js} +1 -1
  20. package/dist/assets/{diagram-KO2AKTUF-C67gpPIs.js → diagram-KO2AKTUF-nhyTgQNk.js} +1 -1
  21. package/dist/assets/{diagram-LMA3HP47-BsR8n_gX.js → diagram-LMA3HP47-Dlx64Gbc.js} +1 -1
  22. package/dist/assets/{diagram-OG6HWLK6-B1zv47yy.js → diagram-OG6HWLK6-BtZhjKp4.js} +1 -1
  23. package/dist/assets/{erDiagram-TEJ5UH35-xXOEdzQg.js → erDiagram-TEJ5UH35-DCjMay5k.js} +1 -1
  24. package/dist/assets/{flowDiagram-I6XJVG4X-DcjP3-zz.js → flowDiagram-I6XJVG4X-VUmu-Ulq.js} +1 -1
  25. package/dist/assets/{ganttDiagram-6RSMTGT7-D9QN8XP5.js → ganttDiagram-6RSMTGT7-DubywubS.js} +1 -1
  26. package/dist/assets/{gitGraphDiagram-PVQCEYII-Cb8pk6QE.js → gitGraphDiagram-PVQCEYII-PcO-oJqF.js} +1 -1
  27. package/dist/assets/index-BhnK9sdD.css +1 -0
  28. package/dist/assets/{index-wflyqKCW.js → index-BxBZmqgf.js} +132 -135
  29. package/dist/assets/{infoDiagram-5YYISTIA-CudTuB2i.js → infoDiagram-5YYISTIA-BZuhNKak.js} +1 -1
  30. package/dist/assets/{ishikawaDiagram-YF4QCWOH-CYrMjwuu.js → ishikawaDiagram-YF4QCWOH-BXwOxb-L.js} +1 -1
  31. package/dist/assets/{journeyDiagram-JHISSGLW-DOODfUXV.js → journeyDiagram-JHISSGLW-C9YvQpLx.js} +1 -1
  32. package/dist/assets/{kanban-definition-UN3LZRKU-CQz0IKkV.js → kanban-definition-UN3LZRKU-BL-bN75y.js} +1 -1
  33. package/dist/assets/{linear-0fFjcdIi.js → linear-ro7DcLah.js} +1 -1
  34. package/dist/assets/{mindmap-definition-RKZ34NQL-BnFimeAu.js → mindmap-definition-RKZ34NQL-TyafSJyD.js} +1 -1
  35. package/dist/assets/{pieDiagram-4H26LBE5-CMoHxt1c.js → pieDiagram-4H26LBE5-CGF8I-Jg.js} +1 -1
  36. package/dist/assets/{quadrantDiagram-W4KKPZXB-C5mSbaI8.js → quadrantDiagram-W4KKPZXB-gPuTV6yC.js} +1 -1
  37. package/dist/assets/{requirementDiagram-4Y6WPE33-CeADpKMZ.js → requirementDiagram-4Y6WPE33-DMNEMII6.js} +1 -1
  38. package/dist/assets/{sankeyDiagram-5OEKKPKP-ELntQlI8.js → sankeyDiagram-5OEKKPKP-CcLp1J1q.js} +1 -1
  39. package/dist/assets/{sequenceDiagram-3UESZ5HK-5mkiPlow.js → sequenceDiagram-3UESZ5HK-CRQIa-m4.js} +1 -1
  40. package/dist/assets/{stateDiagram-AJRCARHV-DbNfJB9W.js → stateDiagram-AJRCARHV-BCDHz4Yx.js} +1 -1
  41. package/dist/assets/stateDiagram-v2-BHNVJYJU-Cyj4KCTb.js +1 -0
  42. package/dist/assets/{timeline-definition-PNZ67QCA-C8k4W5sA.js → timeline-definition-PNZ67QCA-BZBzVnMe.js} +1 -1
  43. package/dist/assets/{vennDiagram-CIIHVFJN-eO-TQun1.js → vennDiagram-CIIHVFJN-BLb-Z_mb.js} +1 -1
  44. package/dist/assets/{wardley-L42UT6IY-BuAaO8uL.js → wardley-L42UT6IY-DqVhxf9g.js} +1 -1
  45. package/dist/assets/{wardleyDiagram-YWT4CUSO-CTutletf.js → wardleyDiagram-YWT4CUSO-cmFDR9p-.js} +1 -1
  46. package/dist/assets/{xychartDiagram-2RQKCTM6-wrRC0RBX.js → xychartDiagram-2RQKCTM6-DmAwTFQ_.js} +1 -1
  47. package/dist/index.html +2 -2
  48. package/package.json +1 -1
  49. package/src/components/DocumentationView.tsx +83 -82
  50. package/src/components/GraphView.tsx +23 -11
  51. package/src/components/GuideView.tsx +5 -3
  52. package/src/components/ImpactView.tsx +10 -6
  53. package/src/components/OrphansView.tsx +8 -5
  54. package/src/utils/artifactUtils.ts +93 -0
  55. package/dist/assets/channel-BGwATj28.js +0 -1
  56. package/dist/assets/classDiagram-4FO5ZUOK-xQe3lOVv.js +0 -1
  57. package/dist/assets/classDiagram-v2-Q7XG4LA2-xQe3lOVv.js +0 -1
  58. package/dist/assets/index-D02phRy3.css +0 -1
  59. package/dist/assets/stateDiagram-v2-BHNVJYJU-hHz3nsFQ.js +0 -1
@@ -1,16 +1,22 @@
1
1
  import React, { useMemo, useState, useEffect } from 'react';
2
2
  import { useStore } from '../store';
3
3
  import { Artifact } from '../types';
4
+ import { getArtifactLayer, getArtifactOwner, getArtifactTeam } from '../utils/artifactUtils';
4
5
  import { Layers, FileText, FileCode2, ShieldCheck, ChevronRight, Search, GitPullRequest, Repeat, Box, Rocket, Activity, Wrench, Trash2, AlertCircle, Printer, Milestone, Bookmark, BookOpen } from 'lucide-react';
5
6
  import { MarkdownRenderer } from './MarkdownRenderer';
6
7
 
7
8
  const OwnershipBadge = ({ artifact }: { artifact: Artifact }) => {
8
- if (!artifact.layer && !artifact.ownership?.owner && !artifact.ownership?.team) return null;
9
+ const { fullGraph } = useStore();
10
+ const computedLayer = getArtifactLayer(artifact);
11
+ const computedOwner = getArtifactOwner(artifact, fullGraph);
12
+ const computedTeam = getArtifactTeam(artifact, fullGraph);
13
+
14
+ if (!computedLayer && !computedOwner && !computedTeam) return null;
9
15
  return (
10
16
  <div className="flex gap-2 mb-3 mt-1 pointer-events-none">
11
- {artifact.layer && <span className="text-[9px] text-purple-400 border border-purple-400/20 bg-purple-400/5 px-1.5 py-0.5 rounded-sm font-mono tracking-tighter">LAYER: {artifact.layer}</span>}
12
- {artifact.ownership?.owner && <span className="text-[9px] text-blue-400 border border-blue-400/20 bg-blue-400/5 px-1.5 py-0.5 rounded-sm font-mono tracking-tighter">OWNER: {artifact.ownership?.owner}</span>}
13
- {artifact.ownership?.team && <span className="text-[9px] text-emerald-400 border border-emerald-400/20 bg-emerald-400/5 px-1.5 py-0.5 rounded-sm font-mono tracking-tighter">TEAM: {artifact.ownership?.team}</span>}
17
+ {computedLayer && <span className="text-[9px] text-purple-400 border border-purple-400/20 bg-purple-400/5 px-1.5 py-0.5 rounded-sm font-mono tracking-tighter">LAYER: {computedLayer}</span>}
18
+ {computedOwner && <span className="text-[9px] text-blue-400 border border-blue-400/20 bg-blue-400/5 px-1.5 py-0.5 rounded-sm font-mono tracking-tighter">OWNER: {computedOwner}</span>}
19
+ {computedTeam && <span className="text-[9px] text-emerald-400 border border-emerald-400/20 bg-emerald-400/5 px-1.5 py-0.5 rounded-sm font-mono tracking-tighter">TEAM: {computedTeam}</span>}
14
20
  </div>
15
21
  );
16
22
  };
@@ -20,15 +26,12 @@ const PHASES = [
20
26
  { id: 'req', title: 'Requirements / Analysis', icon: FileText, types: ['REQUIREMENT', 'USE_CASE'] },
21
27
  { id: 'design', title: 'Architecture & Design', icon: Layers, types: ['DESIGN', 'COMPONENT', 'API', 'DATABASE_ENTITY'] },
22
28
  { id: 'dev', title: 'Development', icon: FileCode2, types: ['CODE_ENTITY', 'LIBRARY', 'ENVIRONMENT'] },
23
- { id: 'review', title: 'Code Review', icon: GitPullRequest, types: ['CODE_ENTITY'] },
24
- { id: 'ci', title: 'Continuous Integration', icon: Repeat, types: ['INFRASTRUCTURE', 'PIPELINE'] },
25
- { id: 'verif', title: 'Testing / QA', icon: ShieldCheck, types: ['TEST', 'TEST_CASE', 'CHECK', 'BUG'] },
26
- { id: 'build', title: 'Build / Packaging', icon: Box, types: ['INFRASTRUCTURE'] },
29
+ { id: 'ci', title: 'Continuous Integration / Build', icon: Repeat, types: ['PIPELINE', 'INFRASTRUCTURE'] },
30
+ { id: 'verif', title: 'Testing / QA', icon: ShieldCheck, types: ['TEST_CASE', 'CHECK', 'BUG'] },
27
31
  { id: 'deploy', title: 'Deployment', icon: Rocket, types: ['DEPLOYMENT'] },
28
32
  { id: 'monitor', title: 'Monitoring & Feedback', icon: Activity, types: ['MONITORING', 'INCIDENT'] },
29
33
  { id: 'docs', title: 'Documentation', icon: BookOpen, types: ['DOCUMENTATION'] },
30
34
  { id: 'maint', title: 'Maintenance / Refactoring', icon: Wrench, types: ['MAINTENANCE'] },
31
- { id: 'retire', title: 'Retirement / Replacement', icon: Trash2, types: ['MAINTENANCE'] },
32
35
  { id: 'versions', title: 'Releases & Versions', icon: Milestone, types: ['VERSION', 'SYSTEM_VERSION', 'CHANGE'] },
33
36
  ];
34
37
 
@@ -53,9 +56,9 @@ export const DocumentationView: React.FC = () => {
53
56
 
54
57
  const filterOptions = useMemo(() => {
55
58
  if (!graph || !graph.artifacts) return { layers: [], owners: [], teams: [] };
56
- const layers = Array.from(new Set(graph.artifacts.map(a => a.layer).filter(Boolean))) as string[];
57
- const owners = Array.from(new Set(graph.artifacts.map(a => a.ownership?.owner).filter(Boolean))) as string[];
58
- const teams = Array.from(new Set(graph.artifacts.map(a => a.ownership?.team).filter(Boolean))) as string[];
59
+ const layers = Array.from(new Set(graph.artifacts.map(a => getArtifactLayer(a)).filter(Boolean))) as string[];
60
+ const owners = Array.from(new Set(graph.artifacts.map(a => getArtifactOwner(a, graph)).filter(Boolean))) as string[];
61
+ const teams = Array.from(new Set(graph.artifacts.map(a => getArtifactTeam(a, graph)).filter(Boolean))) as string[];
59
62
  return { layers, owners, teams };
60
63
  }, [graph]);
61
64
 
@@ -133,9 +136,12 @@ export const DocumentationView: React.FC = () => {
133
136
 
134
137
  const filterByLayerAndOwnership = (list: Artifact[] = []) => {
135
138
  return list.filter(a => {
136
- if (filterLayer !== 'ALL' && a.layer !== filterLayer) return false;
137
- if (filterOwner !== 'ALL' && a.ownership?.owner !== filterOwner) return false;
138
- if (filterTeam !== 'ALL' && a.ownership?.team !== filterTeam) return false;
139
+ const computedLayer = getArtifactLayer(a);
140
+ const computedOwner = getArtifactOwner(a, graph);
141
+ const computedTeam = getArtifactTeam(a, graph);
142
+ if (filterLayer !== 'ALL' && computedLayer !== filterLayer) return false;
143
+ if (filterOwner !== 'ALL' && computedOwner !== filterOwner) return false;
144
+ if (filterTeam !== 'ALL' && computedTeam !== filterTeam) return false;
139
145
  return true;
140
146
  });
141
147
  };
@@ -155,31 +161,8 @@ export const DocumentationView: React.FC = () => {
155
161
  (filteredGroups[type] || [])
156
162
  );
157
163
 
158
- if (phase.id === 'dev') {
159
- filteredArtifactsInPhase = filteredArtifactsInPhase.filter(a => a.subType !== 'Review');
160
- } else if (phase.id === 'review') {
161
- filteredArtifactsInPhase = filteredArtifactsInPhase.filter(a => a.subType === 'Review');
162
- } else if (phase.id === 'ci') {
163
- filteredArtifactsInPhase = filteredArtifactsInPhase.filter(a => a.type === 'PIPELINE' || (a.type === 'INFRASTRUCTURE' && a.subType?.includes('CI')));
164
- } else if (phase.id === 'build') {
165
- filteredArtifactsInPhase = filteredArtifactsInPhase.filter(a => a.type === 'INFRASTRUCTURE' && (a.subType === 'Build' || a.subType === 'Package'));
166
- } else if (phase.id === 'maint') {
167
- filteredArtifactsInPhase = filteredArtifactsInPhase.filter(a => a.type === 'MAINTENANCE' && a.subType !== 'Retirement');
168
- } else if (phase.id === 'retire') {
169
- filteredArtifactsInPhase = filteredArtifactsInPhase.filter(a => a.type === 'MAINTENANCE' && a.subType === 'Retirement');
170
- }
171
-
172
164
  const subTypes = Array.from(new Set(filteredArtifactsInPhase.map(a => a.subType).filter(Boolean))) as string[];
173
165
 
174
- if (phase.id === 'design' && (filteredGroups['COMPONENT'] || []).length > 0) {
175
- const compSubTypes = Array.from(new Set((filteredGroups['COMPONENT'] || []).map(a => a.subType).filter(Boolean))) as string[];
176
- for (const s of compSubTypes) {
177
- if (!subTypes.includes(s)) subTypes.push(s);
178
- }
179
- if (compSubTypes.length === 0 && !subTypes.includes('Component')) {
180
- subTypes.push('Component');
181
- }
182
- }
183
166
  return {
184
167
  ...phase,
185
168
  count: filteredArtifactsInPhase.length,
@@ -486,26 +469,8 @@ export const DocumentationView: React.FC = () => {
486
469
 
487
470
  let phaseArtifacts = phase.types.flatMap(type => filteredGroups[type] || []);
488
471
 
489
- if (phase.id === 'dev') {
490
- phaseArtifacts = phaseArtifacts.filter(a => a.subType !== 'Review');
491
- } else if (phase.id === 'review') {
492
- phaseArtifacts = phaseArtifacts.filter(a => a.subType === 'Review');
493
- } else if (phase.id === 'ci') {
494
- phaseArtifacts = phaseArtifacts.filter(a => a.type === 'PIPELINE' || (a.type === 'INFRASTRUCTURE' && a.subType?.includes('CI')));
495
- } else if (phase.id === 'build') {
496
- phaseArtifacts = phaseArtifacts.filter(a => a.type === 'INFRASTRUCTURE' && (a.subType === 'Build' || a.subType === 'Package'));
497
- } else if (phase.id === 'maint') {
498
- phaseArtifacts = phaseArtifacts.filter(a => a.type === 'MAINTENANCE' && a.subType !== 'Retirement');
499
- } else if (phase.id === 'retire') {
500
- phaseArtifacts = phaseArtifacts.filter(a => a.type === 'MAINTENANCE' && a.subType === 'Retirement');
501
- }
502
-
503
472
  if (selectedSubType) {
504
- if (selectedSubType === 'Component') {
505
- phaseArtifacts = phaseArtifacts.filter(a => a.type === 'COMPONENT' && !a.subType);
506
- } else {
507
- phaseArtifacts = phaseArtifacts.filter(a => a.subType === selectedSubType);
508
- }
473
+ phaseArtifacts = phaseArtifacts.filter(a => a.subType === selectedSubType);
509
474
  }
510
475
 
511
476
  if (!shouldShow(phaseArtifacts.length > 0)) return null;
@@ -513,7 +478,7 @@ export const DocumentationView: React.FC = () => {
513
478
 
514
479
  return (
515
480
  <section key={phase.id} className="mb-16">
516
- <h2 className="text-[10px] uppercase tracking-widest text-white/40 mb-6 bg-white/5 p-3 border-l-2 border-white/20">
481
+ <h2 className="text-xl font-serif text-white mb-6 p-4 bg-white/5 border-l-4 border-emerald-500/50">
517
482
  {idx + 1}. {phase.title}
518
483
  </h2>
519
484
  {phaseArtifacts.length === 0 && (
@@ -521,30 +486,66 @@ export const DocumentationView: React.FC = () => {
521
486
  No active artifacts for this phase.
522
487
  </p>
523
488
  )}
524
- <div className="space-y-4">
525
- {phaseArtifacts.map((artifact) => (
526
- <div key={artifact.id} id={artifact.id} className="mb-6 bg-[#0c0c0c] border border-white/10 p-5 pl-6 border-l-[3px] border-l-emerald-500/50 hover:bg-white/[0.02] transition-colors relative group">
527
- <h3 className="font-serif text-lg text-white flex items-center gap-4 flex-wrap mb-4">
528
- <button
529
- onClick={() => setSelectedArtifact(artifact.id)}
530
- className={`text-[10px] font-mono bg-black px-2 py-1 border border-white/5 uppercase tracking-widest transition-colors hover:border-emerald-500/50 ${selectedArtifactId === artifact.id ? 'text-emerald-400 border-emerald-500/50' : 'text-white/40'}`}
531
- >
532
- {artifact.id}
533
- </button>
534
- {artifact.subType && <span className="text-[10px] font-sans bg-white/5 text-white/60 px-2 py-1 border border-white/10 uppercase tracking-widest">{artifact.subType}</span>}
535
- {orphanArtifactIds.has(artifact.id) && (
536
- <span className="text-[10px] bg-red-500/10 text-red-400 px-2 py-1 border border-red-500/20 uppercase tracking-widest flex items-center gap-1">
537
- <AlertCircle size={10} />
538
- Traceability Gap
539
- </span>
540
- )}
541
- <span>{artifact.title}</span>
542
- {artifact.version && <span className="ml-auto text-xs font-mono text-emerald-400 border border-emerald-500/30 bg-emerald-500/10 px-2 py-1 rounded">v{artifact.version}</span>}
543
- </h3>
544
- <OwnershipBadge artifact={artifact} />
545
- <MarkdownRenderer content={artifact.description} />
546
- </div>
547
- ))}
489
+ <div className="space-y-12">
490
+ {(() => {
491
+ // Extract and group artifacts by subType
492
+ const groupedBySubType: Record<string, Artifact[]> = {};
493
+ const noSubTypeArtifacts: Artifact[] = [];
494
+
495
+ phaseArtifacts.forEach(a => {
496
+ if (a.subType) {
497
+ if (!groupedBySubType[a.subType]) groupedBySubType[a.subType] = [];
498
+ groupedBySubType[a.subType].push(a);
499
+ } else {
500
+ noSubTypeArtifacts.push(a);
501
+ }
502
+ });
503
+
504
+ const renderArtifacts = (artifactsToRender: Artifact[]) => (
505
+ <div className="space-y-6">
506
+ {artifactsToRender.map(artifact => (
507
+ <div key={artifact.id} id={artifact.id} className="bg-[#0c0c0c] border border-white/10 p-5 pl-6 border-l-[3px] border-l-emerald-500/50 hover:bg-white/[0.02] transition-colors relative group print-block-break-inside-avoid">
508
+ <h3 className="font-serif text-lg text-white flex items-center gap-4 flex-wrap mb-4">
509
+ <button
510
+ onClick={() => setSelectedArtifact(artifact.id)}
511
+ className={`text-[10px] font-mono bg-black px-2 py-1 border border-white/5 uppercase tracking-widest transition-colors hover:border-emerald-500/50 ${selectedArtifactId === artifact.id ? 'text-emerald-400 border-emerald-500/50' : 'text-white/40'}`}
512
+ >
513
+ {artifact.id}
514
+ </button>
515
+ {artifact.subType && <span className="text-[10px] font-sans bg-white/5 text-white/60 px-2 py-1 border border-white/10 uppercase tracking-widest">{artifact.subType}</span>}
516
+ {orphanArtifactIds.has(artifact.id) && (
517
+ <span className="text-[10px] bg-red-500/10 text-red-400 px-2 py-1 border border-red-500/20 uppercase tracking-widest flex items-center gap-1">
518
+ <AlertCircle size={10} />
519
+ Traceability Gap
520
+ </span>
521
+ )}
522
+ <span>{artifact.title}</span>
523
+ {artifact.version && <span className="ml-auto text-xs font-mono text-emerald-400 border border-emerald-500/30 bg-emerald-500/10 px-2 py-1 rounded">v{artifact.version}</span>}
524
+ </h3>
525
+ <OwnershipBadge artifact={artifact} />
526
+ <MarkdownRenderer content={artifact.description} />
527
+ </div>
528
+ ))}
529
+ </div>
530
+ );
531
+
532
+ return (
533
+ <>
534
+ {noSubTypeArtifacts.length > 0 && renderArtifacts(noSubTypeArtifacts)}
535
+ {Object.entries(groupedBySubType)
536
+ .sort(([a], [b]) => a.localeCompare(b))
537
+ .map(([subType, stArtifacts]) => (
538
+ <div key={subType} className="mt-8">
539
+ <h3 className="text-xs uppercase tracking-widest text-emerald-400/80 mb-4 flex items-center gap-2">
540
+ <Layers size={14} />
541
+ {subType}
542
+ </h3>
543
+ {renderArtifacts(stArtifacts)}
544
+ </div>
545
+ ))}
546
+ </>
547
+ );
548
+ })()}
548
549
  </div>
549
550
  </section>
550
551
  );
@@ -6,15 +6,16 @@ import Markdown from 'react-markdown';
6
6
  import '@xyflow/react/dist/style.css';
7
7
  import { useStore } from '../store';
8
8
  import { ArtifactType } from '../types';
9
+ import { getArtifactLayer, getArtifactOwner, getArtifactTeam } from '../utils/artifactUtils';
9
10
 
10
11
  const typeColors: Record<ArtifactType, string> = {
11
12
  REQUIREMENT: 'text-blue-400 border-blue-400',
12
13
  USE_CASE: 'text-indigo-400 border-indigo-400',
13
14
  DESIGN: 'text-purple-400 border-purple-400',
14
- COMPONENT: 'text-amber-400 border-amber-400',
15
- CODE_ENTITY: 'text-emerald-400 border-emerald-400',
16
- TEST: 'text-rose-400 border-rose-400',
17
- DOCUMENTATION: 'text-slate-400 border-slate-400',
15
+ COMPONENT: 'text-amber-400 border-amber-400',
16
+ CODE_ENTITY: 'text-emerald-400 border-emerald-400',
17
+ TEST: 'text-rose-400 border-rose-400',
18
+ DOCUMENTATION: 'text-slate-400 border-slate-400',
18
19
  INCIDENT: 'text-red-400 border-red-400',
19
20
  INFRASTRUCTURE: 'text-cyan-400 border-cyan-400',
20
21
  DEPLOYMENT: 'text-sky-400 border-sky-400',
@@ -311,9 +312,12 @@ const GraphFlow: React.FC = () => {
311
312
  isConnectedToSelected = connectedNodes.has(a.id);
312
313
  }
313
314
 
314
- const isFilteredOut = (filterLayer !== 'ALL' && a.layer !== filterLayer) ||
315
- (filterOwner !== 'ALL' && a.ownership?.owner !== filterOwner) ||
316
- (filterTeam !== 'ALL' && a.ownership?.team !== filterTeam);
315
+ const computedLayer = getArtifactLayer(a);
316
+ const computedOwner = getArtifactOwner(a, fullGraph);
317
+ const computedTeam = getArtifactTeam(a, fullGraph);
318
+ const isFilteredOut = (filterLayer !== 'ALL' && computedLayer !== filterLayer) ||
319
+ (filterOwner !== 'ALL' && computedOwner !== filterOwner) ||
320
+ (filterTeam !== 'ALL' && computedTeam !== filterTeam);
317
321
 
318
322
  const isDimmed = isFilteredOut || (selectedArtifactId !== null && !isSelected && !isConnectedToSelected);
319
323
  const isOrphan = orphanIds.has(a.id);
@@ -334,9 +338,16 @@ const GraphFlow: React.FC = () => {
334
338
 
335
339
  const sourceNode = graph.artifacts.find(a => a.id === r.from);
336
340
  const targetNode = graph.artifacts.find(a => a.id === r.to);
337
- const isFilteredOut = (filterLayer !== 'ALL' && (sourceNode?.layer !== filterLayer || targetNode?.layer !== filterLayer)) ||
338
- (filterOwner !== 'ALL' && (sourceNode?.ownership?.owner !== filterOwner || targetNode?.ownership?.owner !== filterOwner)) ||
339
- (filterTeam !== 'ALL' && (sourceNode?.ownership?.team !== filterTeam || targetNode?.ownership?.team !== filterTeam));
341
+ const sourceLayer = sourceNode ? getArtifactLayer(sourceNode) : undefined;
342
+ const targetLayer = targetNode ? getArtifactLayer(targetNode) : undefined;
343
+ const sourceOwner = sourceNode ? getArtifactOwner(sourceNode, fullGraph) : undefined;
344
+ const targetOwner = targetNode ? getArtifactOwner(targetNode, fullGraph) : undefined;
345
+ const sourceTeam = sourceNode ? getArtifactTeam(sourceNode, fullGraph) : undefined;
346
+ const targetTeam = targetNode ? getArtifactTeam(targetNode, fullGraph) : undefined;
347
+
348
+ const isFilteredOut = (filterLayer !== 'ALL' && (sourceLayer !== filterLayer || targetLayer !== filterLayer)) ||
349
+ (filterOwner !== 'ALL' && (sourceOwner !== filterOwner || targetOwner !== filterOwner)) ||
350
+ (filterTeam !== 'ALL' && (sourceTeam !== filterTeam || targetTeam !== filterTeam));
340
351
 
341
352
  const isDimmed = isFilteredOut || (selectedArtifactId !== null && !isConnectedToSelected);
342
353
 
@@ -549,4 +560,5 @@ export const GraphView: React.FC = () => {
549
560
  <GraphFlow />
550
561
  </ReactFlowProvider>
551
562
  );
552
- };
563
+ };
564
+
@@ -39,7 +39,7 @@ export const GuideView: React.FC = () => {
39
39
  refactor: { label: "REFACTOR", desc: "Code restructurings without changing external behavior." },
40
40
  adaptation: { label: "ADAPTATION", desc: "Changes to adjust to new environmental/integration constraints." },
41
41
  frontmatter: "Official Markdown Format",
42
- frontmatterDesc: "Each artifact requires a YAML frontmatter. Mandatory fields: id, type, status, title.",
42
+ frontmatterDesc: "Each artifact requires a YAML frontmatter. Mandatory: id, type, status, title. Optional: subType (for taxonomic classification).",
43
43
  statusFlow: "Lifecycle Status Flow",
44
44
  statusFlowDesc: "Evolution of artifacts. The system tolerates missing relations in 'draft', but expects full traceability in 'closed'.",
45
45
  versions: "Project & System Versions",
@@ -93,7 +93,7 @@ export const GuideView: React.FC = () => {
93
93
  refactor: { label: "REFACTOR", desc: "Reestructuraciones sin alterar el comportamiento externo." },
94
94
  adaptation: { label: "ADAPTATION", desc: "Ajustes para nuevas integraciones o restricciones del entorno." },
95
95
  frontmatter: "Formato Oficial Markdown",
96
- frontmatterDesc: "Cada artefacto requiere un YAML frontmatter. Campos obligatorios: id, type, status, title.",
96
+ frontmatterDesc: "Cada artefacto requiere un YAML frontmatter. Campos obligatorios: id, type, status, title. Opcional: subType (clasificación taxonómica).",
97
97
  statusFlow: "Estados y Ciclo de Vida",
98
98
  statusFlowDesc: "En estado 'draft' se permiten huérfanos, pero en 'closed' se exige trazabilidad completa.",
99
99
  versions: "Versiones del Sistema y Proyecto",
@@ -173,7 +173,7 @@ export const GuideView: React.FC = () => {
173
173
  <h3 className="text-sm font-bold text-white/80 mb-2 uppercase tracking-widest">{c.implementationLayer}</h3>
174
174
  <p className="text-[11px] text-white/40 leading-relaxed mb-4">{c.implementationLayerDesc}</p>
175
175
  <div className="flex flex-wrap gap-2">
176
- {['CODE_ENTITY', 'TEST_CASE', 'DATABASE_ENTITY', 'CHANGE', 'BUG', 'RISK', 'TEST'].map(t => (
176
+ {['CODE_ENTITY', 'TEST_CASE', 'DATABASE_ENTITY', 'CHANGE', 'BUG', 'RISK'].map(t => (
177
177
  <span key={t} className="text-[9px] bg-white/5 px-1.5 py-0.5 rounded text-white/60">{t}</span>
178
178
  ))}
179
179
  </div>
@@ -306,6 +306,7 @@ export const GuideView: React.FC = () => {
306
306
  {`---
307
307
  id: REQ-001
308
308
  type: REQUIREMENT
309
+ subType: SYSTEM
309
310
  status: draft
310
311
  layer: BUSINESS
311
312
  title: Generar graph-data.json
@@ -452,6 +453,7 @@ We will use JWT tokens for authentication...`}
452
453
  {`---
453
454
  id: CODE-auth-service
454
455
  type: CODE_ENTITY
456
+ subType: Microservice
455
457
  status: in_progress
456
458
  layer: IMPLEMENTATION
457
459
  title: AuthService.ts
@@ -2,6 +2,7 @@ import React, { useState, useMemo, useEffect } from 'react';
2
2
  import { useStore } from '../store';
3
3
  import { Activity, ArrowRight, GitCommit, Search, ShieldAlert, Zap } from 'lucide-react';
4
4
  import { Artifact } from '../types';
5
+ import { getArtifactLayer, getArtifactOwner, getArtifactTeam } from '../utils/artifactUtils';
5
6
 
6
7
  export const ImpactView: React.FC = () => {
7
8
  const { changes, versions, fullGraph: graph, systemVersions, globalFilters, setGlobalFilter } = useStore();
@@ -20,9 +21,9 @@ export const ImpactView: React.FC = () => {
20
21
 
21
22
  const filterOptions = useMemo(() => {
22
23
  if (!graph || !graph.artifacts) return { layers: [], owners: [], teams: [] };
23
- const layers = Array.from(new Set(graph.artifacts.map(a => a.layer).filter(Boolean))) as string[];
24
- const owners = Array.from(new Set(graph.artifacts.map(a => a.ownership?.owner).filter(Boolean))) as string[];
25
- const teams = Array.from(new Set(graph.artifacts.map(a => a.ownership?.team).filter(Boolean))) as string[];
24
+ const layers = Array.from(new Set(graph.artifacts.map(a => getArtifactLayer(a)).filter(Boolean))) as string[];
25
+ const owners = Array.from(new Set(graph.artifacts.map(a => getArtifactOwner(a, graph)).filter(Boolean))) as string[];
26
+ const teams = Array.from(new Set(graph.artifacts.map(a => getArtifactTeam(a, graph)).filter(Boolean))) as string[];
26
27
  return { layers, owners, teams };
27
28
  }, [graph]);
28
29
 
@@ -42,9 +43,12 @@ export const ImpactView: React.FC = () => {
42
43
  const affectedLayerTeamOwner = (c.affects || []).some(id => {
43
44
  const artifact = graph?.artifacts.find(a => a.id === id);
44
45
  if (!artifact) return false;
45
- const matchesLayer = filterLayer === 'ALL' || artifact.layer === filterLayer;
46
- const matchesOwner = filterOwner === 'ALL' || artifact.ownership?.owner === filterOwner;
47
- const matchesTeam = filterTeam === 'ALL' || artifact.ownership?.team === filterTeam;
46
+ const computedLayer = getArtifactLayer(artifact);
47
+ const computedOwner = getArtifactOwner(artifact, graph);
48
+ const computedTeam = getArtifactTeam(artifact, graph);
49
+ const matchesLayer = filterLayer === 'ALL' || computedLayer === filterLayer;
50
+ const matchesOwner = filterOwner === 'ALL' || computedOwner === filterOwner;
51
+ const matchesTeam = filterTeam === 'ALL' || computedTeam === filterTeam;
48
52
  return matchesLayer && matchesOwner && matchesTeam;
49
53
  });
50
54
 
@@ -1,6 +1,7 @@
1
1
  import React, { useMemo, useState } from 'react';
2
2
  import { useStore } from '../store';
3
3
  import { ArtifactType } from '../types';
4
+ import { getArtifactLayer, getArtifactOwner, getArtifactTeam } from '../utils/artifactUtils';
4
5
  import { AlertCircle, Search, Trash2, ChevronRight, FileText, ExternalLink, ShieldAlert, AlertTriangle, Info, Download } from 'lucide-react';
5
6
  import { generateGapsReport, downloadTextFile } from '../lib/reportUtils';
6
7
 
@@ -72,10 +73,13 @@ export const OrphansView: React.FC = () => {
72
73
  const filteredGaps = useMemo(() => {
73
74
  return gaps.filter(gap => {
74
75
  const art = gap.artifact;
76
+ const computedLayer = getArtifactLayer(art);
77
+ const computedOwner = getArtifactOwner(art, graph);
78
+ const computedTeam = getArtifactTeam(art, graph);
75
79
 
76
- if (filterLayer !== 'ALL' && art.layer !== filterLayer) return false;
77
- if (filterOwner !== 'ALL' && art.ownership?.owner !== filterOwner) return false;
78
- if (filterTeam !== 'ALL' && art.ownership?.team !== filterTeam) return false;
80
+ if (filterLayer !== 'ALL' && computedLayer !== filterLayer) return false;
81
+ if (filterOwner !== 'ALL' && computedOwner !== filterOwner) return false;
82
+ if (filterTeam !== 'ALL' && computedTeam !== filterTeam) return false;
79
83
 
80
84
  if (selectedArtifactId && art.id !== selectedArtifactId) return false;
81
85
 
@@ -95,7 +99,7 @@ export const OrphansView: React.FC = () => {
95
99
  // Match the grouped keys in DocumentationView
96
100
  return [
97
101
  'REQUIREMENT', 'USE_CASE', 'DESIGN', 'COMPONENT',
98
- 'CODE_ENTITY', 'TEST', 'DOCUMENTATION', 'INCIDENT',
102
+ 'CODE_ENTITY', 'TEST_CASE', 'DOCUMENTATION', 'INCIDENT',
99
103
  'INFRASTRUCTURE', 'DEPLOYMENT', 'MONITORING', 'MAINTENANCE'
100
104
  ];
101
105
  }, []);
@@ -357,4 +361,3 @@ export const OrphansView: React.FC = () => {
357
361
  </div>
358
362
  );
359
363
  };
360
-
@@ -0,0 +1,93 @@
1
+ import { Artifact, ArtifactType, ArtifactLayer, GraphSnapshot } from '../types';
2
+
3
+ export function getImplicitLayer(type: ArtifactType): ArtifactLayer | undefined {
4
+ switch (type) {
5
+ case 'PROJECT':
6
+ case 'EPIC':
7
+ case 'FEATURE':
8
+ case 'REQUIREMENT':
9
+ case 'USE_CASE':
10
+ case 'BUSINESS_RULE':
11
+ return 'BUSINESS';
12
+ case 'DESIGN':
13
+ case 'DECISION':
14
+ case 'API':
15
+ case 'COMPONENT':
16
+ return 'ARCHITECTURE';
17
+ case 'CODE_ENTITY':
18
+ case 'TEST_CASE':
19
+ case 'DATABASE_ENTITY':
20
+ case 'CHANGE':
21
+ case 'BUG':
22
+ case 'LIBRARY':
23
+ case 'ENVIRONMENT':
24
+ return 'IMPLEMENTATION';
25
+ case 'INFRASTRUCTURE':
26
+ case 'DEPLOYMENT':
27
+ case 'MONITORING':
28
+ case 'INCIDENT':
29
+ case 'MAINTENANCE':
30
+ case 'PIPELINE':
31
+ case 'CHECK':
32
+ return 'OPERATIONS';
33
+ case 'GLOSSARY_TERM':
34
+ case 'DOCUMENTATION':
35
+ case 'PROCESS':
36
+ case 'VERSION':
37
+ case 'SYSTEM_VERSION':
38
+ return 'DOCUMENTATION';
39
+ default:
40
+ return undefined;
41
+ }
42
+ }
43
+
44
+ export function getArtifactLayer(artifact: Artifact): ArtifactLayer | undefined {
45
+ if (artifact.layer) {
46
+ return artifact.layer;
47
+ }
48
+ return getImplicitLayer(artifact.type);
49
+ }
50
+
51
+ // Traverse up REFINES or IMPLEMENTS relations to find inherited ownership
52
+ function findInheritedOwnership(
53
+ artifactId: string,
54
+ graph: GraphSnapshot,
55
+ visited: Set<string>
56
+ ): { owner?: string; team?: string } {
57
+ if (visited.has(artifactId)) return {};
58
+ visited.add(artifactId);
59
+
60
+ const artifact = graph.artifacts.find(a => a.id === artifactId);
61
+ if (!artifact) return {};
62
+
63
+ if (artifact.ownership?.owner || artifact.ownership?.team) {
64
+ return { owner: artifact.ownership.owner, team: artifact.ownership.team };
65
+ }
66
+
67
+ // Look for incoming REFINES or IMPLEMENTS (e.g. this artifact REFINES parent or this artifact IMPLEMENTS parent)
68
+ // Actually, wait: 'from' REFINES 'to' meant "from is smaller, to is bigger". So 'to' is the parent!
69
+ const parentRelations = graph.relations.filter(
70
+ r => r.from === artifactId && (r.type === 'REFINES' || r.type === 'IMPLEMENTS')
71
+ );
72
+
73
+ for (const rel of parentRelations) {
74
+ const parentOwnership = findInheritedOwnership(rel.to, graph, visited);
75
+ if (parentOwnership.owner || parentOwnership.team) {
76
+ return parentOwnership;
77
+ }
78
+ }
79
+
80
+ return {};
81
+ }
82
+
83
+ export function getArtifactOwner(artifact: Artifact, graph: GraphSnapshot | null): string | undefined {
84
+ if (artifact.ownership?.owner) return artifact.ownership.owner;
85
+ if (!graph) return undefined;
86
+ return findInheritedOwnership(artifact.id, graph, new Set()).owner;
87
+ }
88
+
89
+ export function getArtifactTeam(artifact: Artifact, graph: GraphSnapshot | null): string | undefined {
90
+ if (artifact.ownership?.team) return artifact.ownership.team;
91
+ if (!graph) return undefined;
92
+ return findInheritedOwnership(artifact.id, graph, new Set()).team;
93
+ }
@@ -1 +0,0 @@
1
- import{ai as o,aj as n}from"./index-wflyqKCW.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-727SXJPM-Ceirad8r.js";import{_ as i}from"./index-wflyqKCW.js";import"./chunk-FMBD7UC4-DGLw7-i0.js";import"./chunk-ND2GUHAM-bdCVW2vW.js";import"./chunk-55IACEB6-DhMUe2Z8.js";import"./chunk-2J33WTMH-CI2-2ImK.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-727SXJPM-Ceirad8r.js";import{_ as i}from"./index-wflyqKCW.js";import"./chunk-FMBD7UC4-DGLw7-i0.js";import"./chunk-ND2GUHAM-bdCVW2vW.js";import"./chunk-55IACEB6-DhMUe2Z8.js";import"./chunk-2J33WTMH-CI2-2ImK.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};