@donartcha/openlag 0.1.4 → 0.1.6

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 (63) 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/cli/openlag.js +16 -17
  48. package/dist/index.html +2 -2
  49. package/package.json +1 -1
  50. package/scripts/cli/init.ts +15 -15
  51. package/scripts/lint/lint-rules.ts +1 -1
  52. package/src/components/DocumentationView.tsx +83 -82
  53. package/src/components/GraphView.tsx +23 -11
  54. package/src/components/GuideView.tsx +5 -3
  55. package/src/components/ImpactView.tsx +10 -6
  56. package/src/components/OrphansView.tsx +8 -5
  57. package/src/core/semantic/artifact-layers.ts +1 -2
  58. package/src/utils/artifactUtils.ts +93 -0
  59. package/dist/assets/channel-BGwATj28.js +0 -1
  60. package/dist/assets/classDiagram-4FO5ZUOK-xQe3lOVv.js +0 -1
  61. package/dist/assets/classDiagram-v2-Q7XG4LA2-xQe3lOVv.js +0 -1
  62. package/dist/assets/index-D02phRy3.css +0 -1
  63. package/dist/assets/stateDiagram-v2-BHNVJYJU-hHz3nsFQ.js +0 -1
package/dist/index.html CHANGED
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>My OpenLAG Project | OpenLAG</title>
7
7
  <meta name="description" content="Living Architecture documentation for my system." />
8
- <script type="module" crossorigin src="/assets/index-wflyqKCW.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-D02phRy3.css">
8
+ <script type="module" crossorigin src="/assets/index-BxBZmqgf.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-BhnK9sdD.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donartcha/openlag",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Architecture as Code traceability graph generator",
5
5
  "type": "module",
6
6
  "license": "MPL-2.0",
@@ -85,8 +85,8 @@ export async function initProject(projectName?: string, projectDesc?: string, in
85
85
  content: `relation: IMPLEMENTS
86
86
  description: "Conecta implementación con necesidad funcional/técnica."
87
87
  category: TRACEABILITY
88
- allowedFrom: [CODE_ENTITY, COMPONENT, API, DATABASE_ENTITY, SYSTEM_VERSION]
89
- allowedTo: [REQUIREMENT, FEATURE, EPIC, DESIGN, USE_CASE, BUSINESS_RULE]
88
+ allowedFrom: [CODE_ENTITY]
89
+ allowedTo: [REQUIREMENT, FEATURE, BUG, API]
90
90
  multiplicity:
91
91
  from: many
92
92
  to: many
@@ -98,8 +98,8 @@ validation:
98
98
  content: `relation: TESTS
99
99
  description: "Conecta tests con comportamiento validado."
100
100
  category: TRACEABILITY
101
- allowedFrom: [TEST_CASE, TEST]
102
- allowedTo: [REQUIREMENT, FEATURE, EPIC, CODE_ENTITY, COMPONENT, API, DATABASE_ENTITY, BUG, INCIDENT]
101
+ allowedFrom: [TEST_CASE]
102
+ allowedTo: [CODE_ENTITY, REQUIREMENT, FEATURE, BUG, USE_CASE]
103
103
  multiplicity:
104
104
  from: many
105
105
  to: many
@@ -111,8 +111,8 @@ validation:
111
111
  content: `relation: REFINES
112
112
  description: "Descompone artefactos en otros más concretos."
113
113
  category: TRACEABILITY
114
- allowedFrom: [FEATURE, REQUIREMENT, BUG, RISK, DESIGN]
115
- allowedTo: [EPIC, FEATURE, PROJECT, BUSINESS_RULE, REQUIREMENT, DESIGN]
114
+ allowedFrom: [EPIC, FEATURE, REQUIREMENT]
115
+ allowedTo: [EPIC, FEATURE, REQUIREMENT]
116
116
  multiplicity:
117
117
  from: many
118
118
  to: many
@@ -124,8 +124,8 @@ validation:
124
124
  content: `relation: FIXES
125
125
  description: "Conecta correcciones con bugs o incidentes."
126
126
  category: TRACEABILITY
127
- allowedFrom: [CHANGE, CODE_ENTITY, COMPONENT, SYSTEM_VERSION]
128
- allowedTo: [BUG, INCIDENT, RISK]
127
+ allowedFrom: [CODE_ENTITY, CHANGE, DECISION]
128
+ allowedTo: [BUG, INCIDENT]
129
129
  multiplicity:
130
130
  from: many
131
131
  to: many
@@ -137,8 +137,8 @@ validation:
137
137
  content: `relation: DOCUMENTS
138
138
  description: "Conecta documentación con el artefacto descrito."
139
139
  category: SEMANTIC
140
- allowedFrom: [DOCUMENTATION, GLOSSARY_TERM]
141
- allowedTo: [PROJECT, EPIC, FEATURE, REQUIREMENT, BUSINESS_RULE, USE_CASE, DESIGN, DECISION, CODE_ENTITY, TEST_CASE, CHANGE, BUG, RISK, GLOSSARY_TERM, COMPONENT, API, DATABASE_ENTITY, TEST, DOCUMENTATION, INCIDENT, INFRASTRUCTURE, DEPLOYMENT, MONITORING, MAINTENANCE, SYSTEM_VERSION, VERSION]
140
+ allowedFrom: [DOCUMENTATION]
141
+ allowedTo: [PROJECT, EPIC, FEATURE, REQUIREMENT, BUSINESS_RULE, USE_CASE, DESIGN, DECISION, CODE_ENTITY, TEST_CASE, CHANGE, BUG, RISK, GLOSSARY_TERM, COMPONENT, API, DATABASE_ENTITY, DOCUMENTATION, INCIDENT, INFRASTRUCTURE, DEPLOYMENT, MONITORING, MAINTENANCE, SYSTEM_VERSION, VERSION, LIBRARY, ENVIRONMENT, CHECK, PROCESS, PIPELINE]
142
142
  multiplicity:
143
143
  from: many
144
144
  to: many
@@ -150,8 +150,8 @@ validation:
150
150
  content: `relation: JUSTIFIES
151
151
  description: "Conecta decisiones con aquello que justifican."
152
152
  category: SEMANTIC
153
- allowedFrom: [DECISION, BUSINESS_RULE, DOCUMENTATION, RISK]
154
- allowedTo: [DESIGN, REQUIREMENT, FEATURE, EPIC, PROJECT, CODE_ENTITY, COMPONENT, INFRASTRUCTURE, DEPLOYMENT, MAINTENANCE]
153
+ allowedFrom: [DECISION]
154
+ allowedTo: [DESIGN, REQUIREMENT, FEATURE, CODE_ENTITY, COMPONENT]
155
155
  multiplicity:
156
156
  from: many
157
157
  to: many
@@ -251,7 +251,7 @@ validation:
251
251
  description: "Averías o rupturas confirmadas."
252
252
  category: OPERATIONAL
253
253
  allowedFrom: [CHANGE, CODE_ENTITY, COMPONENT, SYSTEM_VERSION]
254
- allowedTo: [TEST_CASE, TEST, API, COMPONENT, REQUIREMENT, FEATURE]
254
+ allowedTo: [TEST_CASE, API, COMPONENT, REQUIREMENT, FEATURE]
255
255
  multiplicity:
256
256
  from: many
257
257
  to: many
@@ -348,8 +348,8 @@ description: |
348
348
  type: RELATES_TO
349
349
  rationale: "No encaja con USES o DEPENDS_ON debido al contexto X."
350
350
  category: SEMANTIC
351
- allowedFrom: [PROJECT, EPIC, FEATURE, REQUIREMENT, BUSINESS_RULE, USE_CASE, DESIGN, DECISION, CODE_ENTITY, TEST_CASE, CHANGE, BUG, RISK, GLOSSARY_TERM, COMPONENT, API, DATABASE_ENTITY, TEST, DOCUMENTATION, INCIDENT, INFRASTRUCTURE, DEPLOYMENT, MONITORING, MAINTENANCE, SYSTEM_VERSION, VERSION]
352
- allowedTo: [PROJECT, EPIC, FEATURE, REQUIREMENT, BUSINESS_RULE, USE_CASE, DESIGN, DECISION, CODE_ENTITY, TEST_CASE, CHANGE, BUG, RISK, GLOSSARY_TERM, COMPONENT, API, DATABASE_ENTITY, TEST, DOCUMENTATION, INCIDENT, INFRASTRUCTURE, DEPLOYMENT, MONITORING, MAINTENANCE, SYSTEM_VERSION, VERSION]
351
+ allowedFrom: [PROJECT, EPIC, FEATURE, REQUIREMENT, BUSINESS_RULE, USE_CASE, DESIGN, DECISION, CODE_ENTITY, TEST_CASE, CHANGE, BUG, RISK, GLOSSARY_TERM, COMPONENT, API, DATABASE_ENTITY, DOCUMENTATION, INCIDENT, INFRASTRUCTURE, DEPLOYMENT, MONITORING, MAINTENANCE, SYSTEM_VERSION, VERSION, LIBRARY, ENVIRONMENT, CHECK, PROCESS, PIPELINE]
352
+ allowedTo: [PROJECT, EPIC, FEATURE, REQUIREMENT, BUSINESS_RULE, USE_CASE, DESIGN, DECISION, CODE_ENTITY, TEST_CASE, CHANGE, BUG, RISK, GLOSSARY_TERM, COMPONENT, API, DATABASE_ENTITY, DOCUMENTATION, INCIDENT, INFRASTRUCTURE, DEPLOYMENT, MONITORING, MAINTENANCE, SYSTEM_VERSION, VERSION, LIBRARY, ENVIRONMENT, CHECK, PROCESS, PIPELINE]
353
353
  multiplicity:
354
354
  from: many
355
355
  to: many
@@ -127,7 +127,7 @@ export function runLintRules(data: OpenLagData, profile: LintProfile): LintIssue
127
127
  }
128
128
  }
129
129
 
130
- if (artifact.type === 'TEST') {
130
+ if (artifact.type === 'TEST_CASE') {
131
131
  const hasReq = reqsByTest.has(artifact.id);
132
132
  if (!hasReq) {
133
133
  addIssue('orphanArtifact', `${artifact.id} is a test without associated requirement`, artifact.file, artifact.id, artifact.status);
@@ -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
-
@@ -1,6 +1,6 @@
1
1
  import { ArtifactType, ArtifactLayer } from '../../types.js';
2
2
 
3
- export const LayerInferenceRules: Record<ArtifactType, ArtifactLayer> = {
3
+ export const LayerInferenceRules: Partial<Record<ArtifactType, ArtifactLayer>> = {
4
4
  PROJECT: 'BUSINESS',
5
5
  EPIC: 'BUSINESS',
6
6
  FEATURE: 'BUSINESS',
@@ -16,7 +16,6 @@ export const LayerInferenceRules: Record<ArtifactType, ArtifactLayer> = {
16
16
  CODE_ENTITY: 'IMPLEMENTATION',
17
17
  DATABASE_ENTITY: 'IMPLEMENTATION',
18
18
  TEST_CASE: 'IMPLEMENTATION',
19
- TEST: 'IMPLEMENTATION',
20
19
  CHANGE: 'IMPLEMENTATION',
21
20
  BUG: 'IMPLEMENTATION',
22
21
  RISK: 'IMPLEMENTATION',