archgraph-argo 0.26.2 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -4
- package/argo/.env.example +170 -163
- package/argo/plugins/argo-wakeup.js +29 -27
- package/argo/rules/archgraph.instructions.md +189 -187
- package/argo/schema/argob-rules.json +12229 -0
- package/argo/schema/argob.config.json +20 -0
- package/argo/scripts/argo-mcp-server.js +67 -1
- package/argo/scripts/argob-schema.js +574 -0
- package/argo/scripts/ensureArgoHarnessEnvironment.js +14 -0
- package/argo/scripts/external-graph-query.js +162 -0
- package/argo/scripts/graph-rag/defaultSemanticRetrieval.js +0 -13
- package/argo/scripts/graph-rag/liveEmbeddingProviderConfig.js +6 -61
- package/argo/scripts/graph-semantics.js +266 -227
- package/argo/scripts/runArchitectureTests.js +14 -6
- package/argo/scripts/systemarchitecture-mcp-server.js +87 -44
- package/argo/scripts/validateSystemArchitecture.js +260 -253
- package/argo/scripts/validator-mcp-server.js +1 -1
- package/argo/skills/argo-init/SKILL.md +7 -9
- package/argo/skills/ea-human-reconcile/SKILL.md +2 -1
- package/dsh-argo-wakeup/index.js +2 -2
- package/dsh-argo-workspace/index.js +3 -2
- package/install-argo.ps1 +215 -186
- package/package.json +5 -2
|
@@ -6,7 +6,6 @@ const readline = require('node:readline');
|
|
|
6
6
|
const crypto = require('node:crypto');
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
|
-
getArgoRoot,
|
|
10
9
|
resolveCallWorkspaceRoot,
|
|
11
10
|
} = require('./argo-paths.js');
|
|
12
11
|
|
|
@@ -132,6 +131,11 @@ const {
|
|
|
132
131
|
validateArchiMateEndpointMatrix,
|
|
133
132
|
validateViewElementLimits,
|
|
134
133
|
} = require('./graph-semantics.js');
|
|
134
|
+
const {
|
|
135
|
+
loadSchemaBundleAndOntology,
|
|
136
|
+
resolveSchemaBundle,
|
|
137
|
+
resolveTypeEnums,
|
|
138
|
+
} = require('./argob-schema.js');
|
|
135
139
|
const {
|
|
136
140
|
createProductionGraphRagRuntime,
|
|
137
141
|
} = require('./graph-rag/productionGraphRagRuntime.js');
|
|
@@ -168,6 +172,7 @@ const {
|
|
|
168
172
|
} = require('./neo4j-system-architecture-store.js');
|
|
169
173
|
|
|
170
174
|
const losslessWriteGate = require('./lossless-write-gate.js');
|
|
175
|
+
const { EXTERNAL_READ_TOOLS } = require('./external-graph-query.js');
|
|
171
176
|
|
|
172
177
|
const HANDLED_MUTATION_TYPES = new Set([
|
|
173
178
|
'addElement',
|
|
@@ -231,7 +236,7 @@ const TOOLS = [
|
|
|
231
236
|
},
|
|
232
237
|
{
|
|
233
238
|
name: 'previewSystemArchitectureMutation',
|
|
234
|
-
description: 'Use before apply for complex or risky changes. Performs a dry-run of one or more mutations, runs schema, graph, view, and
|
|
239
|
+
description: 'Use before apply for complex or risky changes. Performs a dry-run of one or more mutations, runs schema, graph, view, and modeling-language validation, and does not write the graph.',
|
|
235
240
|
inputSchema: mutationInputSchema(),
|
|
236
241
|
},
|
|
237
242
|
{
|
|
@@ -380,7 +385,7 @@ const TOOLS = [
|
|
|
380
385
|
},
|
|
381
386
|
{
|
|
382
387
|
name: 'queryNeo4jGraph',
|
|
383
|
-
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
|
|
388
|
+
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 element/relationship type enums of the workspace-resolved schema bundle. 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.',
|
|
384
389
|
inputSchema: {
|
|
385
390
|
type: 'object',
|
|
386
391
|
properties: {
|
|
@@ -415,6 +420,13 @@ const WORKSPACE_ROOT_PARAM = Object.freeze({
|
|
|
415
420
|
description:
|
|
416
421
|
'Optional absolute workspace root for this call. When provided it is used as-is; otherwise the server launch directory is used.',
|
|
417
422
|
});
|
|
423
|
+
// Cross-project graph query: the 5 read tools accept an optional `projectId`;
|
|
424
|
+
// provided => routed to the federation center, omitted => the local workspace.
|
|
425
|
+
const PROJECT_ID_PARAM = Object.freeze({
|
|
426
|
+
type: 'string',
|
|
427
|
+
description:
|
|
428
|
+
'Optional external project id. When provided, the query is routed to the federation center to read that project\'s graph (this project must be registered in .argo/federation.json); omitted = the local workspace (unchanged).',
|
|
429
|
+
});
|
|
418
430
|
// Lossless write gate: every update/remove helper accepts an explicit loss
|
|
419
431
|
// acknowledgement (see lossless-write-gate.js). add* is purely additive and is
|
|
420
432
|
// left alone. Without the acknowledgement a shrinking text edit or a destructive
|
|
@@ -436,6 +448,9 @@ for (const tool of TOOLS) {
|
|
|
436
448
|
if (!Object.prototype.hasOwnProperty.call(inputSchema.properties, 'workspaceRoot')) {
|
|
437
449
|
inputSchema.properties.workspaceRoot = WORKSPACE_ROOT_PARAM;
|
|
438
450
|
}
|
|
451
|
+
if (EXTERNAL_READ_TOOLS.has(tool.name) && !Object.prototype.hasOwnProperty.call(inputSchema.properties, 'projectId')) {
|
|
452
|
+
inputSchema.properties.projectId = PROJECT_ID_PARAM;
|
|
453
|
+
}
|
|
439
454
|
if (tool.name && /^(update|remove)Architecture/.test(tool.name)) {
|
|
440
455
|
for (const [key, value] of Object.entries(LOSS_ACK_INPUT_PROPS)) {
|
|
441
456
|
if (!Object.prototype.hasOwnProperty.call(inputSchema.properties, key)) {
|
|
@@ -559,22 +574,7 @@ function normalizeRelativePath(value) {
|
|
|
559
574
|
}
|
|
560
575
|
|
|
561
576
|
function resolveSchemaPath(workspaceRoot) {
|
|
562
|
-
|
|
563
|
-
if (fs.existsSync(bundledSchemaPath)) {
|
|
564
|
-
return { absolutePath: bundledSchemaPath, relativePath: SCHEMA_PATH_CANDIDATES[0] };
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
for (const candidate of SCHEMA_PATH_CANDIDATES) {
|
|
568
|
-
const absolutePath = path.join(workspaceRoot, candidate);
|
|
569
|
-
if (fs.existsSync(absolutePath)) {
|
|
570
|
-
return { absolutePath, relativePath: candidate };
|
|
571
|
-
}
|
|
572
|
-
const bundledPath = path.resolve(__dirname, '..', '..', candidate);
|
|
573
|
-
if (fs.existsSync(bundledPath)) {
|
|
574
|
-
return { absolutePath: bundledPath, relativePath: candidate };
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
throw new Error(`Unable to locate SystemArchitecture schema. Checked: ${SCHEMA_PATH_CANDIDATES.join(', ')}`);
|
|
577
|
+
return resolveSchemaBundle(workspaceRoot).schema;
|
|
578
578
|
}
|
|
579
579
|
|
|
580
580
|
function readJson(filePath, label) {
|
|
@@ -588,13 +588,17 @@ function readJson(filePath, label) {
|
|
|
588
588
|
async function loadContext(args = {}) {
|
|
589
589
|
const workspaceRoot = resolveWorkspaceRoot(args);
|
|
590
590
|
const graphPath = resolveWorkspacePath(workspaceRoot, args.architecturePath || DEFAULT_GRAPH_PATH);
|
|
591
|
-
|
|
591
|
+
// Resolve the modeling language for THIS workspace: a repository that ships
|
|
592
|
+
// its own bundle under .argo/schema is validated against that schema.
|
|
593
|
+
const { bundle, ontology } = loadSchemaBundleAndOntology(workspaceRoot);
|
|
592
594
|
const context = {
|
|
593
595
|
workspaceRoot,
|
|
594
596
|
graphPath,
|
|
595
|
-
schemaPath,
|
|
597
|
+
schemaPath: bundle.schema,
|
|
598
|
+
schemaBundle: bundle,
|
|
599
|
+
ontology,
|
|
596
600
|
document: readJson(graphPath.absolutePath, graphPath.relativePath),
|
|
597
|
-
schema:
|
|
601
|
+
schema: bundle.schemaDocument,
|
|
598
602
|
};
|
|
599
603
|
context.neo4jSyncRecovery = await recoverNeo4jSyncIfNeeded({
|
|
600
604
|
architecturePath: graphPath.relativePath,
|
|
@@ -605,13 +609,19 @@ async function loadContext(args = {}) {
|
|
|
605
609
|
}
|
|
606
610
|
|
|
607
611
|
function validateDocument(document, schema, options = {}) {
|
|
612
|
+
const ontology = options.ontology;
|
|
608
613
|
const errors = [];
|
|
614
|
+
if (ontology && ontology.bundleValidation && ontology.bundleValidation.status === 'failed') {
|
|
615
|
+
errors.push(...ontology.bundleValidation.errors.map(error => `schema bundle: ${error}`));
|
|
616
|
+
}
|
|
609
617
|
validateAgainstSchema(document, schema, '#', errors, schema);
|
|
610
|
-
validateGraphSemantics(document, errors);
|
|
618
|
+
validateGraphSemantics(document, errors, ontology);
|
|
611
619
|
validateArchiMateEndpointMatrix(document, errors, {
|
|
620
|
+
ontology,
|
|
612
621
|
touchedRelationshipIds: options.touchedRelationshipIds,
|
|
613
622
|
});
|
|
614
623
|
validateViewElementLimits(document, errors, {
|
|
624
|
+
ontology,
|
|
615
625
|
touchedViewIds: options.validateAllViewElementLimits
|
|
616
626
|
? (document.views || []).map(view => view && view.view_id)
|
|
617
627
|
: options.touchedViewIds,
|
|
@@ -679,7 +689,7 @@ function buildIntentElementContext(context, args = {}) {
|
|
|
679
689
|
const dependentDepth = normalizeDepth(args.dependentDepth, 1);
|
|
680
690
|
const associationDepth = Math.max(1, normalizeDepth(args.associationDepth, 1));
|
|
681
691
|
const associationNeighborDependencyDepth = normalizeDepth(args.associationNeighborDependencyDepth, 0);
|
|
682
|
-
const graphIndex = buildGraphIndex(context.document);
|
|
692
|
+
const graphIndex = buildGraphIndex(context.document, context.ontology);
|
|
683
693
|
const focusElement = focusResult.element;
|
|
684
694
|
const includedElementIds = new Set([focusElement.id]);
|
|
685
695
|
const includedRelationshipIds = new Set();
|
|
@@ -991,7 +1001,7 @@ function normalizeDepth(value, defaultValue) {
|
|
|
991
1001
|
return Math.floor(numericValue);
|
|
992
1002
|
}
|
|
993
1003
|
|
|
994
|
-
function buildGraphIndex(document) {
|
|
1004
|
+
function buildGraphIndex(document, ontology) {
|
|
995
1005
|
const relationshipById = new Map();
|
|
996
1006
|
const elementById = new Map((document.elements || []).map(element => [element.id, element]));
|
|
997
1007
|
const relationshipsByElementId = new Map();
|
|
@@ -1000,9 +1010,19 @@ function buildGraphIndex(document) {
|
|
|
1000
1010
|
addIndexedRelationship(relationshipsByElementId, relationship.source_id, relationship);
|
|
1001
1011
|
addIndexedRelationship(relationshipsByElementId, relationship.target_id, relationship);
|
|
1002
1012
|
}
|
|
1003
|
-
|
|
1013
|
+
const deliveryDependencies = ontology && ontology.deliveryDependencies
|
|
1014
|
+
? ontology.deliveryDependencies
|
|
1015
|
+
: DEFAULT_DELIVERY_DEPENDENCIES;
|
|
1016
|
+
return { elementById, relationshipById, relationshipsByElementId, deliveryDependencies };
|
|
1004
1017
|
}
|
|
1005
1018
|
|
|
1019
|
+
// Dependency direction for the semantic-edge walk comes from the active schema
|
|
1020
|
+
// bundle's deliveryDependencies; this is the ArgoBument fallback by default.
|
|
1021
|
+
const DEFAULT_DELIVERY_DEPENDENCIES = Object.freeze({
|
|
1022
|
+
sourceDependsOnTarget: Object.freeze(['Access', 'Assignment', 'Specialization', 'Composition', 'Aggregation']),
|
|
1023
|
+
targetDependsOnSource: Object.freeze(['Serving', 'Realization', 'Flow', 'Triggering', 'Influence']),
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1006
1026
|
function addIndexedRelationship(index, elementId, relationship) {
|
|
1007
1027
|
if (!index.has(elementId)) {
|
|
1008
1028
|
index.set(elementId, []);
|
|
@@ -1057,8 +1077,9 @@ function resolveSemanticEdges(elementId, graphIndex) {
|
|
|
1057
1077
|
continue;
|
|
1058
1078
|
}
|
|
1059
1079
|
|
|
1060
|
-
const
|
|
1061
|
-
const
|
|
1080
|
+
const deliveryDependencies = graphIndex.deliveryDependencies || DEFAULT_DELIVERY_DEPENDENCIES;
|
|
1081
|
+
const sourceDependsOnTarget = deliveryDependencies.sourceDependsOnTarget.includes(relationshipType);
|
|
1082
|
+
const targetDependsOnSource = deliveryDependencies.targetDependsOnSource.includes(relationshipType);
|
|
1062
1083
|
if (sourceDependsOnTarget) {
|
|
1063
1084
|
edges.push({ kind: isSource ? 'dependency' : 'dependent', neighborId, relationship });
|
|
1064
1085
|
continue;
|
|
@@ -1930,7 +1951,7 @@ async function buildMutationResult(context, mutations, write, dependencies, loss
|
|
|
1930
1951
|
before: beforeSummary,
|
|
1931
1952
|
after: beforeSummary,
|
|
1932
1953
|
errors,
|
|
1933
|
-
guidance: buildFailureGuidance(errors),
|
|
1954
|
+
guidance: buildFailureGuidance(errors, context.ontology),
|
|
1934
1955
|
};
|
|
1935
1956
|
if (error && Array.isArray(error.duplicateConflicts) && error.duplicateConflicts.length > 0) {
|
|
1936
1957
|
failed.duplicateConflicts = error.duplicateConflicts;
|
|
@@ -1941,6 +1962,7 @@ async function buildMutationResult(context, mutations, write, dependencies, loss
|
|
|
1941
1962
|
return failed;
|
|
1942
1963
|
}
|
|
1943
1964
|
const errors = validateDocument(mutationResult.document, context.schema, {
|
|
1965
|
+
ontology: context.ontology,
|
|
1944
1966
|
touchedRelationshipIds: mutationResult.touchedRelationshipIds,
|
|
1945
1967
|
touchedViewIds: mutationResult.viewLimitCheckIds,
|
|
1946
1968
|
});
|
|
@@ -1960,7 +1982,7 @@ async function buildMutationResult(context, mutations, write, dependencies, loss
|
|
|
1960
1982
|
errors,
|
|
1961
1983
|
};
|
|
1962
1984
|
if (errors.length > 0) {
|
|
1963
|
-
result.guidance = buildFailureGuidance(errors);
|
|
1985
|
+
result.guidance = buildFailureGuidance(errors, context.ontology);
|
|
1964
1986
|
}
|
|
1965
1987
|
|
|
1966
1988
|
// Semantic dedup gate: a would-be element create is blocked when a same-type
|
|
@@ -2215,10 +2237,10 @@ function buildMutationEmbeddingLifecycleFailure(error, result) {
|
|
|
2215
2237
|
});
|
|
2216
2238
|
}
|
|
2217
2239
|
|
|
2218
|
-
function buildFailureGuidance(errors) {
|
|
2240
|
+
function buildFailureGuidance(errors, ontology) {
|
|
2219
2241
|
const guidance = [];
|
|
2220
2242
|
for (const error of errors || []) {
|
|
2221
|
-
addGuidanceForError(guidance, String(error));
|
|
2243
|
+
addGuidanceForError(guidance, String(error), ontology);
|
|
2222
2244
|
}
|
|
2223
2245
|
if (guidance.length === 0 && Array.isArray(errors) && errors.length > 0) {
|
|
2224
2246
|
guidance.push('Inspect the error text, call getSystemArchitecture with an explicit semantic query to refresh relevant ids, use getIntentElementContext for focused dependency context when needed, then retry with previewSystemArchitectureMutation before writing. Use an omitted-query full snapshot only when exact complete view membership is required.');
|
|
@@ -2226,15 +2248,17 @@ function buildFailureGuidance(errors) {
|
|
|
2226
2248
|
return guidance;
|
|
2227
2249
|
}
|
|
2228
2250
|
|
|
2229
|
-
function addGuidanceForError(guidance, error) {
|
|
2251
|
+
function addGuidanceForError(guidance, error, ontology) {
|
|
2252
|
+
const language = ontology && ontology.language ? ontology.language : 'ArchiMate 3.2';
|
|
2253
|
+
const matrixLabel = ontology && ontology.matrixErrorLabel ? ontology.matrixErrorLabel : 'ArchiMate 3.2 relationship matrix';
|
|
2230
2254
|
if (error.includes('mutation.view_ids must contain at least one view id')) {
|
|
2231
2255
|
pushUnique(guidance, 'Select the target view_ids explicitly. Prefer getSystemArchitecture with an explicit semantic query to find relevant views, then use getIntentElementContext for focused element dependencies when needed. Use a full snapshot only if exact complete view membership is required.');
|
|
2232
2256
|
}
|
|
2233
|
-
if (error.includes(
|
|
2234
|
-
pushUnique(guidance,
|
|
2257
|
+
if (error.includes(`violates ${matrixLabel}`)) {
|
|
2258
|
+
pushUnique(guidance, `Check relationship.type and the source and target element types against ${language}. If the intended meaning is still valid, choose a compliant relationship type or change the endpoint element types by remove-and-add.`);
|
|
2235
2259
|
}
|
|
2236
|
-
if (error.includes('uses unsupported
|
|
2237
|
-
pushUnique(guidance,
|
|
2260
|
+
if (error.includes('uses unsupported') && error.includes('relationship type')) {
|
|
2261
|
+
pushUnique(guidance, `Use relationship.type for the relationship type and choose one of the schema-supported ${language} relationship types.`);
|
|
2238
2262
|
}
|
|
2239
2263
|
if (error.includes('id cannot be updated') || error.includes('type cannot be updated')) {
|
|
2240
2264
|
pushUnique(guidance, 'Do not patch immutable identity or type fields. To change an id or type, remove the existing element or relationship, then add the replacement with the desired id or type.');
|
|
@@ -2243,10 +2267,16 @@ function addGuidanceForError(guidance, error) {
|
|
|
2243
2267
|
pushUnique(guidance, 'Every element and relationship must belong to at least one view. Add it with view_ids, or add the existing object to an appropriate view before validating again.');
|
|
2244
2268
|
}
|
|
2245
2269
|
if (error.includes('must declare parent_element_id') || error.includes('top-level view')) {
|
|
2246
|
-
|
|
2270
|
+
const rootViewName = ontology && ontology.invariants && ontology.invariants.rootViewName
|
|
2271
|
+
? ontology.invariants.rootViewName
|
|
2272
|
+
: 'SystemArchitecture';
|
|
2273
|
+
pushUnique(guidance, `Keep exactly one top-level view${rootViewName ? ` named ${rootViewName}` : ''}. For any sub-view, set parent_element_id to an existing element and keep parent_element_name aligned with that element name.`);
|
|
2247
2274
|
}
|
|
2248
|
-
if (error.includes('must contain at most
|
|
2249
|
-
|
|
2275
|
+
if (error.includes('must contain at most') && error.includes('elements')) {
|
|
2276
|
+
const maxElements = ontology && ontology.invariants && ontology.invariants.maxElementsPerView !== undefined && ontology.invariants.maxElementsPerView !== null
|
|
2277
|
+
? ontology.invariants.maxElementsPerView
|
|
2278
|
+
: 15;
|
|
2279
|
+
pushUnique(guidance, `Do not force more than ${maxElements} included_elements into one view. Pause and think about layered architecture: split the view into layered sub-views, attach each sub-view with parent_element_id, and move lower-level elements into the appropriate child view before retrying.`);
|
|
2250
2280
|
}
|
|
2251
2281
|
if (error.includes('does not exist') || error.includes('references missing')) {
|
|
2252
2282
|
pushUnique(guidance, 'Refresh current ids with getSystemArchitecture semantic query first, then call getIntentElementContext for any returned element that needs dependency context. Do not guess ids; use existing element, relationship, and view ids or create missing objects first.');
|
|
@@ -3102,13 +3132,25 @@ async function queryNeo4jGraphTool(args = {}) {
|
|
|
3102
3132
|
function queryNeo4jGraphSchemaResult(architecturePath, workspaceRoot) {
|
|
3103
3133
|
const schema = buildNeo4jGraphSchema(architecturePath);
|
|
3104
3134
|
let typeEnums = {};
|
|
3135
|
+
let schemaBundleInfo = {};
|
|
3105
3136
|
try {
|
|
3106
3137
|
const resolvedRoot = workspaceRoot || resolveWorkspaceRoot({ architecturePath });
|
|
3107
|
-
const
|
|
3108
|
-
const
|
|
3138
|
+
const { bundle, ontology } = loadSchemaBundleAndOntology(resolvedRoot);
|
|
3139
|
+
const enums = resolveTypeEnums(bundle);
|
|
3109
3140
|
typeEnums = {
|
|
3110
|
-
archimateElementTypes:
|
|
3111
|
-
archimateRelationshipTypes:
|
|
3141
|
+
archimateElementTypes: enums.elementTypes,
|
|
3142
|
+
archimateRelationshipTypes: enums.relationshipTypes,
|
|
3143
|
+
schemaLanguage: (bundle.config && bundle.config.language) || null,
|
|
3144
|
+
schemaKind: bundle.kind,
|
|
3145
|
+
schemaDialect: ontology.dialect,
|
|
3146
|
+
actorElementType: ontology.actorElementType,
|
|
3147
|
+
bundleValidation: ontology.bundleValidation,
|
|
3148
|
+
};
|
|
3149
|
+
schemaBundleInfo = {
|
|
3150
|
+
schemaPath: bundle.schema.relativePath,
|
|
3151
|
+
schemaKind: bundle.kind,
|
|
3152
|
+
schemaDir: bundle.relativeDir,
|
|
3153
|
+
guidePath: bundle.guidePath ? bundle.guidePath.relativePath : null,
|
|
3112
3154
|
};
|
|
3113
3155
|
} catch (error) {
|
|
3114
3156
|
typeEnums = {
|
|
@@ -3125,6 +3167,7 @@ function queryNeo4jGraphSchemaResult(architecturePath, workspaceRoot) {
|
|
|
3125
3167
|
schema: {
|
|
3126
3168
|
...schema,
|
|
3127
3169
|
...typeEnums,
|
|
3170
|
+
...schemaBundleInfo,
|
|
3128
3171
|
},
|
|
3129
3172
|
usage: {
|
|
3130
3173
|
scopeGraph: 'MATCH (e:Element {graphKey: $graphKey}) ...',
|