@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.
- package/dist/assets/{arc-5hssXHF1.js → arc-Cg8uAUBB.js} +1 -1
- package/dist/assets/{architectureDiagram-3BPJPVTR-CoCQ7I8T.js → architectureDiagram-3BPJPVTR-BS-jlSix.js} +1 -1
- package/dist/assets/{blockDiagram-GPEHLZMM-Ce3SBFaK.js → blockDiagram-GPEHLZMM-piJhj2Kl.js} +1 -1
- package/dist/assets/{c4Diagram-AAUBKEIU-Cxiak8OI.js → c4Diagram-AAUBKEIU-CUkupHTt.js} +1 -1
- package/dist/assets/channel-Dg6EjY0N.js +1 -0
- package/dist/assets/{chunk-2J33WTMH-CI2-2ImK.js → chunk-2J33WTMH-DK1RvYbJ.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-CK7HADOu.js → chunk-4BX2VUAB-DkbrtaN7.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-DhMUe2Z8.js → chunk-55IACEB6-DeeqmVSw.js} +1 -1
- package/dist/assets/{chunk-727SXJPM-Ceirad8r.js → chunk-727SXJPM-BuaIQz3A.js} +1 -1
- package/dist/assets/{chunk-AQP2D5EJ-5f7TUd6D.js → chunk-AQP2D5EJ-BXTLYgxd.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DGLw7-i0.js → chunk-FMBD7UC4-hrXWkcdb.js} +1 -1
- package/dist/assets/{chunk-ND2GUHAM-bdCVW2vW.js → chunk-ND2GUHAM-D6pYSJIp.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Cvl6Nxaw.js → chunk-QZHKN3VN-DC-A3Aq8.js} +1 -1
- package/dist/assets/classDiagram-4FO5ZUOK-C_u0dh5t.js +1 -0
- package/dist/assets/classDiagram-v2-Q7XG4LA2-C_u0dh5t.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-D5hPTlJh.js → cose-bilkent-S5V4N54A-DsxxO59f.js} +1 -1
- package/dist/assets/{dagre-BM42HDAG-EaLHJBg9.js → dagre-BM42HDAG-BfiDIYHL.js} +1 -1
- package/dist/assets/{diagram-2AECGRRQ-B03rLzzC.js → diagram-2AECGRRQ-DtT3XImB.js} +1 -1
- package/dist/assets/{diagram-5GNKFQAL-BMGFDeYz.js → diagram-5GNKFQAL-DJFNnWg9.js} +1 -1
- package/dist/assets/{diagram-KO2AKTUF-C67gpPIs.js → diagram-KO2AKTUF-nhyTgQNk.js} +1 -1
- package/dist/assets/{diagram-LMA3HP47-BsR8n_gX.js → diagram-LMA3HP47-Dlx64Gbc.js} +1 -1
- package/dist/assets/{diagram-OG6HWLK6-B1zv47yy.js → diagram-OG6HWLK6-BtZhjKp4.js} +1 -1
- package/dist/assets/{erDiagram-TEJ5UH35-xXOEdzQg.js → erDiagram-TEJ5UH35-DCjMay5k.js} +1 -1
- package/dist/assets/{flowDiagram-I6XJVG4X-DcjP3-zz.js → flowDiagram-I6XJVG4X-VUmu-Ulq.js} +1 -1
- package/dist/assets/{ganttDiagram-6RSMTGT7-D9QN8XP5.js → ganttDiagram-6RSMTGT7-DubywubS.js} +1 -1
- package/dist/assets/{gitGraphDiagram-PVQCEYII-Cb8pk6QE.js → gitGraphDiagram-PVQCEYII-PcO-oJqF.js} +1 -1
- package/dist/assets/index-BhnK9sdD.css +1 -0
- package/dist/assets/{index-wflyqKCW.js → index-BxBZmqgf.js} +132 -135
- package/dist/assets/{infoDiagram-5YYISTIA-CudTuB2i.js → infoDiagram-5YYISTIA-BZuhNKak.js} +1 -1
- package/dist/assets/{ishikawaDiagram-YF4QCWOH-CYrMjwuu.js → ishikawaDiagram-YF4QCWOH-BXwOxb-L.js} +1 -1
- package/dist/assets/{journeyDiagram-JHISSGLW-DOODfUXV.js → journeyDiagram-JHISSGLW-C9YvQpLx.js} +1 -1
- package/dist/assets/{kanban-definition-UN3LZRKU-CQz0IKkV.js → kanban-definition-UN3LZRKU-BL-bN75y.js} +1 -1
- package/dist/assets/{linear-0fFjcdIi.js → linear-ro7DcLah.js} +1 -1
- package/dist/assets/{mindmap-definition-RKZ34NQL-BnFimeAu.js → mindmap-definition-RKZ34NQL-TyafSJyD.js} +1 -1
- package/dist/assets/{pieDiagram-4H26LBE5-CMoHxt1c.js → pieDiagram-4H26LBE5-CGF8I-Jg.js} +1 -1
- package/dist/assets/{quadrantDiagram-W4KKPZXB-C5mSbaI8.js → quadrantDiagram-W4KKPZXB-gPuTV6yC.js} +1 -1
- package/dist/assets/{requirementDiagram-4Y6WPE33-CeADpKMZ.js → requirementDiagram-4Y6WPE33-DMNEMII6.js} +1 -1
- package/dist/assets/{sankeyDiagram-5OEKKPKP-ELntQlI8.js → sankeyDiagram-5OEKKPKP-CcLp1J1q.js} +1 -1
- package/dist/assets/{sequenceDiagram-3UESZ5HK-5mkiPlow.js → sequenceDiagram-3UESZ5HK-CRQIa-m4.js} +1 -1
- package/dist/assets/{stateDiagram-AJRCARHV-DbNfJB9W.js → stateDiagram-AJRCARHV-BCDHz4Yx.js} +1 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-Cyj4KCTb.js +1 -0
- package/dist/assets/{timeline-definition-PNZ67QCA-C8k4W5sA.js → timeline-definition-PNZ67QCA-BZBzVnMe.js} +1 -1
- package/dist/assets/{vennDiagram-CIIHVFJN-eO-TQun1.js → vennDiagram-CIIHVFJN-BLb-Z_mb.js} +1 -1
- package/dist/assets/{wardley-L42UT6IY-BuAaO8uL.js → wardley-L42UT6IY-DqVhxf9g.js} +1 -1
- package/dist/assets/{wardleyDiagram-YWT4CUSO-CTutletf.js → wardleyDiagram-YWT4CUSO-cmFDR9p-.js} +1 -1
- package/dist/assets/{xychartDiagram-2RQKCTM6-wrRC0RBX.js → xychartDiagram-2RQKCTM6-DmAwTFQ_.js} +1 -1
- package/dist/cli/openlag.js +16 -17
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/scripts/cli/init.ts +15 -15
- package/scripts/lint/lint-rules.ts +1 -1
- package/src/components/DocumentationView.tsx +83 -82
- package/src/components/GraphView.tsx +23 -11
- package/src/components/GuideView.tsx +5 -3
- package/src/components/ImpactView.tsx +10 -6
- package/src/components/OrphansView.tsx +8 -5
- package/src/core/semantic/artifact-layers.ts +1 -2
- package/src/utils/artifactUtils.ts +93 -0
- package/dist/assets/channel-BGwATj28.js +0 -1
- package/dist/assets/classDiagram-4FO5ZUOK-xQe3lOVv.js +0 -1
- package/dist/assets/classDiagram-v2-Q7XG4LA2-xQe3lOVv.js +0 -1
- package/dist/assets/index-D02phRy3.css +0 -1
- 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-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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
package/scripts/cli/init.ts
CHANGED
|
@@ -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
|
|
89
|
-
allowedTo: [REQUIREMENT, FEATURE,
|
|
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
|
|
102
|
-
allowedTo: [
|
|
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
|
|
115
|
-
allowedTo: [EPIC, FEATURE,
|
|
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: [
|
|
128
|
-
allowedTo: [BUG, INCIDENT
|
|
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
|
|
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,
|
|
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
|
|
154
|
-
allowedTo: [DESIGN, REQUIREMENT, FEATURE,
|
|
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,
|
|
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,
|
|
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,
|
|
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 === '
|
|
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
|
-
|
|
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
|
-
{
|
|
12
|
-
{
|
|
13
|
-
{
|
|
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: '
|
|
24
|
-
{ id: '
|
|
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
|
|
57
|
-
const owners = Array.from(new Set(graph.artifacts.map(a => a
|
|
58
|
-
const teams = Array.from(new Set(graph.artifacts.map(a => a
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
525
|
-
{
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
338
|
-
|
|
339
|
-
|
|
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
|
|
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'
|
|
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
|
|
24
|
-
const owners = Array.from(new Set(graph.artifacts.map(a => a
|
|
25
|
-
const teams = Array.from(new Set(graph.artifacts.map(a => a
|
|
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
|
|
46
|
-
const
|
|
47
|
-
const
|
|
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' &&
|
|
77
|
-
if (filterOwner !== 'ALL' &&
|
|
78
|
-
if (filterTeam !== 'ALL' &&
|
|
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', '
|
|
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',
|