archgraph-argo 0.10.14 → 0.10.16
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.
|
@@ -781,9 +781,255 @@ function toNumber(value) {
|
|
|
781
781
|
return Number(value);
|
|
782
782
|
}
|
|
783
783
|
|
|
784
|
+
// --- Read-only Cypher query + structural projection schema -------------------
|
|
785
|
+
|
|
786
|
+
const FORBIDDEN_CYPHER_CLAUSES = Object.freeze([
|
|
787
|
+
'CREATE',
|
|
788
|
+
'MERGE',
|
|
789
|
+
'DELETE',
|
|
790
|
+
'SET',
|
|
791
|
+
'REMOVE',
|
|
792
|
+
'DROP',
|
|
793
|
+
'LOAD CSV',
|
|
794
|
+
'FOREACH',
|
|
795
|
+
'IN TRANSACTIONS',
|
|
796
|
+
]);
|
|
797
|
+
|
|
798
|
+
function stripCypherNoise(cypher) {
|
|
799
|
+
let result = String(cypher);
|
|
800
|
+
// Block comments.
|
|
801
|
+
result = result.replace(/\/\*[\s\S]*?\*\//g, ' ');
|
|
802
|
+
// Line comments.
|
|
803
|
+
result = result.replace(/\/\/[^\r\n]*/g, ' ');
|
|
804
|
+
// Single-quoted string literals.
|
|
805
|
+
result = result.replace(/'(?:[^'\\]|\\.)*'/g, ' ');
|
|
806
|
+
// Double-quoted identifiers/literals.
|
|
807
|
+
result = result.replace(/"(?:[^"\\]|\\.)*"/g, ' ');
|
|
808
|
+
// Backtick-escaped identifiers.
|
|
809
|
+
result = result.replace(/`(?:[^`\\]|\\.)*`/g, ' ');
|
|
810
|
+
return result;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function assertReadOnlyCypher(cypher) {
|
|
814
|
+
if (typeof cypher !== 'string' || cypher.trim().length === 0) {
|
|
815
|
+
const error = new Error('cypher must be a non-empty string');
|
|
816
|
+
error.category = 'CYPHER_QUERY_REQUIRED';
|
|
817
|
+
throw error;
|
|
818
|
+
}
|
|
819
|
+
if (cypher.length > 20000) {
|
|
820
|
+
const error = new Error('cypher exceeds the 20000 character limit');
|
|
821
|
+
error.category = 'CYPHER_QUERY_TOO_LONG';
|
|
822
|
+
throw error;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const normalized = stripCypherNoise(cypher).toUpperCase();
|
|
826
|
+
const found = FORBIDDEN_CYPHER_CLAUSES.find(clause => {
|
|
827
|
+
const escaped = clause.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
828
|
+
return new RegExp(`\\b${escaped}\\b`).test(normalized);
|
|
829
|
+
});
|
|
830
|
+
if (found) {
|
|
831
|
+
const error = new Error(`Cypher write clause '${found}' is not allowed; the Neo4j graph query interface is read-only`);
|
|
832
|
+
error.category = 'READ_ONLY_CYPHER_REQUIRED';
|
|
833
|
+
error.clause = found;
|
|
834
|
+
throw error;
|
|
835
|
+
}
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function serializeNeo4jValue(value) {
|
|
840
|
+
if (value === null || value === undefined) {
|
|
841
|
+
return value === undefined ? null : value;
|
|
842
|
+
}
|
|
843
|
+
if (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean') {
|
|
844
|
+
return value;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
let neo4j = null;
|
|
848
|
+
try {
|
|
849
|
+
neo4j = requireNeo4jDriver();
|
|
850
|
+
} catch {
|
|
851
|
+
neo4j = null;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (neo4j && typeof neo4j.isInt === 'function' && neo4j.isInt(value)) {
|
|
855
|
+
if (typeof value.inSafeRange === 'function' && value.inSafeRange()) {
|
|
856
|
+
return value.toNumber();
|
|
857
|
+
}
|
|
858
|
+
return value.toString();
|
|
859
|
+
}
|
|
860
|
+
if (neo4j && typeof neo4j.isNode === 'function' && neo4j.isNode(value)) {
|
|
861
|
+
return {
|
|
862
|
+
$node: {
|
|
863
|
+
identity: value.identity ? String(value.identity) : null,
|
|
864
|
+
labels: Array.isArray(value.labels) ? value.labels : [],
|
|
865
|
+
properties: serializeNeo4jValue(value.properties),
|
|
866
|
+
},
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
if (neo4j && typeof neo4j.isRelationship === 'function' && neo4j.isRelationship(value)) {
|
|
870
|
+
return {
|
|
871
|
+
$relationship: {
|
|
872
|
+
identity: value.identity ? String(value.identity) : null,
|
|
873
|
+
type: value.type,
|
|
874
|
+
start: value.start ? String(value.start) : null,
|
|
875
|
+
end: value.end ? String(value.end) : null,
|
|
876
|
+
properties: serializeNeo4jValue(value.properties),
|
|
877
|
+
},
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
if (neo4j && typeof neo4j.isPath === 'function' && neo4j.isPath(value)) {
|
|
881
|
+
return {
|
|
882
|
+
$path: {
|
|
883
|
+
start: serializeNeo4jValue(value.start),
|
|
884
|
+
end: serializeNeo4jValue(value.end),
|
|
885
|
+
length: serializeNeo4jValue(value.length),
|
|
886
|
+
segments: Array.isArray(value.segments) ? value.segments.map(serializeNeo4jValue) : [],
|
|
887
|
+
},
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
if (Array.isArray(value)) {
|
|
891
|
+
return value.map(serializeNeo4jValue);
|
|
892
|
+
}
|
|
893
|
+
if (typeof value === 'object') {
|
|
894
|
+
const result = {};
|
|
895
|
+
for (const [key, item] of Object.entries(value)) {
|
|
896
|
+
result[key] = serializeNeo4jValue(item);
|
|
897
|
+
}
|
|
898
|
+
return result;
|
|
899
|
+
}
|
|
900
|
+
return String(value);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function buildNeo4jGraphSchema(architecturePath = DEFAULT_GRAPH_PATH) {
|
|
904
|
+
return {
|
|
905
|
+
graphKey: buildGraphKey(architecturePath),
|
|
906
|
+
nodeLabels: {
|
|
907
|
+
ArchitectureGraph: {
|
|
908
|
+
description: 'One node per canonical graph document; identified by graphKey.',
|
|
909
|
+
properties: [
|
|
910
|
+
'graphKey',
|
|
911
|
+
'source_path',
|
|
912
|
+
'name',
|
|
913
|
+
'description',
|
|
914
|
+
'attributes_json',
|
|
915
|
+
'raw_json',
|
|
916
|
+
'element_count',
|
|
917
|
+
'relationship_count',
|
|
918
|
+
'view_count',
|
|
919
|
+
],
|
|
920
|
+
},
|
|
921
|
+
Element: {
|
|
922
|
+
description: 'Canonical architecture elements. Element.type holds the ArchiMate element type.',
|
|
923
|
+
properties: [
|
|
924
|
+
'graphKey',
|
|
925
|
+
'id',
|
|
926
|
+
'name',
|
|
927
|
+
'type',
|
|
928
|
+
'parent',
|
|
929
|
+
'alias',
|
|
930
|
+
'classifier',
|
|
931
|
+
'description',
|
|
932
|
+
'attributes_json',
|
|
933
|
+
'subdiagram_views_json',
|
|
934
|
+
'testcases_json',
|
|
935
|
+
'raw_json',
|
|
936
|
+
],
|
|
937
|
+
},
|
|
938
|
+
ArchitectureRelationship: {
|
|
939
|
+
description: 'Canonical architecture relationships. type holds the ArchiMate relationship type; source_id/target_id reference Element.id.',
|
|
940
|
+
properties: [
|
|
941
|
+
'graphKey',
|
|
942
|
+
'id',
|
|
943
|
+
'name',
|
|
944
|
+
'type',
|
|
945
|
+
'statement',
|
|
946
|
+
'description',
|
|
947
|
+
'document',
|
|
948
|
+
'attributes_json',
|
|
949
|
+
'source_id',
|
|
950
|
+
'source_name',
|
|
951
|
+
'target_id',
|
|
952
|
+
'target_name',
|
|
953
|
+
'raw_json',
|
|
954
|
+
],
|
|
955
|
+
},
|
|
956
|
+
View: {
|
|
957
|
+
description: 'Canonical views; identified by view_id.',
|
|
958
|
+
properties: [
|
|
959
|
+
'graphKey',
|
|
960
|
+
'view_id',
|
|
961
|
+
'view_name',
|
|
962
|
+
'parent_element_id',
|
|
963
|
+
'parent_element_name',
|
|
964
|
+
'description',
|
|
965
|
+
'included_elements_json',
|
|
966
|
+
'included_relationships_json',
|
|
967
|
+
'raw_json',
|
|
968
|
+
],
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
relationshipTypes: {
|
|
972
|
+
OWNS_ELEMENT: { from: 'ArchitectureGraph', to: 'Element' },
|
|
973
|
+
OWNS_RELATIONSHIP: { from: 'ArchitectureGraph', to: 'ArchitectureRelationship' },
|
|
974
|
+
OWNS_VIEW: { from: 'ArchitectureGraph', to: 'View' },
|
|
975
|
+
RELATIONSHIP_SOURCE: { from: 'ArchitectureRelationship', to: 'Element', description: 'Source endpoint element of a relationship record.' },
|
|
976
|
+
RELATIONSHIP_TARGET: { from: 'ArchitectureRelationship', to: 'Element', description: 'Target endpoint element of a relationship record.' },
|
|
977
|
+
ARCHIMATE_RELATES: { from: 'Element', to: 'Element', properties: ['graphKey', 'relationship_id'], description: 'Direct ArchiMate semantic edge between two elements.' },
|
|
978
|
+
VIEW_OF: { from: 'View', to: 'Element', description: 'Parent element of a sub-view.' },
|
|
979
|
+
INCLUDES_ELEMENT: { from: 'View', to: 'Element', properties: ['order'] },
|
|
980
|
+
INCLUDES_RELATIONSHIP: { from: 'View', to: 'ArchitectureRelationship', properties: ['order'] },
|
|
981
|
+
HAS_SUBDIAGRAM: { from: 'Element', to: 'View' },
|
|
982
|
+
},
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
async function runNeo4jCypherQuery(options = {}) {
|
|
987
|
+
const architecturePath = options.architecturePath || DEFAULT_GRAPH_PATH;
|
|
988
|
+
const cypher = options.cypher;
|
|
989
|
+
assertReadOnlyCypher(cypher);
|
|
990
|
+
|
|
991
|
+
const graphKey = buildGraphKey(architecturePath);
|
|
992
|
+
const config = getNeo4jConfig(options);
|
|
993
|
+
const driver = options.driver || createDriver(config);
|
|
994
|
+
const ownDriver = !options.driver;
|
|
995
|
+
const session = driver.session({ database: config.database });
|
|
996
|
+
|
|
997
|
+
try {
|
|
998
|
+
const result = await session.executeRead(tx => tx.run(cypher, { graphKey }));
|
|
999
|
+
const records = result.records.map(record => {
|
|
1000
|
+
const entry = {};
|
|
1001
|
+
for (const key of record.keys) {
|
|
1002
|
+
entry[key] = serializeNeo4jValue(record.get(key));
|
|
1003
|
+
}
|
|
1004
|
+
return entry;
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
const summary = result.summary;
|
|
1008
|
+
return {
|
|
1009
|
+
architecturePath,
|
|
1010
|
+
graphKey,
|
|
1011
|
+
records,
|
|
1012
|
+
summary: {
|
|
1013
|
+
queryType: summary ? summary.queryType : null,
|
|
1014
|
+
database: summary && summary.database ? summary.database.name : null,
|
|
1015
|
+
containsUpdates: summary && summary.counters && typeof summary.counters.containsUpdates === 'function'
|
|
1016
|
+
? summary.counters.containsUpdates()
|
|
1017
|
+
: null,
|
|
1018
|
+
},
|
|
1019
|
+
};
|
|
1020
|
+
} finally {
|
|
1021
|
+
await session.close();
|
|
1022
|
+
if (ownDriver) {
|
|
1023
|
+
await driver.close();
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
784
1028
|
module.exports = {
|
|
785
1029
|
DEFAULT_GRAPH_PATH,
|
|
1030
|
+
assertReadOnlyCypher,
|
|
786
1031
|
buildGraphKey,
|
|
1032
|
+
buildNeo4jGraphSchema,
|
|
787
1033
|
createDriver,
|
|
788
1034
|
digestCanonicalArchitecture,
|
|
789
1035
|
ensureDatabaseExists,
|
|
@@ -797,6 +1043,8 @@ module.exports = {
|
|
|
797
1043
|
readNeo4jSyncState,
|
|
798
1044
|
recoverNeo4jSyncIfNeeded,
|
|
799
1045
|
resolveArchitecturePath,
|
|
1046
|
+
runNeo4jCypherQuery,
|
|
1047
|
+
serializeNeo4jValue,
|
|
800
1048
|
syncArchitectureToNeo4j,
|
|
801
1049
|
verifyArchitectureSync,
|
|
802
1050
|
waitForDatabaseOnline,
|
|
@@ -164,7 +164,10 @@ const {
|
|
|
164
164
|
} = require('./graph-rag/mutationEmbeddingVectorLifecycle.js');
|
|
165
165
|
const {
|
|
166
166
|
DEFAULT_GRAPH_PATH: NEO4J_DEFAULT_GRAPH_PATH,
|
|
167
|
+
buildGraphKey,
|
|
168
|
+
buildNeo4jGraphSchema,
|
|
167
169
|
recoverNeo4jSyncIfNeeded,
|
|
170
|
+
runNeo4jCypherQuery,
|
|
168
171
|
syncArchitectureToNeo4j,
|
|
169
172
|
verifyArchitectureSync,
|
|
170
173
|
} = require('./neo4j-system-architecture-store.js');
|
|
@@ -379,6 +382,19 @@ const TOOLS = [
|
|
|
379
382
|
additionalProperties: false,
|
|
380
383
|
},
|
|
381
384
|
},
|
|
385
|
+
{
|
|
386
|
+
name: 'queryNeo4jGraph',
|
|
387
|
+
description: 'Run a read-only Cypher query against the Neo4j structural projection of the intent architecture, or request the projection schema so an agent can construct its own Cypher. Pass {schema: true} to return node labels, relationship types, property keys, and the legal ArchiMate element/relationship type enums. Pass {cypher: "..."} to execute a read-only query; scope it with {graphKey: $graphKey}. Write clauses (CREATE/MERGE/DELETE/SET/REMOVE/DROP/LOAD CSV/FOREACH/IN TRANSACTIONS) are rejected.',
|
|
388
|
+
inputSchema: {
|
|
389
|
+
type: 'object',
|
|
390
|
+
properties: {
|
|
391
|
+
architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
|
|
392
|
+
cypher: { type: 'string', description: 'Read-only Cypher query to execute. Use $graphKey to scope to the current architecture graph, e.g. MATCH (e:Element {graphKey: $graphKey}) RETURN e.id, e.name.' },
|
|
393
|
+
schema: { type: 'boolean', description: 'When true, return the Neo4j projection schema instead of running a Cypher query.' },
|
|
394
|
+
},
|
|
395
|
+
additionalProperties: false,
|
|
396
|
+
},
|
|
397
|
+
},
|
|
382
398
|
];
|
|
383
399
|
|
|
384
400
|
// Every tool accepts an optional per-call `workspaceRoot` (absolute path,
|
|
@@ -1041,6 +1057,7 @@ function applyMutations(document, mutations) {
|
|
|
1041
1057
|
const existingElement = findById(nextDocument.elements, mutation.element.id);
|
|
1042
1058
|
if (!existingElement) {
|
|
1043
1059
|
nextDocument.elements.push(clone(mutation.element));
|
|
1060
|
+
syncViewsToElementSubdiagramViews(nextDocument, findById(nextDocument.elements, mutation.element.id));
|
|
1044
1061
|
}
|
|
1045
1062
|
for (const view of scopedViews) {
|
|
1046
1063
|
view.included_elements = addUnique(view.included_elements || [], [mutation.element.id]);
|
|
@@ -1065,7 +1082,12 @@ function applyMutations(document, mutations) {
|
|
|
1065
1082
|
throw new Error(`Element '${mutation.id}' does not exist`);
|
|
1066
1083
|
}
|
|
1067
1084
|
requirePatchDoesNotChangeElementIdentityOrType(mutation.id, mutation.patch);
|
|
1085
|
+
const patchesSubdiagramViews = Object.prototype.hasOwnProperty.call(mutation.patch, 'subdiagram_views');
|
|
1086
|
+
const patchesName = Object.prototype.hasOwnProperty.call(mutation.patch, 'name');
|
|
1068
1087
|
Object.assign(element, clone(mutation.patch));
|
|
1088
|
+
if (patchesSubdiagramViews || patchesName) {
|
|
1089
|
+
reconcileSubdiagramViewsForElement(nextDocument, element);
|
|
1090
|
+
}
|
|
1069
1091
|
touchedElementIds.add(element.id);
|
|
1070
1092
|
mutationSummaries.push({ type: mutation.type, id: element.id });
|
|
1071
1093
|
continue;
|
|
@@ -1077,6 +1099,10 @@ function applyMutations(document, mutations) {
|
|
|
1077
1099
|
if (!element) {
|
|
1078
1100
|
throw new Error(`Element '${mutation.id}' does not exist`);
|
|
1079
1101
|
}
|
|
1102
|
+
const childViewIds = collectChildViewIds(nextDocument, mutation.id);
|
|
1103
|
+
if (childViewIds.length > 0) {
|
|
1104
|
+
throw new Error(`Element '${mutation.id}' cannot be removed: it still has ${childViewIds.length} sub-view(s) mounted under it (${childViewIds.join(', ')}). Remove or re-parent those sub-views first.`);
|
|
1105
|
+
}
|
|
1080
1106
|
const scopedViews = mutation.view_ids === undefined
|
|
1081
1107
|
? nextDocument.views
|
|
1082
1108
|
: requireViewScope(nextDocument.views, mutation.view_ids, 'mutation.view_ids');
|
|
@@ -1155,13 +1181,25 @@ function applyMutations(document, mutations) {
|
|
|
1155
1181
|
throw new Error(`Relationship '${mutation.id}' does not exist`);
|
|
1156
1182
|
}
|
|
1157
1183
|
requirePatchDoesNotChangeRelationshipIdentityOrType(mutation.id, mutation.patch);
|
|
1184
|
+
const oldSourceId = relationship.source_id;
|
|
1185
|
+
const oldTargetId = relationship.target_id;
|
|
1158
1186
|
Object.assign(relationship, clone(mutation.patch));
|
|
1159
1187
|
for (const view of nextDocument.views) {
|
|
1160
|
-
if ((view.included_relationships || []).includes(relationship.id)) {
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1188
|
+
if (!(view.included_relationships || []).includes(relationship.id)) {
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
view.included_elements = addUnique(view.included_elements || [], [
|
|
1192
|
+
relationship.source_id,
|
|
1193
|
+
relationship.target_id,
|
|
1194
|
+
]);
|
|
1195
|
+
for (const oldEndpointId of [oldSourceId, oldTargetId]) {
|
|
1196
|
+
if (oldEndpointId === relationship.source_id || oldEndpointId === relationship.target_id) {
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
if (isElementUsedByRelationship(nextDocument, view, oldEndpointId, relationship.id)) {
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
view.included_elements = removeEntries(view.included_elements || [], [oldEndpointId]);
|
|
1165
1203
|
}
|
|
1166
1204
|
}
|
|
1167
1205
|
touchedRelationshipIds.add(relationship.id);
|
|
@@ -1180,6 +1218,12 @@ function applyMutations(document, mutations) {
|
|
|
1180
1218
|
: requireViewScope(nextDocument.views, mutation.view_ids, 'mutation.view_ids');
|
|
1181
1219
|
for (const view of scopedViews) {
|
|
1182
1220
|
view.included_relationships = removeEntries(view.included_relationships || [], [mutation.id]);
|
|
1221
|
+
for (const endpointId of [relationship.source_id, relationship.target_id]) {
|
|
1222
|
+
if (isElementUsedByRelationship(nextDocument, view, endpointId, mutation.id)) {
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
view.included_elements = removeEntries(view.included_elements || [], [endpointId]);
|
|
1226
|
+
}
|
|
1183
1227
|
touchedViewIds.add(view.view_id);
|
|
1184
1228
|
}
|
|
1185
1229
|
const stillIncludedInView = nextDocument.views.some(view => (
|
|
@@ -1349,6 +1393,78 @@ function upsertSubdiagramViewIntoElement(document, elementId, view) {
|
|
|
1349
1393
|
}
|
|
1350
1394
|
}
|
|
1351
1395
|
|
|
1396
|
+
// Forward-sync: make every view listed in `element.subdiagram_views` point back
|
|
1397
|
+
// to this element (stealing the view from any previous parent when necessary).
|
|
1398
|
+
function syncViewsToElementSubdiagramViews(document, element) {
|
|
1399
|
+
if (!element || !Array.isArray(element.subdiagram_views)) {
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
for (const entry of element.subdiagram_views) {
|
|
1403
|
+
if (!entry || !entry.view_id) {
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
const view = findView(document.views, entry.view_id);
|
|
1407
|
+
if (!view) {
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
if (view.parent_element_id && view.parent_element_id !== element.id) {
|
|
1411
|
+
removeSubdiagramViewFromElement(document, view.parent_element_id, view.view_id);
|
|
1412
|
+
}
|
|
1413
|
+
view.parent_element_id = element.id;
|
|
1414
|
+
view.parent_element_name = element.name;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Bidirectional sync after an element's subdiagram_views (or name) changed:
|
|
1419
|
+
// listed views point back, and views no longer listed stop pointing at this element.
|
|
1420
|
+
function reconcileSubdiagramViewsForElement(document, element) {
|
|
1421
|
+
const currentViewIds = new Set(
|
|
1422
|
+
(element.subdiagram_views || [])
|
|
1423
|
+
.filter(entry => entry && entry.view_id)
|
|
1424
|
+
.map(entry => entry.view_id),
|
|
1425
|
+
);
|
|
1426
|
+
syncViewsToElementSubdiagramViews(document, element);
|
|
1427
|
+
for (const view of document.views || []) {
|
|
1428
|
+
if (view && view.parent_element_id === element.id && !currentViewIds.has(view.view_id)) {
|
|
1429
|
+
delete view.parent_element_id;
|
|
1430
|
+
delete view.parent_element_name;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function collectChildViewIds(document, elementId) {
|
|
1436
|
+
const viewIds = new Set();
|
|
1437
|
+
const element = findById(document.elements, elementId);
|
|
1438
|
+
if (element && Array.isArray(element.subdiagram_views)) {
|
|
1439
|
+
for (const entry of element.subdiagram_views) {
|
|
1440
|
+
if (entry && entry.view_id) {
|
|
1441
|
+
viewIds.add(entry.view_id);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
for (const view of document.views || []) {
|
|
1446
|
+
if (view && view.parent_element_id === elementId && view.view_id) {
|
|
1447
|
+
viewIds.add(view.view_id);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return Array.from(viewIds);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// Whether any relationship in the view (other than exceptRelationshipId) still
|
|
1454
|
+
// references elementId as one of its endpoints.
|
|
1455
|
+
function isElementUsedByRelationship(document, view, elementId, exceptRelationshipId) {
|
|
1456
|
+
for (const relationshipId of view.included_relationships || []) {
|
|
1457
|
+
if (relationshipId === exceptRelationshipId) {
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
const relationship = findById(document.relationships, relationshipId);
|
|
1461
|
+
if (relationship && (relationship.source_id === elementId || relationship.target_id === elementId)) {
|
|
1462
|
+
return true;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1352
1468
|
function addUnique(existing, additions) {
|
|
1353
1469
|
const result = Array.isArray(existing) ? [...existing] : [];
|
|
1354
1470
|
for (const addition of additions) {
|
|
@@ -2054,9 +2170,80 @@ async function callTool(name, args = {}, dependencies = undefined) {
|
|
|
2054
2170
|
return mutationToolResult(attachContextWarnings(await buildMutationResult(context, [{ type: 'removeView', view_id: args.view_id }], write), context), write);
|
|
2055
2171
|
}
|
|
2056
2172
|
|
|
2173
|
+
if (name === 'queryNeo4jGraph') {
|
|
2174
|
+
return queryNeo4jGraphTool(args);
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2057
2177
|
throw new Error(`Unknown tool: ${name}`);
|
|
2058
2178
|
}
|
|
2059
2179
|
|
|
2180
|
+
async function queryNeo4jGraphTool(args = {}) {
|
|
2181
|
+
const architecturePath = args.architecturePath || DEFAULT_GRAPH_PATH;
|
|
2182
|
+
|
|
2183
|
+
if (args.schema === true) {
|
|
2184
|
+
return queryNeo4jGraphSchemaResult(architecturePath);
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
try {
|
|
2188
|
+
const result = await runNeo4jCypherQuery({
|
|
2189
|
+
architecturePath,
|
|
2190
|
+
cypher: args.cypher,
|
|
2191
|
+
});
|
|
2192
|
+
return toolResult({
|
|
2193
|
+
status: 'passed',
|
|
2194
|
+
architecturePath: result.architecturePath,
|
|
2195
|
+
graphKey: result.graphKey,
|
|
2196
|
+
records: result.records,
|
|
2197
|
+
summary: result.summary,
|
|
2198
|
+
});
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
return toolResult(queryError(
|
|
2201
|
+
error && error.category ? error.category : 'NEO4J_QUERY_FAILED',
|
|
2202
|
+
String(error && error.message ? error.message : error),
|
|
2203
|
+
{
|
|
2204
|
+
architecturePath,
|
|
2205
|
+
graphKey: buildGraphKey(architecturePath),
|
|
2206
|
+
...(error && error.clause ? { clause: error.clause } : {}),
|
|
2207
|
+
},
|
|
2208
|
+
));
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
function queryNeo4jGraphSchemaResult(architecturePath) {
|
|
2213
|
+
const schema = buildNeo4jGraphSchema(architecturePath);
|
|
2214
|
+
let typeEnums = {};
|
|
2215
|
+
try {
|
|
2216
|
+
const workspaceRoot = resolveWorkspaceRoot({ architecturePath });
|
|
2217
|
+
const schemaPath = resolveSchemaPath(workspaceRoot);
|
|
2218
|
+
const jsonSchema = readJson(schemaPath.absolutePath, schemaPath.relativePath);
|
|
2219
|
+
typeEnums = {
|
|
2220
|
+
archimateElementTypes: (jsonSchema.$defs.archimateElementType || {}).enum || [],
|
|
2221
|
+
archimateRelationshipTypes: (jsonSchema.$defs.archimateRelationshipType || {}).enum || [],
|
|
2222
|
+
};
|
|
2223
|
+
} catch (error) {
|
|
2224
|
+
typeEnums = {
|
|
2225
|
+
archimateElementTypes: [],
|
|
2226
|
+
archimateRelationshipTypes: [],
|
|
2227
|
+
schemaEnumError: String(error && error.message ? error.message : error),
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
return toolResult({
|
|
2232
|
+
status: 'passed',
|
|
2233
|
+
architecturePath,
|
|
2234
|
+
graphKey: buildGraphKey(architecturePath),
|
|
2235
|
+
schema: {
|
|
2236
|
+
...schema,
|
|
2237
|
+
...typeEnums,
|
|
2238
|
+
},
|
|
2239
|
+
usage: {
|
|
2240
|
+
scopeGraph: 'MATCH (e:Element {graphKey: $graphKey}) ...',
|
|
2241
|
+
exampleAllBusinessActors: "MATCH (e:Element {graphKey: $graphKey}) WHERE e.type = 'Business Actor' RETURN e.id, e.name, e.description",
|
|
2242
|
+
exampleAssignments: "MATCH (a:Element {graphKey: $graphKey, type: 'Business Actor'})-[r:ARCHIMATE_RELATES]->(b:Element {graphKey: $graphKey}) WHERE r.relationship_id IS NOT NULL RETURN a.name, type(r), b.name, r.relationship_id",
|
|
2243
|
+
},
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2060
2247
|
async function resolveSemanticOperatorJourney(dependencies) {
|
|
2061
2248
|
return dependencies && dependencies.semanticOperatorJourney
|
|
2062
2249
|
? dependencies.semanticOperatorJourney
|
package/install-argo.ps1
CHANGED
|
@@ -726,7 +726,7 @@ if ($SkipDsh) {
|
|
|
726
726
|
Write-DshManagedBlock -Path $patchPath -Block $block -MarkerStart '# BEGIN ArchGraph ARGO deployment' -MarkerEnd '# END ArchGraph ARGO deployment'
|
|
727
727
|
}
|
|
728
728
|
|
|
729
|
-
Write-Host "[19/19] argo\
|
|
729
|
+
Write-Host "[19/19] argo\agents5 -> $DshHome\.agent-presets\<id> (DeepSeek Harness agent presets)"
|
|
730
730
|
New-DshAgentPresets -DshHome $DshHome -AgentsSrc (Join-Path $argoDir 'agents')
|
|
731
731
|
|
|
732
732
|
Write-Host ' Restart `dsh web` to activate the MCP bridge and the wakeup plugin;'
|
package/package.json
CHANGED