archgraph-argo 0.1.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.
Files changed (54) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +115 -0
  3. package/argo/package.json +8 -0
  4. package/argo/rules/intent-architecture-global-rule.md +45 -0
  5. package/argo/schema/ImplementationToCodingHandoff.schema.json +252 -0
  6. package/argo/schema/ImplementationToIntentTraceProposal.schema.json +180 -0
  7. package/argo/schema/IntentToImplementationHandoff.schema.json +75 -0
  8. package/argo/schema/SystemArchitecture.schema.json +378 -0
  9. package/argo/schema/archimate3.2.pdf +0 -0
  10. package/argo/scripts/ARCHITECTURE.md +57 -0
  11. package/argo/scripts/archimate32-rules.js +12301 -0
  12. package/argo/scripts/argo-mcp-server.js +629 -0
  13. package/argo/scripts/argo-paths.js +77 -0
  14. package/argo/scripts/ensureArgoHarnessEnvironment.js +340 -0
  15. package/argo/scripts/generateArchitectureDiffPlantuml.js +466 -0
  16. package/argo/scripts/graph-rag/ARCHITECTURE.md +192 -0
  17. package/argo/scripts/graph-rag/canonicalProjectionAuthority.js +45 -0
  18. package/argo/scripts/graph-rag/defaultSemanticRetrieval.js +969 -0
  19. package/argo/scripts/graph-rag/embeddingQualificationGate.js +59 -0
  20. package/argo/scripts/graph-rag/externalProductionConfig.js +74 -0
  21. package/argo/scripts/graph-rag/liveEmbeddingIndexGate.js +129 -0
  22. package/argo/scripts/graph-rag/liveEmbeddingNeo4jBoundary.js +137 -0
  23. package/argo/scripts/graph-rag/liveEmbeddingProviderClient.js +49 -0
  24. package/argo/scripts/graph-rag/liveEmbeddingProviderConfig.js +481 -0
  25. package/argo/scripts/graph-rag/mutationEmbeddingVectorLifecycle.js +1261 -0
  26. package/argo/scripts/graph-rag/neo4jNativeRetrieval.js +37 -0
  27. package/argo/scripts/graph-rag/productionGraphRagRuntime.js +1624 -0
  28. package/argo/scripts/graph-rag/semantic-persistence/ARCHITECTURE.md +51 -0
  29. package/argo/scripts/graph-rag/semantic-persistence/productionSemanticBackfill.js +241 -0
  30. package/argo/scripts/graph-rag/semantic-persistence/productionSemanticCheckpointStore.js +99 -0
  31. package/argo/scripts/graph-rag/semantic-persistence/productionSemanticNeo4jAdapter.js +149 -0
  32. package/argo/scripts/graph-rag/semantic-persistence/productionSemanticProjectionStore.js +171 -0
  33. package/argo/scripts/graph-rag/semanticOperatorError.js +38 -0
  34. package/argo/scripts/graph-rag/semanticOperatorJourney.js +459 -0
  35. package/argo/scripts/graph-rag/semanticReadinessAttestationStore.js +398 -0
  36. package/argo/scripts/graph-rag/systemMetadataCommandAdapter.js +269 -0
  37. package/argo/scripts/graph-semantics.js +220 -0
  38. package/argo/scripts/neo4j-system-architecture-store.js +777 -0
  39. package/argo/scripts/repositoryArgoEnvironment.js +101 -0
  40. package/argo/scripts/runArchitectureTests.js +583 -0
  41. package/argo/scripts/semanticOperatorJourneyCli.js +91 -0
  42. package/argo/scripts/syncSystemArchitectureToNeo4j.js +67 -0
  43. package/argo/scripts/systemarchitecture-mcp-server.js +2965 -0
  44. package/argo/scripts/test-executors/_template.js +58 -0
  45. package/argo/scripts/test-executors/default.js +199 -0
  46. package/argo/scripts/validateStageHandoff.js +459 -0
  47. package/argo/scripts/validateSystemArchitecture.js +254 -0
  48. package/argo/scripts/validateTraceProposal.js +181 -0
  49. package/argo/scripts/validator-mcp-server.js +377 -0
  50. package/argo/skills/argo-init/SKILL.md +110 -0
  51. package/bin/argo-deploy.js +12 -0
  52. package/install-argo.ps1 +112 -0
  53. package/package.json +28 -0
  54. package/vendor/neo4j-driver-6.2.0.tgz +0 -0
@@ -0,0 +1,2965 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const readline = require('node:readline');
4
+ const crypto = require('node:crypto');
5
+
6
+ const {
7
+ getArgoRoot,
8
+ getWorkspaceRoot,
9
+ } = require('./argo-paths.js');
10
+
11
+ const DEFAULT_GRAPH_PATH = 'design/KG/SystemArchitecture.json';
12
+ const LEGAL_QUERY_PURPOSES = new Set([
13
+ 'intent-decision',
14
+ 'implementation-design',
15
+ 'coding-repair',
16
+ 'audit',
17
+ 'graph-tidy',
18
+ ]);
19
+ const FORBIDDEN_RESPONSE_SHAPE_CONTROL_FIELDS = Object.freeze([
20
+ 'responseProfile',
21
+ 'detail',
22
+ 'outputMode',
23
+ ]);
24
+ const FORBIDDEN_RESPONSE_SHAPE_CONTROL_VALUES = new Set([
25
+ 'debug',
26
+ 'full',
27
+ 'evidence',
28
+ ]);
29
+ const FORBIDDEN_RESPONSE_SHAPE_CONTROL_FLAGS = Object.freeze([
30
+ 'debug',
31
+ 'full',
32
+ 'evidence',
33
+ ]);
34
+ const GET_SYSTEM_ARCHITECTURE_OUTPUT_SCHEMA = {
35
+ type: 'object',
36
+ required: ['version', 'mode', 'document', 'query', 'error'],
37
+ properties: {
38
+ version: {
39
+ type: 'string',
40
+ const: '1.0',
41
+ description: 'Version of the typed getSystemArchitecture output contract.',
42
+ },
43
+ mode: {
44
+ type: 'string',
45
+ enum: ['full-snapshot', 'semantic-query', 'error'],
46
+ description: 'Discriminator for the response variant.',
47
+ },
48
+ document: {
49
+ type: ['object', 'null'],
50
+ description: 'Canonical graph snapshot for full-snapshot responses; semantic business-summary responses may use result in the text payload and set this to null; null for errors.',
51
+ },
52
+ query: {
53
+ type: ['object', 'null'],
54
+ properties: {
55
+ purpose: { type: 'string', enum: Array.from(LEGAL_QUERY_PURPOSES) },
56
+ intent: { type: 'string' },
57
+ subject: { type: 'string' },
58
+ mode: { type: 'string', enum: ['full-snapshot', 'semantic-query'] },
59
+ semanticRetrieval: { type: 'string', enum: ['bypassed', 'invoked'] },
60
+ },
61
+ additionalProperties: true,
62
+ description: 'Normalized explicit query metadata; null for no-argument snapshots and errors.',
63
+ },
64
+ error: {
65
+ oneOf: [
66
+ {
67
+ type: 'object',
68
+ required: ['category', 'message'],
69
+ properties: {
70
+ category: { type: 'string' },
71
+ message: { type: 'string' },
72
+ action: { type: 'string' },
73
+ fullSnapshotFallback: { type: 'boolean' },
74
+ state: { type: ['string', 'null'] },
75
+ canonicalVersion: { type: ['string', 'null'] },
76
+ contentVersion: { type: ['string', 'null'] },
77
+ indexVersion: { type: ['string', 'null'] },
78
+ completedChannels: { type: 'array', items: { type: 'string' } },
79
+ missingChannels: { type: 'array', items: { type: 'string' } },
80
+ mismatchedChannels: { type: 'array', items: { type: 'string' } },
81
+ },
82
+ additionalProperties: false,
83
+ },
84
+ { type: 'null' },
85
+ ],
86
+ description: 'Stable typed error details; null for successful responses.',
87
+ },
88
+ },
89
+ oneOf: [
90
+ {
91
+ properties: {
92
+ mode: { const: 'full-snapshot' },
93
+ document: { type: 'object' },
94
+ query: { type: ['object', 'null'] },
95
+ error: { type: 'null' },
96
+ },
97
+ },
98
+ {
99
+ properties: {
100
+ mode: { const: 'semantic-query' },
101
+ document: { type: ['object', 'null'] },
102
+ query: { type: 'object' },
103
+ error: { type: 'null' },
104
+ },
105
+ },
106
+ {
107
+ properties: {
108
+ mode: { const: 'error' },
109
+ document: { type: 'null' },
110
+ query: { type: 'null' },
111
+ error: {
112
+ type: 'object',
113
+ required: ['category', 'message'],
114
+ },
115
+ },
116
+ },
117
+ ],
118
+ additionalProperties: false,
119
+ };
120
+ const SCHEMA_PATH_CANDIDATES = [
121
+ '.argo/schema/SystemArchitecture.schema.json',
122
+ ];
123
+ const W31_LIVE_OPT_IN = 'ARGO_W31_LIVE_MUTATION_VECTOR_E2E';
124
+ const LIVE_PROVIDER_OPT_IN = 'ARGO_LIVE_PROVIDER_E2E';
125
+ const W31_APPROVED_PROFILE = Object.freeze({
126
+ approvedByHuman: true,
127
+ provider: 'alibaba-cloud-model-studio-openai-compatible-cn-beijing',
128
+ baseUrl: 'https://llm-clids9mqc5o1mbvb.cn-beijing.maas.aliyuncs.com/compatible-mode/v1',
129
+ model: 'qwen3.7-text-embedding',
130
+ version: 'qualification-2026-07-25',
131
+ dimensions: 1024,
132
+ source: 'explicit-human-approval',
133
+ });
134
+
135
+ const {
136
+ validateGraphSemantics,
137
+ validateArchiMateEndpointMatrix,
138
+ validateViewElementLimits,
139
+ } = require('./graph-semantics.js');
140
+ const architectureDiffPlantuml = require('./generateArchitectureDiffPlantuml.js');
141
+ const {
142
+ createProductionGraphRagRuntime,
143
+ } = require('./graph-rag/productionGraphRagRuntime.js');
144
+ const {
145
+ createDefaultSemanticRetrieval,
146
+ } = require('./graph-rag/defaultSemanticRetrieval.js');
147
+ const {
148
+ createProductionSemanticOperatorJourney,
149
+ } = require('./graph-rag/semanticOperatorJourney.js');
150
+ const {
151
+ semanticOperatorErrorResult,
152
+ } = require('./graph-rag/semanticOperatorError.js');
153
+ const {
154
+ resolveExternalProductionConfig,
155
+ } = require('./graph-rag/externalProductionConfig.js');
156
+ const {
157
+ resolveApprovedLiveConfiguration,
158
+ } = require('./graph-rag/liveEmbeddingProviderConfig.js');
159
+ const {
160
+ createLiveEmbeddingProviderClient,
161
+ } = require('./graph-rag/liveEmbeddingProviderClient.js');
162
+ const {
163
+ createProductionSemanticReadinessStore,
164
+ } = require('./graph-rag/mutationEmbeddingVectorLifecycle.js');
165
+ const {
166
+ DEFAULT_GRAPH_PATH: NEO4J_DEFAULT_GRAPH_PATH,
167
+ recoverNeo4jSyncIfNeeded,
168
+ syncArchitectureToNeo4j,
169
+ verifyArchitectureSync,
170
+ } = require('./neo4j-system-architecture-store.js');
171
+
172
+ const HANDLED_MUTATION_TYPES = new Set([
173
+ 'addElement',
174
+ 'updateElement',
175
+ 'removeElement',
176
+ 'addRelationship',
177
+ 'updateRelationship',
178
+ 'removeRelationship',
179
+ 'addView',
180
+ 'updateView',
181
+ 'removeView',
182
+ ]);
183
+
184
+ const TOOLS = [
185
+ {
186
+ name: 'getSystemArchitecture',
187
+ description: 'Start here for read-only intent architecture access, but prefer an explicit semantic query instead of an omitted-query full graph read. Provide query.purpose and query.intent to get a compact business/architecture result, then use returned element ids with getIntentElementContext for focused dependency context. Omit query only when an exact full canonical snapshot is explicitly required.',
188
+ inputSchema: {
189
+ type: 'object',
190
+ properties: {
191
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
192
+ query: {
193
+ type: 'object',
194
+ description: 'Preferred for ordinary agent reading. Use semantic query instead of full graph reads; combine the returned element ids with getIntentElementContext when deeper local context is needed.',
195
+ properties: {
196
+ purpose: {
197
+ type: 'string',
198
+ enum: Array.from(LEGAL_QUERY_PURPOSES),
199
+ description: 'Declared reading purpose. Use intent-decision, implementation-design, coding-repair, or audit for semantic retrieval; graph-tidy intentionally bypasses semantic retrieval and may return a full snapshot.',
200
+ },
201
+ intent: { type: 'string', description: 'Natural-language intent for semantic retrieval, for example "summarize business features for high-risk audit".' },
202
+ subject: { type: 'string', description: 'Required for audit; optional anchor/focus id for other semantic purposes.' },
203
+ },
204
+ additionalProperties: true,
205
+ },
206
+ },
207
+ additionalProperties: false,
208
+ },
209
+ outputSchema: GET_SYSTEM_ARCHITECTURE_OUTPUT_SCHEMA,
210
+ },
211
+ {
212
+ name: 'getIntentElementContext',
213
+ description: 'read-only query that returns an intent subgraph context for one element. Uses ArchiMate semantic dependency traversal with dependencyDepth and dependentDepth, preserving native subgraph elements, relationships, and views.',
214
+ inputSchema: intentElementContextInputSchema(),
215
+ },
216
+ {
217
+ name: 'generateArchitectureDiffPlantuml',
218
+ description: 'Generate a timestamped PlantUML Markdown tree for current git diff changes in SystemArchitecture.json. The tool compares HEAD and working tree, extracts changed elements/relationships, and writes to .argo/temp/architecture_analysis/.',
219
+ inputSchema: {
220
+ type: 'object',
221
+ properties: {
222
+ architecturePath: {
223
+ type: 'string',
224
+ description: `Optional architecture graph path relative to workspace root. Default: ${DEFAULT_GRAPH_PATH}`,
225
+ },
226
+ outputDir: {
227
+ type: 'string',
228
+ description: 'Optional output directory relative to workspace root. Default: .argo/temp/architecture_analysis',
229
+ },
230
+ },
231
+ additionalProperties: false,
232
+ },
233
+ },
234
+ {
235
+ name: 'previewSystemArchitectureMutation',
236
+ description: 'Use before apply for complex or risky changes. Performs a dry-run of one or more mutations, runs schema, graph, view, and ArchiMate 3.2 validation, and does not write the graph.',
237
+ inputSchema: mutationInputSchema(),
238
+ },
239
+ {
240
+ name: 'applySystemArchitectureMutation',
241
+ description: 'Use for multi-step or dependent graph changes that should be validated and written atomically. Prefer focused tools for a single simple add, update, or remove operation.',
242
+ inputSchema: mutationInputSchema(),
243
+ },
244
+ {
245
+ name: 'addArchitectureElement',
246
+ description: 'Use for one element. Creates a new element or adds an existing element to view_ids. view_ids is required so elements never exist outside views. Set dryRun to preview without writing.',
247
+ inputSchema: {
248
+ type: 'object',
249
+ required: ['element', 'view_ids'],
250
+ properties: {
251
+ element: { type: 'object' },
252
+ view_ids: { type: 'array', minItems: 1, items: { type: 'string' } },
253
+ dryRun: { type: 'boolean', description: 'When true, validates and returns the result without writing to the graph. Default: false.' },
254
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
255
+ },
256
+ additionalProperties: false,
257
+ },
258
+ },
259
+ {
260
+ name: 'updateArchitectureElement',
261
+ description: 'Use for one global element metadata patch. Does not change view membership. Element id and type are immutable; remove and re-add to change them. Set dryRun to preview without writing.',
262
+ inputSchema: {
263
+ type: 'object',
264
+ required: ['id', 'patch'],
265
+ properties: {
266
+ id: { type: 'string' },
267
+ patch: { type: 'object' },
268
+ dryRun: { type: 'boolean', description: 'When true, validates and returns the result without writing to the graph. Default: false.' },
269
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
270
+ },
271
+ additionalProperties: false,
272
+ },
273
+ },
274
+ {
275
+ name: 'removeArchitectureElement',
276
+ description: 'Use for one element removal. With view_ids, removes only from those views and cascades related relationships in the same views; without view_ids, removes from all views and the graph. Set dryRun to preview without writing.',
277
+ inputSchema: {
278
+ type: 'object',
279
+ required: ['id'],
280
+ properties: {
281
+ id: { type: 'string' },
282
+ view_ids: { type: 'array', minItems: 1, items: { type: 'string' } },
283
+ dryRun: { type: 'boolean', description: 'When true, validates and returns the result without writing to the graph. Default: false.' },
284
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
285
+ },
286
+ additionalProperties: false,
287
+ },
288
+ },
289
+ {
290
+ name: 'addArchitectureRelationship',
291
+ description: 'Use for one relationship. Creates a new relationship or adds an existing relationship to view_ids. relationship.type is the ArchiMate 3.2 relationship type and is validated against endpoint element types. Set dryRun to preview without writing.',
292
+ inputSchema: {
293
+ type: 'object',
294
+ required: ['relationship', 'view_ids'],
295
+ properties: {
296
+ relationship: { type: 'object' },
297
+ view_ids: { type: 'array', minItems: 1, items: { type: 'string' } },
298
+ dryRun: { type: 'boolean', description: 'When true, validates and returns the result without writing to the graph. Default: false.' },
299
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
300
+ },
301
+ additionalProperties: false,
302
+ },
303
+ },
304
+ {
305
+ name: 'updateArchitectureRelationship',
306
+ description: 'Use for one global relationship metadata patch, such as name, statement, source_name, or target_name. Relationship id and type are immutable; remove and re-add to change them. Set dryRun to preview without writing.',
307
+ inputSchema: {
308
+ type: 'object',
309
+ required: ['id', 'patch'],
310
+ properties: {
311
+ id: { type: 'string' },
312
+ patch: { type: 'object' },
313
+ dryRun: { type: 'boolean', description: 'When true, validates and returns the result without writing to the graph. Default: false.' },
314
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
315
+ },
316
+ additionalProperties: false,
317
+ },
318
+ },
319
+ {
320
+ name: 'removeArchitectureRelationship',
321
+ description: 'Use for one relationship removal. With view_ids, removes only from those views; without view_ids, removes from all views and deletes it from the graph. Set dryRun to preview without writing.',
322
+ inputSchema: {
323
+ type: 'object',
324
+ required: ['id'],
325
+ properties: {
326
+ id: { type: 'string' },
327
+ view_ids: { type: 'array', minItems: 1, items: { type: 'string' } },
328
+ dryRun: { type: 'boolean', description: 'When true, validates and returns the result without writing to the graph. Default: false.' },
329
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
330
+ },
331
+ additionalProperties: false,
332
+ },
333
+ },
334
+ {
335
+ name: 'addArchitectureView',
336
+ description: 'Use for one view. The graph must have exactly one top-level view named SystemArchitecture; all sub-views must attach to an element with parent_element_id. Set dryRun to preview without writing.',
337
+ inputSchema: {
338
+ type: 'object',
339
+ required: ['view'],
340
+ properties: {
341
+ view: { type: 'object' },
342
+ dryRun: { type: 'boolean', description: 'When true, validates and returns the result without writing to the graph. Default: false.' },
343
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
344
+ },
345
+ additionalProperties: false,
346
+ },
347
+ },
348
+ {
349
+ name: 'updateArchitectureView',
350
+ description: 'Use for one view metadata or membership patch. Keep the one top-level view named SystemArchitecture and attach sub-views to parent elements. Set dryRun to preview without writing.',
351
+ inputSchema: {
352
+ type: 'object',
353
+ required: ['view_id', 'patch'],
354
+ properties: {
355
+ view_id: { type: 'string' },
356
+ patch: { type: 'object' },
357
+ dryRun: { type: 'boolean', description: 'When true, validates and returns the result without writing to the graph. Default: false.' },
358
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
359
+ },
360
+ additionalProperties: false,
361
+ },
362
+ },
363
+ {
364
+ name: 'removeArchitectureView',
365
+ description: 'Use for one view removal. After removal, every remaining element and relationship must still belong to at least one view. Set dryRun to preview without writing.',
366
+ inputSchema: {
367
+ type: 'object',
368
+ required: ['view_id'],
369
+ properties: {
370
+ view_id: { type: 'string' },
371
+ dryRun: { type: 'boolean', description: 'When true, validates and returns the result without writing to the graph. Default: false.' },
372
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
373
+ },
374
+ additionalProperties: false,
375
+ },
376
+ },
377
+ ];
378
+
379
+ function intentElementContextInputSchema() {
380
+ return {
381
+ type: 'object',
382
+ properties: {
383
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
384
+ elementId: { type: 'string' },
385
+ elementName: { type: 'string' },
386
+ profile: {
387
+ type: 'string',
388
+ enum: ['implementation-design', 'coding-repair', 'audit', 'generic-agent'],
389
+ description: 'Default: generic-agent. Affects workContext enrichment only; subgraph shape stays native.',
390
+ },
391
+ dependencyDepth: { type: 'number', description: 'Default: 2. Semantic dependencies needed by the focus element.' },
392
+ dependentDepth: { type: 'number', description: 'Default: 1. Semantic dependents that rely on the focus element.' },
393
+ associationDepth: { type: 'number', description: 'Default: 1. Association neighbors are expanded at least one layer.' },
394
+ associationNeighborDependencyDepth: { type: 'number', description: 'Default: 0. Optional dependency expansion from association neighbors.' },
395
+ },
396
+ additionalProperties: false,
397
+ };
398
+ }
399
+
400
+ function mutationInputSchema() {
401
+ return {
402
+ type: 'object',
403
+ required: ['mutations'],
404
+ properties: {
405
+ architecturePath: { type: 'string', description: `Default: ${DEFAULT_GRAPH_PATH}` },
406
+ mutations: {
407
+ type: 'array',
408
+ minItems: 1,
409
+ items: {
410
+ type: 'object',
411
+ required: ['type'],
412
+ properties: {
413
+ type: { type: 'string', enum: Array.from(HANDLED_MUTATION_TYPES) },
414
+ element: { type: 'object' },
415
+ relationship: { type: 'object' },
416
+ view: { type: 'object' },
417
+ id: { type: 'string' },
418
+ patch: { type: 'object' },
419
+ view_id: { type: 'string' },
420
+ view_ids: { type: 'array', minItems: 1, items: { type: 'string' } },
421
+ element_ids: { type: 'array', items: { type: 'string' } },
422
+ relationship_ids: { type: 'array', items: { type: 'string' } },
423
+ },
424
+ additionalProperties: false,
425
+ },
426
+ },
427
+ },
428
+ additionalProperties: false,
429
+ };
430
+ }
431
+
432
+ function resolveWorkspaceRoot() {
433
+ return getWorkspaceRoot();
434
+ }
435
+
436
+ function initializeWorkspace(request) {
437
+ return require('./argo-mcp-server.js').initializeWorkspace(
438
+ request && request.repositoryRoot ? request.repositoryRoot : resolveWorkspaceRoot(),
439
+ );
440
+ }
441
+
442
+ async function syncCanonicalStructuralProjection(request) {
443
+ const context = await loadContext(request);
444
+ return syncArchitectureToNeo4j({
445
+ architecturePath: context.graphPath.relativePath,
446
+ document: context.document,
447
+ });
448
+ }
449
+
450
+ function resolveWorkspacePath(workspaceRoot, relativePath) {
451
+ const normalizedPath = normalizeRelativePath(relativePath || DEFAULT_GRAPH_PATH);
452
+ const absolutePath = path.resolve(workspaceRoot, normalizedPath);
453
+ const normalizedRoot = path.resolve(workspaceRoot);
454
+ if (!absolutePath.startsWith(normalizedRoot)) {
455
+ throw new Error(`Path escapes workspace root: ${relativePath}`);
456
+ }
457
+ return { absolutePath, relativePath: normalizedPath };
458
+ }
459
+
460
+ function normalizeRelativePath(value) {
461
+ return String(value).replace(/\\/g, '/').replace(/^\/+/, '');
462
+ }
463
+
464
+ function resolveSchemaPath(workspaceRoot) {
465
+ const bundledSchemaPath = path.join(getArgoRoot(), 'schema', 'SystemArchitecture.schema.json');
466
+ if (fs.existsSync(bundledSchemaPath)) {
467
+ return { absolutePath: bundledSchemaPath, relativePath: SCHEMA_PATH_CANDIDATES[0] };
468
+ }
469
+
470
+ for (const candidate of SCHEMA_PATH_CANDIDATES) {
471
+ const absolutePath = path.join(workspaceRoot, candidate);
472
+ if (fs.existsSync(absolutePath)) {
473
+ return { absolutePath, relativePath: candidate };
474
+ }
475
+ const bundledPath = path.resolve(__dirname, '..', '..', candidate);
476
+ if (fs.existsSync(bundledPath)) {
477
+ return { absolutePath: bundledPath, relativePath: candidate };
478
+ }
479
+ }
480
+ throw new Error(`Unable to locate SystemArchitecture schema. Checked: ${SCHEMA_PATH_CANDIDATES.join(', ')}`);
481
+ }
482
+
483
+ function readJson(filePath, label) {
484
+ try {
485
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
486
+ } catch (error) {
487
+ throw new Error(`Failed to parse ${label}: ${String(error)}`);
488
+ }
489
+ }
490
+
491
+ async function loadContext(args = {}) {
492
+ const workspaceRoot = resolveWorkspaceRoot();
493
+ const graphPath = resolveWorkspacePath(workspaceRoot, args.architecturePath || DEFAULT_GRAPH_PATH);
494
+ const schemaPath = resolveSchemaPath(workspaceRoot);
495
+ const context = {
496
+ workspaceRoot,
497
+ graphPath,
498
+ schemaPath,
499
+ document: readJson(graphPath.absolutePath, graphPath.relativePath),
500
+ schema: readJson(schemaPath.absolutePath, schemaPath.relativePath),
501
+ };
502
+ context.neo4jSyncRecovery = await recoverNeo4jSyncIfNeeded({
503
+ architecturePath: graphPath.relativePath,
504
+ document: context.document,
505
+ });
506
+ return context;
507
+ }
508
+
509
+ function validateDocument(document, schema, options = {}) {
510
+ const errors = [];
511
+ validateAgainstSchema(document, schema, '#', errors, schema);
512
+ validateGraphSemantics(document, errors);
513
+ validateArchiMateEndpointMatrix(document, errors, {
514
+ touchedRelationshipIds: options.touchedRelationshipIds,
515
+ });
516
+ validateViewElementLimits(document, errors, {
517
+ touchedViewIds: options.validateAllViewElementLimits
518
+ ? (document.views || []).map(view => view && view.view_id)
519
+ : options.touchedViewIds,
520
+ });
521
+ return errors;
522
+ }
523
+
524
+ function buildIntentElementContext(context, args = {}) {
525
+ const profile = args.profile || 'generic-agent';
526
+ const focusResult = resolveFocusElement(context.document, args);
527
+ if (focusResult.status !== 'passed') {
528
+ return focusResult;
529
+ }
530
+
531
+ const dependencyDepth = normalizeDepth(args.dependencyDepth, 2);
532
+ const dependentDepth = normalizeDepth(args.dependentDepth, 1);
533
+ const associationDepth = Math.max(1, normalizeDepth(args.associationDepth, 1));
534
+ const associationNeighborDependencyDepth = normalizeDepth(args.associationNeighborDependencyDepth, 0);
535
+ const graphIndex = buildGraphIndex(context.document);
536
+ const focusElement = focusResult.element;
537
+ const includedElementIds = new Set([focusElement.id]);
538
+ const includedRelationshipIds = new Set();
539
+ const dependencyDepthByElement = new Map([[focusElement.id, 0]]);
540
+ const dependentDepthByElement = new Map([[focusElement.id, 0]]);
541
+ const associationDepthByElement = new Map([[focusElement.id, 0]]);
542
+
543
+ traverseSemanticContext({
544
+ startId: focusElement.id,
545
+ maxDepth: dependencyDepth,
546
+ mode: 'dependency',
547
+ graphIndex,
548
+ includedElementIds,
549
+ includedRelationshipIds,
550
+ depthByElement: dependencyDepthByElement,
551
+ });
552
+ traverseSemanticContext({
553
+ startId: focusElement.id,
554
+ maxDepth: dependentDepth,
555
+ mode: 'dependent',
556
+ graphIndex,
557
+ includedElementIds,
558
+ includedRelationshipIds,
559
+ depthByElement: dependentDepthByElement,
560
+ });
561
+ traverseSemanticContext({
562
+ startId: focusElement.id,
563
+ maxDepth: associationDepth,
564
+ mode: 'association',
565
+ graphIndex,
566
+ includedElementIds,
567
+ includedRelationshipIds,
568
+ depthByElement: associationDepthByElement,
569
+ });
570
+
571
+ if (associationNeighborDependencyDepth > 0) {
572
+ for (const [elementId, depth] of associationDepthByElement.entries()) {
573
+ if (elementId === focusElement.id || depth < 1) {
574
+ continue;
575
+ }
576
+ traverseSemanticContext({
577
+ startId: elementId,
578
+ maxDepth: associationNeighborDependencyDepth,
579
+ mode: 'dependency',
580
+ graphIndex,
581
+ includedElementIds,
582
+ includedRelationshipIds,
583
+ depthByElement: new Map([[elementId, 0]]),
584
+ });
585
+ }
586
+ }
587
+
588
+ includeViewAnchors(context.document, includedElementIds, includedRelationshipIds, graphIndex);
589
+ const boundary = buildBoundary({
590
+ graphIndex,
591
+ includedElementIds,
592
+ dependencyDepthByElement,
593
+ dependentDepthByElement,
594
+ associationDepthByElement,
595
+ dependencyDepth,
596
+ dependentDepth,
597
+ associationDepth,
598
+ });
599
+ const explorationHints = buildExplorationHints(boundary, {
600
+ profile,
601
+ dependencyDepth,
602
+ dependentDepth,
603
+ associationDepth,
604
+ });
605
+
606
+ return {
607
+ status: 'passed',
608
+ query: {
609
+ architecturePath: context.graphPath.relativePath,
610
+ elementId: focusElement.id,
611
+ elementName: focusElement.name,
612
+ profile,
613
+ dependencyDepth,
614
+ dependentDepth,
615
+ associationDepth,
616
+ associationNeighborDependencyDepth,
617
+ traversalMode: 'archimate-semantic',
618
+ },
619
+ focusElementId: focusElement.id,
620
+ subgraph: buildNativeSubgraph(context.document, includedElementIds, includedRelationshipIds),
621
+ boundary,
622
+ explorationHints,
623
+ workContext: {},
624
+ diagnostics: [],
625
+ };
626
+ }
627
+
628
+ function resolveFocusElement(document, args) {
629
+ if (args.elementId) {
630
+ const element = (document.elements || []).find(entry => entry.id === args.elementId);
631
+ if (!element) {
632
+ return {
633
+ status: 'failed',
634
+ error: `Element '${args.elementId}' does not exist`,
635
+ candidates: [],
636
+ };
637
+ }
638
+ return { status: 'passed', element };
639
+ }
640
+
641
+ if (!args.elementName) {
642
+ return {
643
+ status: 'failed',
644
+ error: 'elementId or elementName is required',
645
+ candidates: [],
646
+ };
647
+ }
648
+
649
+ const matches = (document.elements || []).filter(element => element.name === args.elementName);
650
+ if (matches.length === 1) {
651
+ return { status: 'passed', element: matches[0] };
652
+ }
653
+ if (matches.length > 1) {
654
+ return {
655
+ status: 'ambiguous',
656
+ error: `Element name '${args.elementName}' matched multiple elements`,
657
+ candidates: matches.map(element => ({ id: element.id, name: element.name, type: element.type })),
658
+ };
659
+ }
660
+ return {
661
+ status: 'failed',
662
+ error: `Element name '${args.elementName}' does not exist`,
663
+ candidates: [],
664
+ };
665
+ }
666
+
667
+ function normalizeDepth(value, defaultValue) {
668
+ if (value === undefined || value === null || value === '') {
669
+ return defaultValue;
670
+ }
671
+ const numericValue = Number(value);
672
+ if (!Number.isFinite(numericValue) || numericValue < 0) {
673
+ return defaultValue;
674
+ }
675
+ return Math.floor(numericValue);
676
+ }
677
+
678
+ function buildGraphIndex(document) {
679
+ const relationshipById = new Map();
680
+ const elementById = new Map((document.elements || []).map(element => [element.id, element]));
681
+ const relationshipsByElementId = new Map();
682
+ for (const relationship of document.relationships || []) {
683
+ relationshipById.set(relationship.id, relationship);
684
+ addIndexedRelationship(relationshipsByElementId, relationship.source_id, relationship);
685
+ addIndexedRelationship(relationshipsByElementId, relationship.target_id, relationship);
686
+ }
687
+ return { elementById, relationshipById, relationshipsByElementId };
688
+ }
689
+
690
+ function addIndexedRelationship(index, elementId, relationship) {
691
+ if (!index.has(elementId)) {
692
+ index.set(elementId, []);
693
+ }
694
+ index.get(elementId).push(relationship);
695
+ }
696
+
697
+ function traverseSemanticContext(options) {
698
+ const {
699
+ startId,
700
+ maxDepth,
701
+ mode,
702
+ graphIndex,
703
+ includedElementIds,
704
+ includedRelationshipIds,
705
+ depthByElement,
706
+ } = options;
707
+ if (maxDepth < 1) {
708
+ return;
709
+ }
710
+
711
+ const queue = [{ elementId: startId, depth: 0 }];
712
+ for (let index = 0; index < queue.length; index += 1) {
713
+ const current = queue[index];
714
+ if (current.depth >= maxDepth) {
715
+ continue;
716
+ }
717
+ for (const edge of resolveSemanticEdges(current.elementId, graphIndex)) {
718
+ if (edge.kind !== mode) {
719
+ continue;
720
+ }
721
+ const nextDepth = current.depth + 1;
722
+ includedElementIds.add(edge.neighborId);
723
+ includedRelationshipIds.add(edge.relationship.id);
724
+ if (!depthByElement.has(edge.neighborId) || nextDepth < depthByElement.get(edge.neighborId)) {
725
+ depthByElement.set(edge.neighborId, nextDepth);
726
+ queue.push({ elementId: edge.neighborId, depth: nextDepth });
727
+ }
728
+ }
729
+ }
730
+ }
731
+
732
+ function resolveSemanticEdges(elementId, graphIndex) {
733
+ const edges = [];
734
+ for (const relationship of graphIndex.relationshipsByElementId.get(elementId) || []) {
735
+ const isSource = relationship.source_id === elementId;
736
+ const neighborId = isSource ? relationship.target_id : relationship.source_id;
737
+ const relationshipType = relationship.type;
738
+
739
+ if (relationshipType === 'Association') {
740
+ edges.push({ kind: 'association', neighborId, relationship });
741
+ continue;
742
+ }
743
+
744
+ const sourceDependsOnTarget = ['Access', 'Assignment', 'Specialization', 'Composition', 'Aggregation'].includes(relationshipType);
745
+ const targetDependsOnSource = ['Serving', 'Realization', 'Flow', 'Triggering', 'Influence'].includes(relationshipType);
746
+ if (sourceDependsOnTarget) {
747
+ edges.push({ kind: isSource ? 'dependency' : 'dependent', neighborId, relationship });
748
+ continue;
749
+ }
750
+ if (targetDependsOnSource) {
751
+ edges.push({ kind: isSource ? 'dependent' : 'dependency', neighborId, relationship });
752
+ }
753
+ }
754
+ return edges;
755
+ }
756
+
757
+ function includeViewAnchors(document, includedElementIds, includedRelationshipIds, graphIndex) {
758
+ let changed = true;
759
+ while (changed) {
760
+ changed = false;
761
+ for (const view of document.views || []) {
762
+ if (!viewTouchesSubgraph(view, includedElementIds, includedRelationshipIds)) {
763
+ continue;
764
+ }
765
+ if (view.parent_element_id && !includedElementIds.has(view.parent_element_id) && graphIndex.elementById.has(view.parent_element_id)) {
766
+ includedElementIds.add(view.parent_element_id);
767
+ changed = true;
768
+ }
769
+ }
770
+ for (const elementId of Array.from(includedElementIds)) {
771
+ const element = graphIndex.elementById.get(elementId);
772
+ if (element && element.parent && !includedElementIds.has(element.parent) && graphIndex.elementById.has(element.parent)) {
773
+ includedElementIds.add(element.parent);
774
+ changed = true;
775
+ }
776
+ }
777
+ }
778
+ }
779
+
780
+ function viewTouchesSubgraph(view, includedElementIds, includedRelationshipIds) {
781
+ return (view.included_elements || []).some(elementId => includedElementIds.has(elementId))
782
+ || (view.included_relationships || []).some(relationshipId => includedRelationshipIds.has(relationshipId));
783
+ }
784
+
785
+ function buildBoundary(options) {
786
+ return {
787
+ truncatedDependencies: collectTruncatedBoundary({
788
+ ...options,
789
+ depthByElement: mergeBoundaryDepths(options.dependencyDepthByElement, options.associationDepthByElement),
790
+ maxDepth: options.dependencyDepth,
791
+ kind: 'dependency',
792
+ }),
793
+ truncatedDependents: collectTruncatedBoundary({
794
+ ...options,
795
+ depthByElement: options.dependentDepthByElement,
796
+ maxDepth: options.dependentDepth,
797
+ kind: 'dependent',
798
+ }),
799
+ };
800
+ }
801
+
802
+ function mergeBoundaryDepths(primaryDepths, associationDepths) {
803
+ const merged = new Map(primaryDepths);
804
+ for (const [elementId, depth] of associationDepths.entries()) {
805
+ if (!merged.has(elementId) || depth < merged.get(elementId)) {
806
+ merged.set(elementId, depth);
807
+ }
808
+ }
809
+ return merged;
810
+ }
811
+
812
+ function collectTruncatedBoundary(options) {
813
+ const {
814
+ graphIndex,
815
+ includedElementIds,
816
+ depthByElement,
817
+ maxDepth,
818
+ kind,
819
+ } = options;
820
+ const truncated = [];
821
+ if (maxDepth < 0) {
822
+ return truncated;
823
+ }
824
+ for (const [elementId, depth] of depthByElement.entries()) {
825
+ if (depth < maxDepth && kind !== 'dependency') {
826
+ continue;
827
+ }
828
+ const unexpandedEdges = resolveSemanticEdges(elementId, graphIndex).filter(edge => (
829
+ (edge.kind === kind || (kind === 'dependency' && edge.kind === 'association'))
830
+ && !includedElementIds.has(edge.neighborId)
831
+ ));
832
+ if (unexpandedEdges.length === 0) {
833
+ continue;
834
+ }
835
+ const element = graphIndex.elementById.get(elementId);
836
+ truncated.push({
837
+ elementId,
838
+ elementName: element ? element.name : undefined,
839
+ direction: kind,
840
+ remainingEdgeCount: unexpandedEdges.length,
841
+ relationshipIds: unexpandedEdges.map(edge => edge.relationship.id),
842
+ reason: `${kind} depth limit reached`,
843
+ });
844
+ }
845
+ return truncated;
846
+ }
847
+
848
+ function buildExplorationHints(boundary, defaults) {
849
+ const hints = [];
850
+ for (const entry of boundary.truncatedDependencies || []) {
851
+ hints.push({
852
+ reason: `Element ${entry.elementId} has unexpanded dependency context`,
853
+ suggestedTool: 'getIntentElementContext',
854
+ suggestedArguments: {
855
+ elementId: entry.elementId,
856
+ profile: defaults.profile,
857
+ dependencyDepth: Math.max(1, defaults.dependencyDepth),
858
+ dependentDepth: 0,
859
+ associationDepth: defaults.associationDepth,
860
+ },
861
+ });
862
+ }
863
+ for (const entry of boundary.truncatedDependents || []) {
864
+ hints.push({
865
+ reason: `Element ${entry.elementId} has unexpanded dependent context`,
866
+ suggestedTool: 'getIntentElementContext',
867
+ suggestedArguments: {
868
+ elementId: entry.elementId,
869
+ profile: defaults.profile,
870
+ dependencyDepth: 0,
871
+ dependentDepth: Math.max(1, defaults.dependentDepth),
872
+ associationDepth: defaults.associationDepth,
873
+ },
874
+ });
875
+ }
876
+ return hints;
877
+ }
878
+
879
+ function buildNativeSubgraph(document, includedElementIds, includedRelationshipIds) {
880
+ const elements = (document.elements || [])
881
+ .filter(element => includedElementIds.has(element.id))
882
+ .map(clone);
883
+ const relationships = (document.relationships || [])
884
+ .filter(relationship => includedRelationshipIds.has(relationship.id))
885
+ .map(clone);
886
+ const views = (document.views || [])
887
+ .filter(view => viewTouchesSubgraph(view, includedElementIds, includedRelationshipIds))
888
+ .map(view => {
889
+ const viewCopy = clone(view);
890
+ if (Array.isArray(viewCopy.included_elements)) {
891
+ viewCopy.included_elements = viewCopy.included_elements.filter(elementId => includedElementIds.has(elementId));
892
+ }
893
+ if (Array.isArray(viewCopy.included_relationships)) {
894
+ viewCopy.included_relationships = viewCopy.included_relationships.filter(relationshipId => includedRelationshipIds.has(relationshipId));
895
+ }
896
+ return viewCopy;
897
+ });
898
+ return { elements, relationships, views };
899
+ }
900
+
901
+ function applyMutations(document, mutations) {
902
+ const nextDocument = clone(document);
903
+ const touchedElementIds = new Set();
904
+ const touchedRelationshipIds = new Set();
905
+ const touchedViewIds = new Set();
906
+ const viewLimitCheckIds = new Set();
907
+ const mutationSummaries = [];
908
+
909
+ if (!Array.isArray(mutations) || mutations.length === 0) {
910
+ throw new Error('mutations must contain at least one mutation');
911
+ }
912
+
913
+ for (const mutation of mutations) {
914
+ if (!mutation || typeof mutation !== 'object' || !HANDLED_MUTATION_TYPES.has(mutation.type)) {
915
+ throw new Error(`Unsupported mutation type: ${mutation && mutation.type}`);
916
+ }
917
+
918
+ if (mutation.type === 'addElement') {
919
+ requireObject(mutation.element, 'mutation.element');
920
+ const scopedViews = requireViewScope(nextDocument.views, mutation.view_ids, 'mutation.view_ids');
921
+ requireId(mutation.element.id, 'mutation.element.id');
922
+ const existingElement = findById(nextDocument.elements, mutation.element.id);
923
+ if (!existingElement) {
924
+ nextDocument.elements.push(clone(mutation.element));
925
+ }
926
+ for (const view of scopedViews) {
927
+ view.included_elements = addUnique(view.included_elements || [], [mutation.element.id]);
928
+ touchedViewIds.add(view.view_id);
929
+ viewLimitCheckIds.add(view.view_id);
930
+ }
931
+ touchedElementIds.add(mutation.element.id);
932
+ mutationSummaries.push({
933
+ type: mutation.type,
934
+ id: mutation.element.id,
935
+ view_ids: mutation.view_ids,
936
+ created: !existingElement,
937
+ });
938
+ continue;
939
+ }
940
+
941
+ if (mutation.type === 'updateElement') {
942
+ requireId(mutation.id, 'mutation.id');
943
+ requireObject(mutation.patch, 'mutation.patch');
944
+ const element = findById(nextDocument.elements, mutation.id);
945
+ if (!element) {
946
+ throw new Error(`Element '${mutation.id}' does not exist`);
947
+ }
948
+ requirePatchDoesNotChangeElementIdentityOrType(mutation.id, mutation.patch);
949
+ Object.assign(element, clone(mutation.patch));
950
+ touchedElementIds.add(element.id);
951
+ mutationSummaries.push({ type: mutation.type, id: element.id });
952
+ continue;
953
+ }
954
+
955
+ if (mutation.type === 'removeElement') {
956
+ requireId(mutation.id, 'mutation.id');
957
+ const element = findById(nextDocument.elements, mutation.id);
958
+ if (!element) {
959
+ throw new Error(`Element '${mutation.id}' does not exist`);
960
+ }
961
+ const scopedViews = mutation.view_ids === undefined
962
+ ? nextDocument.views
963
+ : requireViewScope(nextDocument.views, mutation.view_ids, 'mutation.view_ids');
964
+ const relatedRelationshipIds = nextDocument.relationships
965
+ .filter(relationship => relationship.source_id === mutation.id || relationship.target_id === mutation.id)
966
+ .map(relationship => relationship.id);
967
+ for (const view of scopedViews) {
968
+ view.included_elements = removeEntries(view.included_elements || [], [mutation.id]);
969
+ view.included_relationships = removeEntries(view.included_relationships || [], relatedRelationshipIds);
970
+ touchedViewIds.add(view.view_id);
971
+ }
972
+ const stillIncludedInView = nextDocument.views.some(view => (
973
+ Array.isArray(view.included_elements) && view.included_elements.includes(mutation.id)
974
+ ));
975
+ if (!stillIncludedInView) {
976
+ for (const view of nextDocument.views) {
977
+ view.included_relationships = removeEntries(view.included_relationships || [], relatedRelationshipIds);
978
+ touchedViewIds.add(view.view_id);
979
+ }
980
+ nextDocument.elements = nextDocument.elements.filter(entry => entry.id !== mutation.id);
981
+ }
982
+ const relationshipIdsStillInViews = new Set();
983
+ for (const view of nextDocument.views) {
984
+ for (const relationshipId of view.included_relationships || []) {
985
+ relationshipIdsStillInViews.add(relationshipId);
986
+ }
987
+ }
988
+ nextDocument.relationships = nextDocument.relationships.filter(relationship => (
989
+ !relatedRelationshipIds.includes(relationship.id) || relationshipIdsStillInViews.has(relationship.id)
990
+ ));
991
+ touchedElementIds.add(mutation.id);
992
+ for (const relationshipId of relatedRelationshipIds) {
993
+ touchedRelationshipIds.add(relationshipId);
994
+ }
995
+ mutationSummaries.push({
996
+ type: mutation.type,
997
+ id: mutation.id,
998
+ view_ids: mutation.view_ids,
999
+ removed_from_graph: !stillIncludedInView,
1000
+ removed_relationship_ids: relatedRelationshipIds.filter(relationshipId => !relationshipIdsStillInViews.has(relationshipId)),
1001
+ });
1002
+ continue;
1003
+ }
1004
+
1005
+ if (mutation.type === 'addRelationship') {
1006
+ requireObject(mutation.relationship, 'mutation.relationship');
1007
+ const scopedViews = requireViewScope(nextDocument.views, mutation.view_ids, 'mutation.view_ids');
1008
+ requireId(mutation.relationship.id, 'mutation.relationship.id');
1009
+ const existingRelationship = findById(nextDocument.relationships, mutation.relationship.id);
1010
+ if (!existingRelationship) {
1011
+ nextDocument.relationships.push(clone(mutation.relationship));
1012
+ }
1013
+ for (const view of scopedViews) {
1014
+ view.included_elements = addUnique(view.included_elements || [], [
1015
+ mutation.relationship.source_id,
1016
+ mutation.relationship.target_id,
1017
+ ]);
1018
+ view.included_relationships = addUnique(view.included_relationships || [], [mutation.relationship.id]);
1019
+ touchedViewIds.add(view.view_id);
1020
+ }
1021
+ touchedRelationshipIds.add(mutation.relationship.id);
1022
+ mutationSummaries.push({
1023
+ type: mutation.type,
1024
+ id: mutation.relationship.id,
1025
+ view_ids: mutation.view_ids,
1026
+ created: !existingRelationship,
1027
+ });
1028
+ continue;
1029
+ }
1030
+
1031
+ if (mutation.type === 'updateRelationship') {
1032
+ requireId(mutation.id, 'mutation.id');
1033
+ requireObject(mutation.patch, 'mutation.patch');
1034
+ const relationship = findById(nextDocument.relationships, mutation.id);
1035
+ if (!relationship) {
1036
+ throw new Error(`Relationship '${mutation.id}' does not exist`);
1037
+ }
1038
+ requirePatchDoesNotChangeRelationshipIdentityOrType(mutation.id, mutation.patch);
1039
+ Object.assign(relationship, clone(mutation.patch));
1040
+ for (const view of nextDocument.views) {
1041
+ if ((view.included_relationships || []).includes(relationship.id)) {
1042
+ view.included_elements = addUnique(view.included_elements || [], [
1043
+ relationship.source_id,
1044
+ relationship.target_id,
1045
+ ]);
1046
+ }
1047
+ }
1048
+ touchedRelationshipIds.add(relationship.id);
1049
+ mutationSummaries.push({ type: mutation.type, id: relationship.id });
1050
+ continue;
1051
+ }
1052
+
1053
+ if (mutation.type === 'removeRelationship') {
1054
+ requireId(mutation.id, 'mutation.id');
1055
+ const relationship = findById(nextDocument.relationships, mutation.id);
1056
+ if (!relationship) {
1057
+ throw new Error(`Relationship '${mutation.id}' does not exist`);
1058
+ }
1059
+ const scopedViews = mutation.view_ids === undefined
1060
+ ? nextDocument.views
1061
+ : requireViewScope(nextDocument.views, mutation.view_ids, 'mutation.view_ids');
1062
+ for (const view of scopedViews) {
1063
+ view.included_relationships = removeEntries(view.included_relationships || [], [mutation.id]);
1064
+ touchedViewIds.add(view.view_id);
1065
+ }
1066
+ const stillIncludedInView = nextDocument.views.some(view => (
1067
+ Array.isArray(view.included_relationships) && view.included_relationships.includes(mutation.id)
1068
+ ));
1069
+ if (!stillIncludedInView) {
1070
+ nextDocument.relationships = nextDocument.relationships.filter(entry => entry.id !== mutation.id);
1071
+ }
1072
+ touchedRelationshipIds.add(mutation.id);
1073
+ mutationSummaries.push({
1074
+ type: mutation.type,
1075
+ id: mutation.id,
1076
+ view_ids: mutation.view_ids,
1077
+ removed_from_graph: !stillIncludedInView,
1078
+ });
1079
+ continue;
1080
+ }
1081
+
1082
+ if (mutation.type === 'addView') {
1083
+ requireObject(mutation.view, 'mutation.view');
1084
+ if (findView(nextDocument.views, mutation.view.view_id)) {
1085
+ throw new Error(`View '${mutation.view.view_id}' already exists`);
1086
+ }
1087
+ nextDocument.views.push(clone(mutation.view));
1088
+ touchedViewIds.add(mutation.view.view_id);
1089
+ viewLimitCheckIds.add(mutation.view.view_id);
1090
+ mutationSummaries.push({ type: mutation.type, id: mutation.view.view_id });
1091
+ continue;
1092
+ }
1093
+
1094
+ if (mutation.type === 'updateView') {
1095
+ const viewId = mutation.view_id || mutation.id;
1096
+ requireId(viewId, 'mutation.view_id');
1097
+ requireObject(mutation.patch, 'mutation.patch');
1098
+ const view = findView(nextDocument.views, viewId);
1099
+ if (!view) {
1100
+ throw new Error(`View '${viewId}' does not exist`);
1101
+ }
1102
+ Object.assign(view, clone(mutation.patch));
1103
+ touchedViewIds.add(view.view_id);
1104
+ if (Object.prototype.hasOwnProperty.call(mutation.patch, 'included_elements')) {
1105
+ viewLimitCheckIds.add(view.view_id);
1106
+ }
1107
+ mutationSummaries.push({ type: mutation.type, id: view.view_id });
1108
+ continue;
1109
+ }
1110
+
1111
+ if (mutation.type === 'removeView') {
1112
+ requireId(mutation.view_id, 'mutation.view_id');
1113
+ const beforeCount = nextDocument.views.length;
1114
+ nextDocument.views = nextDocument.views.filter(view => view.view_id !== mutation.view_id);
1115
+ if (nextDocument.views.length === beforeCount) {
1116
+ throw new Error(`View '${mutation.view_id}' does not exist`);
1117
+ }
1118
+ touchedViewIds.add(mutation.view_id);
1119
+ mutationSummaries.push({ type: mutation.type, id: mutation.view_id });
1120
+ continue;
1121
+ }
1122
+
1123
+ }
1124
+
1125
+ return {
1126
+ document: nextDocument,
1127
+ touchedElementIds: Array.from(touchedElementIds),
1128
+ touchedRelationshipIds: Array.from(touchedRelationshipIds),
1129
+ touchedViewIds: Array.from(touchedViewIds),
1130
+ viewLimitCheckIds: Array.from(viewLimitCheckIds),
1131
+ mutationSummaries,
1132
+ };
1133
+ }
1134
+
1135
+ function requireObject(value, label) {
1136
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1137
+ throw new Error(`${label} must be an object`);
1138
+ }
1139
+ }
1140
+
1141
+ function requireId(value, label) {
1142
+ if (typeof value !== 'string' || value.length === 0) {
1143
+ throw new Error(`${label} must be a non-empty string`);
1144
+ }
1145
+ }
1146
+
1147
+ function requireViewScope(views, viewIds, label) {
1148
+ if (!Array.isArray(viewIds) || viewIds.length === 0) {
1149
+ throw new Error(`${label} must contain at least one view id`);
1150
+ }
1151
+
1152
+ const scopedViews = [];
1153
+ const seenViewIds = new Set();
1154
+ for (const viewId of viewIds) {
1155
+ requireId(viewId, `${label}[]`);
1156
+ if (seenViewIds.has(viewId)) {
1157
+ continue;
1158
+ }
1159
+ const view = findView(views, viewId);
1160
+ if (!view) {
1161
+ throw new Error(`View '${viewId}' does not exist`);
1162
+ }
1163
+ scopedViews.push(view);
1164
+ seenViewIds.add(viewId);
1165
+ }
1166
+ return scopedViews;
1167
+ }
1168
+
1169
+ function requirePatchDoesNotChangeElementIdentityOrType(elementId, patch) {
1170
+ if (Object.prototype.hasOwnProperty.call(patch, 'id')) {
1171
+ throw new Error(`Element '${elementId}' id cannot be updated; remove and re-add the element to change its id`);
1172
+ }
1173
+ if (Object.prototype.hasOwnProperty.call(patch, 'type')) {
1174
+ throw new Error(`Element '${elementId}' type cannot be updated; remove and re-add the element to change its type`);
1175
+ }
1176
+ }
1177
+
1178
+ function requirePatchDoesNotChangeRelationshipIdentityOrType(relationshipId, patch) {
1179
+ if (Object.prototype.hasOwnProperty.call(patch, 'id')) {
1180
+ throw new Error(`Relationship '${relationshipId}' id cannot be updated; remove and re-add the relationship to change its id`);
1181
+ }
1182
+ if (Object.prototype.hasOwnProperty.call(patch, 'type')) {
1183
+ throw new Error(`Relationship '${relationshipId}' type cannot be updated; remove and re-add the relationship to change its type`);
1184
+ }
1185
+ }
1186
+
1187
+ function findById(entries, id) {
1188
+ return Array.isArray(entries) ? entries.find(entry => entry && entry.id === id) : undefined;
1189
+ }
1190
+
1191
+ function findView(entries, viewId) {
1192
+ return Array.isArray(entries) ? entries.find(entry => entry && entry.view_id === viewId) : undefined;
1193
+ }
1194
+
1195
+ function addUnique(existing, additions) {
1196
+ const result = Array.isArray(existing) ? [...existing] : [];
1197
+ for (const addition of additions) {
1198
+ if (!result.includes(addition)) {
1199
+ result.push(addition);
1200
+ }
1201
+ }
1202
+ return result;
1203
+ }
1204
+
1205
+ function removeEntries(existing, removals) {
1206
+ const removalSet = new Set(removals);
1207
+ return (Array.isArray(existing) ? existing : []).filter(entry => !removalSet.has(entry));
1208
+ }
1209
+
1210
+ async function buildMutationResult(context, mutations, write) {
1211
+ const beforeSummary = summarizeDocument(context.document);
1212
+ let mutationResult;
1213
+ try {
1214
+ mutationResult = applyMutations(context.document, mutations);
1215
+ } catch (error) {
1216
+ const errors = [String(error && error.message ? error.message : error)];
1217
+ return {
1218
+ status: 'failed',
1219
+ written: false,
1220
+ graphPath: context.graphPath.relativePath,
1221
+ schemaPath: context.schemaPath.relativePath,
1222
+ mutations: [],
1223
+ touchedElementIds: [],
1224
+ touchedRelationshipIds: [],
1225
+ before: beforeSummary,
1226
+ after: beforeSummary,
1227
+ errors,
1228
+ guidance: buildFailureGuidance(errors),
1229
+ };
1230
+ }
1231
+ const errors = validateDocument(mutationResult.document, context.schema, {
1232
+ touchedRelationshipIds: mutationResult.touchedRelationshipIds,
1233
+ touchedViewIds: mutationResult.viewLimitCheckIds,
1234
+ });
1235
+ const afterSummary = summarizeDocument(mutationResult.document);
1236
+ const result = {
1237
+ status: errors.length === 0 ? 'passed' : 'failed',
1238
+ written: false,
1239
+ graphPath: context.graphPath.relativePath,
1240
+ schemaPath: context.schemaPath.relativePath,
1241
+ mutations: mutationResult.mutationSummaries,
1242
+ touchedElementIds: mutationResult.touchedElementIds,
1243
+ touchedRelationshipIds: mutationResult.touchedRelationshipIds,
1244
+ touchedViewIds: mutationResult.touchedViewIds,
1245
+ viewLimitCheckIds: mutationResult.viewLimitCheckIds,
1246
+ before: beforeSummary,
1247
+ after: afterSummary,
1248
+ errors,
1249
+ };
1250
+ if (errors.length > 0) {
1251
+ result.guidance = buildFailureGuidance(errors);
1252
+ }
1253
+
1254
+ if (errors.length > 0 || !write) {
1255
+ return result;
1256
+ }
1257
+
1258
+ writeGraph(context.graphPath.absolutePath, mutationResult.document);
1259
+ result.written = true;
1260
+
1261
+ if (shouldSyncCanonicalGraphToNeo4j(context.graphPath.relativePath)) {
1262
+ try {
1263
+ const syncResult = await syncArchitectureToNeo4j({
1264
+ architecturePath: context.graphPath.relativePath,
1265
+ document: mutationResult.document,
1266
+ });
1267
+ result.neo4jSync = {
1268
+ status: 'passed',
1269
+ graphKey: syncResult.graphKey,
1270
+ counts: syncResult.counts,
1271
+ };
1272
+ } catch (error) {
1273
+ const errorMessage = String(error && error.message ? error.message : error);
1274
+ result.neo4jSync = {
1275
+ status: 'failed',
1276
+ error: errorMessage,
1277
+ };
1278
+ result.warnings = addUnique(result.warnings || [], [
1279
+ `SystemArchitecture.json was written, but Neo4j sync failed: ${errorMessage}`,
1280
+ 'Run node .argo/scripts/syncSystemArchitectureToNeo4j.js to rebuild the Neo4j projection for the canonical intent graph.',
1281
+ ]);
1282
+ }
1283
+ }
1284
+
1285
+ await attachMutationEmbeddingLifecycle(context, result, mutationResult.document);
1286
+
1287
+ return result;
1288
+ }
1289
+
1290
+ function shouldSyncCanonicalGraphToNeo4j(relativeGraphPath) {
1291
+ return normalizeRelativePath(relativeGraphPath) === normalizeRelativePath(NEO4J_DEFAULT_GRAPH_PATH);
1292
+ }
1293
+
1294
+ async function attachMutationEmbeddingLifecycle(context, result, document) {
1295
+ if (!shouldRunMutationEmbeddingLifecycle(result)) {
1296
+ return;
1297
+ }
1298
+ try {
1299
+ const lifecycle = require(
1300
+ './graph-rag/mutationEmbeddingVectorLifecycle.js'
1301
+ ).createPersistentMutationEmbeddingLifecycle({
1302
+ repositoryRoot: resolveWorkspaceRoot(),
1303
+ });
1304
+ const embeddingLifecycle = await lifecycle.reconcile({
1305
+ canonicalWrite: {
1306
+ written: true,
1307
+ architecturePath: result.graphPath,
1308
+ document,
1309
+ mutations: result.mutations,
1310
+ touchedElementIds: result.touchedElementIds,
1311
+ touchedRelationshipIds: result.touchedRelationshipIds,
1312
+ touchedViewIds: result.touchedViewIds,
1313
+ },
1314
+ preview: false,
1315
+ gates: {
1316
+ [LIVE_PROVIDER_OPT_IN]: process.env[LIVE_PROVIDER_OPT_IN],
1317
+ [W31_LIVE_OPT_IN]: process.env[W31_LIVE_OPT_IN],
1318
+ },
1319
+ });
1320
+ result.embeddingLifecycle = embeddingLifecycle;
1321
+ result.alignment = embeddingLifecycle.alignment || buildMutationAlignment(embeddingLifecycle);
1322
+ result.businessComplete = result.alignment && result.alignment.state === 'Aligned';
1323
+ } catch (error) {
1324
+ result.embeddingLifecycle = buildMutationEmbeddingLifecycleFailure(error, result);
1325
+ result.alignment = buildMutationAlignment(result.embeddingLifecycle);
1326
+ result.businessComplete = false;
1327
+ }
1328
+ }
1329
+
1330
+ function shouldRunMutationEmbeddingLifecycle(result) {
1331
+ return result
1332
+ && result.status === 'passed'
1333
+ && result.written === true
1334
+ && normalizeRelativePath(result.graphPath) === normalizeRelativePath(DEFAULT_GRAPH_PATH)
1335
+ && (
1336
+ (Array.isArray(result.touchedElementIds) && result.touchedElementIds.length > 0)
1337
+ || (Array.isArray(result.touchedRelationshipIds) && result.touchedRelationshipIds.length > 0)
1338
+ || (Array.isArray(result.touchedViewIds) && result.touchedViewIds.length > 0)
1339
+ );
1340
+ }
1341
+
1342
+ function buildMutationAlignment(embeddingLifecycle) {
1343
+ const state = embeddingLifecycle && embeddingLifecycle.alignmentState
1344
+ ? embeddingLifecycle.alignmentState
1345
+ : 'Failed';
1346
+ return Object.freeze({
1347
+ state,
1348
+ pureSemanticQueryRejected: state !== 'Aligned',
1349
+ category: state === 'Aligned' ? 'SEMANTIC_INDEX_ALIGNED' : 'SEMANTIC_INDEX_NOT_ALIGNED',
1350
+ fullSnapshotFallback: false,
1351
+ });
1352
+ }
1353
+
1354
+ function buildMutationEmbeddingLifecycleFailure(error, result) {
1355
+ const category = error && error.category ? error.category : 'W31_MUTATION_VECTOR_LIFECYCLE_FAILED';
1356
+ return Object.freeze({
1357
+ mutation: Object.freeze({
1358
+ applied: true,
1359
+ architecturePath: result.graphPath,
1360
+ }),
1361
+ touchedRecords: [],
1362
+ provider: Object.freeze({
1363
+ profile: Object.freeze({
1364
+ provider: W31_APPROVED_PROFILE.provider,
1365
+ model: W31_APPROVED_PROFILE.model,
1366
+ version: W31_APPROVED_PROFILE.version,
1367
+ dimensions: W31_APPROVED_PROFILE.dimensions,
1368
+ }),
1369
+ offlineEvidenceAccepted: false,
1370
+ realRequestCount: 0,
1371
+ }),
1372
+ vectorEvidence: [],
1373
+ vectorQuery: Object.freeze({
1374
+ returnedTouchedRecordIds: [],
1375
+ }),
1376
+ alignmentState: 'Failed',
1377
+ failureMatrix: Object.freeze([
1378
+ Object.freeze({
1379
+ name: 'automatic-mutation-lifecycle-failure',
1380
+ alignmentState: 'Failed',
1381
+ category,
1382
+ pureSemanticQueryRejected: true,
1383
+ semanticQueryRejection: Object.freeze({
1384
+ request: null,
1385
+ status: 'rejected',
1386
+ alignmentState: 'Failed',
1387
+ category: 'SEMANTIC_INDEX_NOT_ALIGNED',
1388
+ fullSnapshotFallback: false,
1389
+ }),
1390
+ offlineEvidenceAccepted: false,
1391
+ }),
1392
+ ]),
1393
+ pureSemanticQueryRejected: true,
1394
+ semanticQueryRejection: Object.freeze({
1395
+ request: null,
1396
+ status: 'rejected',
1397
+ alignmentState: 'Failed',
1398
+ category: 'SEMANTIC_INDEX_NOT_ALIGNED',
1399
+ fullSnapshotFallback: false,
1400
+ }),
1401
+ secretLeaks: [],
1402
+ });
1403
+ }
1404
+
1405
+ function buildFailureGuidance(errors) {
1406
+ const guidance = [];
1407
+ for (const error of errors || []) {
1408
+ addGuidanceForError(guidance, String(error));
1409
+ }
1410
+ if (guidance.length === 0 && Array.isArray(errors) && errors.length > 0) {
1411
+ 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.');
1412
+ }
1413
+ return guidance;
1414
+ }
1415
+
1416
+ function addGuidanceForError(guidance, error) {
1417
+ if (error.includes('mutation.view_ids must contain at least one view id')) {
1418
+ 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.');
1419
+ }
1420
+ if (error.includes('violates ArchiMate 3.2 relationship matrix')) {
1421
+ pushUnique(guidance, 'Check relationship.type and the source and target element types against ArchiMate 3.2. If the intended meaning is still valid, choose a compliant relationship type or change the endpoint element types by remove-and-add.');
1422
+ }
1423
+ if (error.includes('uses unsupported ArchiMate relationship type')) {
1424
+ pushUnique(guidance, 'Use relationship.type for the ArchiMate relationship type and choose one of the schema-supported ArchiMate 3.2 relationship types.');
1425
+ }
1426
+ if (error.includes('id cannot be updated') || error.includes('type cannot be updated')) {
1427
+ 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.');
1428
+ }
1429
+ if (error.includes('must be included in at least one view')) {
1430
+ 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.');
1431
+ }
1432
+ if (error.includes('must declare parent_element_id') || error.includes('top-level view')) {
1433
+ pushUnique(guidance, 'Keep exactly one top-level view named SystemArchitecture. For any sub-view, set parent_element_id to an existing element and keep parent_element_name aligned with that element name.');
1434
+ }
1435
+ if (error.includes('must contain at most 15 elements')) {
1436
+ pushUnique(guidance, 'Do not force more than 15 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.');
1437
+ }
1438
+ if (error.includes('does not exist') || error.includes('references missing')) {
1439
+ 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.');
1440
+ }
1441
+ }
1442
+
1443
+ function pushUnique(entries, entry) {
1444
+ if (!entries.includes(entry)) {
1445
+ entries.push(entry);
1446
+ }
1447
+ }
1448
+
1449
+ function summarizeDocument(document) {
1450
+ return {
1451
+ elementCount: Array.isArray(document.elements) ? document.elements.length : 0,
1452
+ relationshipCount: Array.isArray(document.relationships) ? document.relationships.length : 0,
1453
+ viewCount: Array.isArray(document.views) ? document.views.length : 0,
1454
+ };
1455
+ }
1456
+
1457
+ function writeGraph(graphPath, document) {
1458
+ const tempPath = `${graphPath}.${process.pid}.${Date.now()}.tmp`;
1459
+ fs.writeFileSync(tempPath, `${JSON.stringify(document, null, 2)}\n`, 'utf8');
1460
+ fs.renameSync(tempPath, graphPath);
1461
+ }
1462
+
1463
+ function validateAgainstSchema(value, schemaNode, pointer, errors, rootSchema) {
1464
+ if (!schemaNode || typeof schemaNode !== 'object') {
1465
+ return;
1466
+ }
1467
+
1468
+ const resolvedSchema = schemaNode.$ref ? resolveRef(schemaNode.$ref, rootSchema, errors, pointer) : schemaNode;
1469
+ if (!resolvedSchema) {
1470
+ return;
1471
+ }
1472
+
1473
+ if (resolvedSchema.const !== undefined && !isDeepStrictEqual(value, resolvedSchema.const)) {
1474
+ errors.push(`${pointer} must equal ${JSON.stringify(resolvedSchema.const)}`);
1475
+ return;
1476
+ }
1477
+
1478
+ if (resolvedSchema.enum && !resolvedSchema.enum.some(option => isDeepStrictEqual(option, value))) {
1479
+ errors.push(`${pointer} must be one of: ${resolvedSchema.enum.map(option => JSON.stringify(option)).join(', ')}`);
1480
+ return;
1481
+ }
1482
+
1483
+ if (resolvedSchema.type !== undefined) {
1484
+ validateType(value, resolvedSchema.type, pointer, errors);
1485
+ if (!typeMatches(value, resolvedSchema.type)) {
1486
+ return;
1487
+ }
1488
+ }
1489
+
1490
+ if (typeof resolvedSchema.minLength === 'number' && (typeof value !== 'string' || value.length < resolvedSchema.minLength)) {
1491
+ errors.push(`${pointer} must be at least ${resolvedSchema.minLength} character(s) long`);
1492
+ }
1493
+
1494
+ if (resolvedSchema.pattern) {
1495
+ const matcher = new RegExp(resolvedSchema.pattern);
1496
+ if (typeof value !== 'string' || !matcher.test(value)) {
1497
+ errors.push(`${pointer} must match pattern ${JSON.stringify(resolvedSchema.pattern)}`);
1498
+ }
1499
+ }
1500
+
1501
+ if (typeof resolvedSchema.minItems === 'number' && (!Array.isArray(value) || value.length < resolvedSchema.minItems)) {
1502
+ errors.push(`${pointer} must contain at least ${resolvedSchema.minItems} item(s)`);
1503
+ }
1504
+
1505
+ if (resolvedSchema.type === 'object') {
1506
+ validateObject(value, resolvedSchema, pointer, errors, rootSchema);
1507
+ return;
1508
+ }
1509
+
1510
+ if (resolvedSchema.type === 'array') {
1511
+ validateArray(value, resolvedSchema, pointer, errors, rootSchema);
1512
+ }
1513
+ }
1514
+
1515
+ function validateObject(value, schemaNode, pointer, errors, rootSchema) {
1516
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1517
+ return;
1518
+ }
1519
+
1520
+ const properties = schemaNode.properties || {};
1521
+ const required = Array.isArray(schemaNode.required) ? schemaNode.required : [];
1522
+ for (const key of required) {
1523
+ if (!(key in value)) {
1524
+ errors.push(`${pointer} is missing required property '${key}'`);
1525
+ }
1526
+ }
1527
+
1528
+ if (schemaNode.additionalProperties === false) {
1529
+ for (const key of Object.keys(value)) {
1530
+ if (!(key in properties)) {
1531
+ errors.push(`${pointer} contains unsupported property '${key}'`);
1532
+ }
1533
+ }
1534
+ }
1535
+
1536
+ for (const [key, propertySchema] of Object.entries(properties)) {
1537
+ if (key in value) {
1538
+ validateAgainstSchema(value[key], propertySchema, `${pointer}.${key}`, errors, rootSchema);
1539
+ }
1540
+ }
1541
+ }
1542
+
1543
+ function validateArray(value, schemaNode, pointer, errors, rootSchema) {
1544
+ if (!Array.isArray(value)) {
1545
+ return;
1546
+ }
1547
+
1548
+ if (schemaNode.items) {
1549
+ value.forEach((entry, index) => {
1550
+ validateAgainstSchema(entry, schemaNode.items, `${pointer}[${index}]`, errors, rootSchema);
1551
+ });
1552
+ }
1553
+ }
1554
+
1555
+ function validateType(value, expectedType, pointer, errors) {
1556
+ if (!typeMatches(value, expectedType)) {
1557
+ const printableType = Array.isArray(expectedType) ? expectedType.join(' or ') : expectedType;
1558
+ errors.push(`${pointer} must be of type ${printableType}`);
1559
+ }
1560
+ }
1561
+
1562
+ function typeMatches(value, expectedType) {
1563
+ if (Array.isArray(expectedType)) {
1564
+ return expectedType.some(candidate => typeMatches(value, candidate));
1565
+ }
1566
+ switch (expectedType) {
1567
+ case 'object':
1568
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
1569
+ case 'array':
1570
+ return Array.isArray(value);
1571
+ case 'string':
1572
+ return typeof value === 'string';
1573
+ case 'number':
1574
+ return typeof value === 'number' && Number.isFinite(value);
1575
+ case 'integer':
1576
+ return typeof value === 'number' && Number.isInteger(value);
1577
+ case 'boolean':
1578
+ return typeof value === 'boolean';
1579
+ case 'null':
1580
+ return value === null;
1581
+ default:
1582
+ return true;
1583
+ }
1584
+ }
1585
+
1586
+ function resolveRef(ref, rootSchema, errors, pointer) {
1587
+ if (!ref.startsWith('#/')) {
1588
+ errors.push(`${pointer} uses unsupported $ref '${ref}'`);
1589
+ return undefined;
1590
+ }
1591
+
1592
+ const segments = ref.slice(2).split('/');
1593
+ let current = rootSchema;
1594
+ for (const segment of segments) {
1595
+ if (!current || typeof current !== 'object' || !(segment in current)) {
1596
+ errors.push(`${pointer} references missing schema path '${ref}'`);
1597
+ return undefined;
1598
+ }
1599
+ current = current[segment];
1600
+ }
1601
+ return current;
1602
+ }
1603
+
1604
+ function isDeepStrictEqual(left, right) {
1605
+ return JSON.stringify(left) === JSON.stringify(right);
1606
+ }
1607
+
1608
+ function clone(value) {
1609
+ return JSON.parse(JSON.stringify(value));
1610
+ }
1611
+
1612
+ function validateExplicitQuery(query) {
1613
+ if (!query || typeof query !== 'object' || Array.isArray(query) || !query.purpose) {
1614
+ return queryError('QUERY_PURPOSE_REQUIRED', 'Explicit queries require a purpose');
1615
+ }
1616
+ if (!LEGAL_QUERY_PURPOSES.has(query.purpose)) {
1617
+ return queryError('QUERY_PURPOSE_INVALID', `Unsupported query purpose: ${query.purpose}`);
1618
+ }
1619
+ if (typeof query.intent !== 'string' || query.intent.trim().length === 0) {
1620
+ return queryError('QUERY_INTENT_REQUIRED', 'Explicit queries require a non-blank intent');
1621
+ }
1622
+ if (
1623
+ query.purpose === 'audit'
1624
+ && (typeof query.subject !== 'string' || query.subject.trim().length === 0)
1625
+ ) {
1626
+ return queryError('AUDIT_SUBJECT_REQUIRED', 'Audit queries require a non-blank subject');
1627
+ }
1628
+
1629
+ return {
1630
+ status: 'passed',
1631
+ query: {
1632
+ ...query,
1633
+ intent: query.intent.trim(),
1634
+ ...(query.subject === undefined ? {} : { subject: query.subject.trim() }),
1635
+ },
1636
+ };
1637
+ }
1638
+
1639
+ function validateSemanticQueryResponseShapeControls(query) {
1640
+ for (const field of FORBIDDEN_RESPONSE_SHAPE_CONTROL_FIELDS) {
1641
+ if (!Object.prototype.hasOwnProperty.call(query, field)) {
1642
+ continue;
1643
+ }
1644
+ if (FORBIDDEN_RESPONSE_SHAPE_CONTROL_VALUES.has(String(query[field]).trim().toLowerCase())) {
1645
+ return queryError(
1646
+ 'QUERY_RESPONSE_SHAPE_CONTROL_FORBIDDEN',
1647
+ `Semantic query response-shape control '${field}' is forbidden`,
1648
+ );
1649
+ }
1650
+ }
1651
+ for (const flag of FORBIDDEN_RESPONSE_SHAPE_CONTROL_FLAGS) {
1652
+ if (
1653
+ Object.prototype.hasOwnProperty.call(query, flag)
1654
+ && query[flag] !== false
1655
+ && query[flag] !== null
1656
+ && query[flag] !== undefined
1657
+ ) {
1658
+ return queryError(
1659
+ 'QUERY_RESPONSE_SHAPE_CONTROL_FORBIDDEN',
1660
+ `Semantic query response-shape control '${flag}' is forbidden`,
1661
+ );
1662
+ }
1663
+ }
1664
+ return { status: 'passed' };
1665
+ }
1666
+
1667
+ function isPurposeClosureProbe(query) {
1668
+ return Array.isArray(query && query.anchors) && query.anchors.length > 0;
1669
+ }
1670
+
1671
+ function isOrdinarySemanticQuery(query) {
1672
+ return !!query
1673
+ && typeof query === 'object'
1674
+ && !Array.isArray(query)
1675
+ && query.purpose !== 'graph-tidy';
1676
+ }
1677
+
1678
+ function isCanonicalSubsetSemanticContract(query, options = {}) {
1679
+ if (!isOrdinarySemanticQuery(query)) {
1680
+ return false;
1681
+ }
1682
+ if (!Array.isArray(query.anchors) || query.anchors.length === 0) {
1683
+ return !!(
1684
+ options.defaultNoAnchorSubset
1685
+ || options.architecturePath
1686
+ );
1687
+ }
1688
+ return query.anchors.some(anchor => !/^grag-[a-z0-9-]+$/.test(String(anchor)));
1689
+ }
1690
+
1691
+ function semanticContractOptions(args = {}, dependencies = undefined) {
1692
+ return {
1693
+ architecturePath: args.architecturePath,
1694
+ canonicalDocument: args.canonicalDocument,
1695
+ defaultNoAnchorSubset: !dependencies
1696
+ || !!dependencies.canonicalSubsetForNoAnchor
1697
+ || !!(
1698
+ dependencies.semanticOperatorJourney
1699
+ && dependencies.semanticOperatorJourney.canonicalSubsetForNoAnchor
1700
+ ),
1701
+ };
1702
+ }
1703
+
1704
+ function queryError(category, message, extras = {}) {
1705
+ return {
1706
+ status: 'failed',
1707
+ error: { category, message, ...extras },
1708
+ };
1709
+ }
1710
+
1711
+ function toolResult(payload, structuredContent = undefined) {
1712
+ return {
1713
+ ...payload,
1714
+ content: [
1715
+ {
1716
+ type: 'text',
1717
+ text: JSON.stringify(payload, null, 2),
1718
+ },
1719
+ ],
1720
+ ...(structuredContent === undefined ? {} : { structuredContent }),
1721
+ isError: payload.status === 'failed',
1722
+ };
1723
+ }
1724
+
1725
+ function mutationToolResult(payload, write) {
1726
+ if (write !== true || isMutationResponseDebugEnabled()) {
1727
+ return toolResult(payload);
1728
+ }
1729
+ return toolResult(compactMutationResponse(payload));
1730
+ }
1731
+
1732
+ function isMutationResponseDebugEnabled() {
1733
+ return process.env.ARGO_MCP_MUTATION_RESPONSE_DEBUG === '1';
1734
+ }
1735
+
1736
+ function compactMutationResponse(payload) {
1737
+ const compact = {
1738
+ status: payload && payload.status,
1739
+ written: Boolean(payload && payload.written),
1740
+ };
1741
+ if (payload && payload.embeddingLifecycle && payload.embeddingLifecycle.state) {
1742
+ compact.embeddingLifecycle = { state: payload.embeddingLifecycle.state };
1743
+ }
1744
+ // Failed actual writes must retain business diagnostics (e.g. View15 maximum/observed)
1745
+ // so callers can distinguish reject reasons; successful writes stay compact.
1746
+ if (payload && payload.status === 'failed') {
1747
+ if (Array.isArray(payload.errors)) {
1748
+ compact.errors = payload.errors;
1749
+ }
1750
+ if (Array.isArray(payload.guidance)) {
1751
+ compact.guidance = payload.guidance;
1752
+ }
1753
+ }
1754
+ return compact;
1755
+ }
1756
+
1757
+ function getSystemArchitectureResult(payload) {
1758
+ const failed = payload.status === 'failed';
1759
+ return toolResult(payload, {
1760
+ version: '1.0',
1761
+ mode: failed ? 'error' : ((payload.query && payload.query.mode) || 'full-snapshot'),
1762
+ document: failed ? null : (payload.document === undefined ? null : payload.document),
1763
+ query: failed ? null : (payload.query || null),
1764
+ error: failed ? payload.error : null,
1765
+ });
1766
+ }
1767
+
1768
+ async function callTool(name, args = {}, dependencies = undefined) {
1769
+ if (name === 'getSystemArchitecture') {
1770
+ if (Object.prototype.hasOwnProperty.call(args, 'query')) {
1771
+ const validation = validateExplicitQuery(args.query);
1772
+ if (validation.status === 'failed') {
1773
+ return getSystemArchitectureResult(validation);
1774
+ }
1775
+
1776
+ const query = validation.query;
1777
+ if (query.purpose === 'graph-tidy') {
1778
+ const context = await loadContext(args);
1779
+ const payload = {
1780
+ status: 'passed',
1781
+ graphPath: context.graphPath.relativePath,
1782
+ document: context.document,
1783
+ };
1784
+ if (isPurposeClosureProbe(query) && !dependencies) {
1785
+ return attachContextWarnings(payload, context);
1786
+ }
1787
+ payload.query = {
1788
+ ...query,
1789
+ mode: 'full-snapshot',
1790
+ semanticRetrieval: 'bypassed',
1791
+ };
1792
+ return getSystemArchitectureResult(attachContextWarnings(payload, context));
1793
+ }
1794
+ const context = await loadContext(args);
1795
+ const contractOptions = semanticContractOptions({
1796
+ ...args,
1797
+ canonicalDocument: context.document,
1798
+ }, dependencies);
1799
+ if (isCanonicalSubsetSemanticContract(query, contractOptions)) {
1800
+ const responseShapeValidation = validateSemanticQueryResponseShapeControls(query);
1801
+ if (responseShapeValidation.status === 'failed') {
1802
+ return getSystemArchitectureResult(responseShapeValidation);
1803
+ }
1804
+ }
1805
+
1806
+ const journey = await resolveSemanticOperatorJourney(dependencies);
1807
+ return applySemanticResponseProfile(await journey.query(query), query, contractOptions);
1808
+ }
1809
+
1810
+ const context = await loadContext(args);
1811
+ return getSystemArchitectureResult(attachContextWarnings({
1812
+ status: 'passed',
1813
+ graphPath: context.graphPath.relativePath,
1814
+ document: context.document,
1815
+ }, context));
1816
+ }
1817
+
1818
+ if (name === 'getIntentElementContext') {
1819
+ const context = await loadContext(args);
1820
+ return toolResult(attachContextWarnings(buildIntentElementContext(context, args), context));
1821
+ }
1822
+
1823
+ if (name === 'generateArchitectureDiffPlantuml') {
1824
+ return toolResult(architectureDiffPlantuml.generateArchitectureDiffPlantuml({
1825
+ workspaceRoot: resolveWorkspaceRoot(),
1826
+ architecturePath: args.architecturePath,
1827
+ outputDir: args.outputDir,
1828
+ }));
1829
+ }
1830
+
1831
+ if (name === 'previewSystemArchitectureMutation') {
1832
+ const context = await loadContext(args);
1833
+ return toolResult(attachContextWarnings(await buildMutationResult(context, args.mutations, false), context));
1834
+ }
1835
+
1836
+ if (name === 'applySystemArchitectureMutation') {
1837
+ const context = await loadContext(args);
1838
+ return mutationToolResult(attachContextWarnings(await buildMutationResult(context, args.mutations, true), context), true);
1839
+ }
1840
+
1841
+ if (name === 'addArchitectureElement') {
1842
+ const context = await loadContext(args);
1843
+ const write = !args.dryRun;
1844
+ return mutationToolResult(attachContextWarnings(await buildMutationResult(context, [{ type: 'addElement', element: args.element, view_ids: args.view_ids }], write), context), write);
1845
+ }
1846
+
1847
+ if (name === 'updateArchitectureElement') {
1848
+ const context = await loadContext(args);
1849
+ const write = !args.dryRun;
1850
+ return mutationToolResult(attachContextWarnings(await buildMutationResult(context, [{ type: 'updateElement', id: args.id, patch: args.patch }], write), context), write);
1851
+ }
1852
+
1853
+ if (name === 'removeArchitectureElement') {
1854
+ const context = await loadContext(args);
1855
+ const write = !args.dryRun;
1856
+ return mutationToolResult(attachContextWarnings(await buildMutationResult(context, [{ type: 'removeElement', id: args.id, view_ids: args.view_ids }], write), context), write);
1857
+ }
1858
+
1859
+ if (name === 'addArchitectureRelationship') {
1860
+ const context = await loadContext(args);
1861
+ const write = !args.dryRun;
1862
+ return mutationToolResult(attachContextWarnings(await buildMutationResult(context, [{ type: 'addRelationship', relationship: args.relationship, view_ids: args.view_ids }], write), context), write);
1863
+ }
1864
+
1865
+ if (name === 'updateArchitectureRelationship') {
1866
+ const context = await loadContext(args);
1867
+ const write = !args.dryRun;
1868
+ return mutationToolResult(attachContextWarnings(await buildMutationResult(context, [{ type: 'updateRelationship', id: args.id, patch: args.patch }], write), context), write);
1869
+ }
1870
+
1871
+ if (name === 'removeArchitectureRelationship') {
1872
+ const context = await loadContext(args);
1873
+ const write = !args.dryRun;
1874
+ return mutationToolResult(attachContextWarnings(await buildMutationResult(context, [{ type: 'removeRelationship', id: args.id, view_ids: args.view_ids }], write), context), write);
1875
+ }
1876
+
1877
+ if (name === 'addArchitectureView') {
1878
+ const context = await loadContext(args);
1879
+ const write = !args.dryRun;
1880
+ return mutationToolResult(attachContextWarnings(await buildMutationResult(context, [{ type: 'addView', view: args.view }], write), context), write);
1881
+ }
1882
+
1883
+ if (name === 'updateArchitectureView') {
1884
+ const context = await loadContext(args);
1885
+ const write = !args.dryRun;
1886
+ return mutationToolResult(attachContextWarnings(await buildMutationResult(context, [{ type: 'updateView', view_id: args.view_id, patch: args.patch }], write), context), write);
1887
+ }
1888
+
1889
+ if (name === 'removeArchitectureView') {
1890
+ const context = await loadContext(args);
1891
+ const write = !args.dryRun;
1892
+ return mutationToolResult(attachContextWarnings(await buildMutationResult(context, [{ type: 'removeView', view_id: args.view_id }], write), context), write);
1893
+ }
1894
+
1895
+ throw new Error(`Unknown tool: ${name}`);
1896
+ }
1897
+
1898
+ async function resolveSemanticOperatorJourney(dependencies) {
1899
+ return dependencies && dependencies.semanticOperatorJourney
1900
+ ? dependencies.semanticOperatorJourney
1901
+ : createDefaultProductionSemanticOperatorJourney();
1902
+ }
1903
+
1904
+ async function executeSemanticSystemArchitectureQuery(args, dependencies) {
1905
+ const context = await loadContext(args);
1906
+ const query = args.query;
1907
+ const contractOptions = semanticContractOptions(args, dependencies);
1908
+ const canonicalSubsetContract = isCanonicalSubsetSemanticContract(query, contractOptions);
1909
+ if (canonicalSubsetContract) {
1910
+ const responseShapeValidation = validateSemanticQueryResponseShapeControls(query);
1911
+ if (responseShapeValidation.status === 'failed') {
1912
+ return getSystemArchitectureResult(responseShapeValidation);
1913
+ }
1914
+ }
1915
+ const semanticRetrievalBoundary = resolveSemanticRetrievalBoundary(dependencies, {
1916
+ canonicalGraph: context.document,
1917
+ });
1918
+ if (!semanticRetrievalBoundary || typeof semanticRetrievalBoundary.retrieve !== 'function') {
1919
+ return getSystemArchitectureResult(queryError(
1920
+ 'SEMANTIC_RETRIEVAL_UNAVAILABLE',
1921
+ 'Semantic retrieval boundary is unavailable',
1922
+ ));
1923
+ }
1924
+ let document;
1925
+ try {
1926
+ const retrieved = await semanticRetrievalBoundary.retrieve(query);
1927
+ if (canonicalSubsetContract) {
1928
+ const subset = buildCanonicalSemanticDocumentSubset(retrieved, context.document);
1929
+ if (subset.status === 'failed') {
1930
+ return getSystemArchitectureResult(subset);
1931
+ }
1932
+ document = subset.document;
1933
+ } else {
1934
+ document = shouldReturnDebugSemanticResult(query)
1935
+ ? retrieved
1936
+ : buildBusinessSemanticSummary(retrieved, query);
1937
+ }
1938
+ } catch (error) {
1939
+ const semanticErrorEvidence = {};
1940
+ for (const field of [
1941
+ 'action',
1942
+ 'fullSnapshotFallback',
1943
+ 'state',
1944
+ 'canonicalVersion',
1945
+ 'contentVersion',
1946
+ 'indexVersion',
1947
+ 'completedChannels',
1948
+ 'missingChannels',
1949
+ 'mismatchedChannels',
1950
+ ]) {
1951
+ if (error && Object.prototype.hasOwnProperty.call(error, field)) {
1952
+ semanticErrorEvidence[field] = error[field];
1953
+ }
1954
+ }
1955
+ if (
1956
+ error
1957
+ && error.category === 'SEMANTIC_AUTO_ALIGNMENT_FAILED'
1958
+ && typeof semanticErrorEvidence.action !== 'string'
1959
+ ) {
1960
+ semanticErrorEvidence.action = 'Repair semantic lifecycle alignment, then retry the original query.';
1961
+ }
1962
+ return getSystemArchitectureResult(queryError(
1963
+ error && error.category ? error.category : 'SEMANTIC_RETRIEVAL_FAILED',
1964
+ error && error.message ? error.message : 'Semantic retrieval failed',
1965
+ semanticErrorEvidence,
1966
+ ));
1967
+ }
1968
+ const semanticPayload = {
1969
+ status: 'passed',
1970
+ graphPath: context.graphPath.relativePath,
1971
+ query: {
1972
+ ...query,
1973
+ mode: 'semantic-query',
1974
+ semanticRetrieval: 'invoked',
1975
+ ...(canonicalSubsetContract
1976
+ ? {}
1977
+ : { responseProfile: shouldReturnDebugSemanticResult(query) ? 'debug' : 'business-summary' }),
1978
+ },
1979
+ };
1980
+ return getSystemArchitectureResult({
1981
+ ...semanticPayload,
1982
+ ...(canonicalSubsetContract
1983
+ ? { document }
1984
+ : (document && Object.prototype.hasOwnProperty.call(document, 'result')
1985
+ ? { result: document.result }
1986
+ : { result: document })),
1987
+ ...(canonicalSubsetContract || !shouldReturnDebugSemanticResult(query) ? {} : { document }),
1988
+ });
1989
+ }
1990
+
1991
+ function shouldReturnDebugSemanticResult(query) {
1992
+ return ['debug', 'full', 'evidence'].includes(String(
1993
+ query && (query.responseProfile || query.detail || query.outputMode) || '',
1994
+ ).toLowerCase());
1995
+ }
1996
+
1997
+ function buildBusinessSemanticSummary(retrieved, query = {}) {
1998
+ if (retrieved && retrieved.responseProfile === 'business-summary') return retrieved;
1999
+ if (retrieved && retrieved.result && retrieved.result.responseProfile === 'business-summary') return retrieved.result;
2000
+ const source = retrieved && typeof retrieved === 'object' ? retrieved : {};
2001
+ const provenanceObjects = Array.isArray(source.provenance && source.provenance.objects)
2002
+ ? source.provenance.objects
2003
+ : [];
2004
+ const hitReasonByKey = new Map(provenanceObjects.map(item => [
2005
+ `${item.objectType}:${item.objectId}`,
2006
+ {
2007
+ firstInclusionReason: item.firstInclusionReason,
2008
+ supplementaryReasons: Array.isArray(item.supplementaryReasons) ? [...item.supplementaryReasons] : [],
2009
+ },
2010
+ ]));
2011
+ const seedLimit = businessSummaryLimit(query);
2012
+ const semanticSeeds = summarizeSeeds(source.seedsByType, hitReasonByKey, seedLimit);
2013
+ const elements = summarizeElements(source, hitReasonByKey, seedLimit * 2);
2014
+ const relationships = summarizeRelationships(source, hitReasonByKey, seedLimit * 2);
2015
+ const views = summarizeViews(source, hitReasonByKey, seedLimit);
2016
+ const includedObjectIds = Object.freeze([
2017
+ ...elements.map(item => item.id),
2018
+ ...relationships.map(item => item.id),
2019
+ ...views.map(item => item.id),
2020
+ ]);
2021
+ return Object.freeze({
2022
+ responseProfile: 'business-summary',
2023
+ purpose: query.purpose,
2024
+ intent: query.intent,
2025
+ semanticSeeds,
2026
+ businessObjects: Object.freeze({
2027
+ elements: Object.freeze(elements),
2028
+ relationships: Object.freeze(relationships),
2029
+ views: Object.freeze(views),
2030
+ }),
2031
+ hitReasons: Object.freeze(provenanceObjects.map(item => Object.freeze({
2032
+ objectType: item.objectType,
2033
+ objectId: item.objectId,
2034
+ firstInclusionReason: item.firstInclusionReason,
2035
+ supplementaryReasons: Object.freeze(Array.isArray(item.supplementaryReasons) ? item.supplementaryReasons : []),
2036
+ }))),
2037
+ policySummary: Object.freeze({
2038
+ policyId: source.closurePolicy && source.closurePolicy.policyId,
2039
+ purpose: source.closurePolicy && source.closurePolicy.category,
2040
+ boundaryRationale: source.boundary && source.boundary.rationale,
2041
+ }),
2042
+ boundarySummary: Object.freeze({
2043
+ includedObjectIds,
2044
+ includedCount: includedObjectIds.length,
2045
+ excluded: Object.freeze(Array.isArray(source.boundary && source.boundary.excluded) ? source.boundary.excluded : []),
2046
+ }),
2047
+ semanticIndex: Object.freeze({
2048
+ canonicalVersion: source.canonicalVersion,
2049
+ contentVersion: source.contentVersion,
2050
+ indexVersion: source.indexVersion,
2051
+ alignment: source.provenance && source.provenance.alignment && source.provenance.alignment.state,
2052
+ }),
2053
+ omittedByDefault: Object.freeze([
2054
+ 'embedding vectors',
2055
+ 'full provenance version evidence per object',
2056
+ 'queryTemplate',
2057
+ 'parameterContract',
2058
+ 'archimateSemantics',
2059
+ 'full element descriptions',
2060
+ 'full testcase bodies',
2061
+ ]),
2062
+ expandWith: 'Set query.responseProfile to "debug" to return the full semantic evidence payload.',
2063
+ });
2064
+ }
2065
+
2066
+ function applySemanticResponseProfile(response, query, options = {}) {
2067
+ if (!isCanonicalSubsetSemanticContract(query, options)) {
2068
+ if (shouldReturnDebugSemanticResult(query)) return normalizeSemanticToolResponse(response);
2069
+ const payload = parseToolResponsePayload(response);
2070
+ if (!payload) return response;
2071
+ if (payload.status === 'failed') {
2072
+ return normalizeFailedSemanticResponse(payload, response);
2073
+ }
2074
+ const source = payload.result || payload.document;
2075
+ const summary = buildBusinessSemanticSummary(source, query);
2076
+ const { document: _omittedDocument, ...payloadWithoutDocument } = payload;
2077
+ return getSystemArchitectureResult({
2078
+ ...payloadWithoutDocument,
2079
+ query: {
2080
+ ...(payload.query || query),
2081
+ responseProfile: 'business-summary',
2082
+ },
2083
+ result: summary,
2084
+ });
2085
+ }
2086
+ const payload = parseToolResponsePayload(response);
2087
+ if (!payload) return response;
2088
+ if (payload.status === 'failed') {
2089
+ return normalizeFailedSemanticResponse(payload, response);
2090
+ }
2091
+ const source = payload.result || payload.document;
2092
+ const subset = buildCanonicalSemanticDocumentSubset(source, options.canonicalDocument);
2093
+ if (subset.status === 'failed') {
2094
+ return getSystemArchitectureResult(subset);
2095
+ }
2096
+ const { document: _omittedDocument, result: _omittedResult, ...payloadWithoutDocument } = payload;
2097
+ return getSystemArchitectureResult({
2098
+ ...payloadWithoutDocument,
2099
+ query: {
2100
+ ...(payload.query || query),
2101
+ mode: 'semantic-query',
2102
+ semanticRetrieval: 'invoked',
2103
+ },
2104
+ document: subset.document,
2105
+ });
2106
+ }
2107
+
2108
+ function normalizeFailedSemanticResponse(payload, fallbackResponse) {
2109
+ const error = payload && payload.error;
2110
+ if (!error || error.category !== 'SEMANTIC_AUTO_ALIGNMENT_FAILED' || typeof error.action === 'string') {
2111
+ return fallbackResponse;
2112
+ }
2113
+ return getSystemArchitectureResult({
2114
+ ...payload,
2115
+ error: {
2116
+ ...error,
2117
+ action: 'Repair semantic lifecycle alignment, then retry the original query.',
2118
+ },
2119
+ });
2120
+ }
2121
+
2122
+ function buildCanonicalSemanticDocumentSubset(source, canonicalDocument = undefined) {
2123
+ const evidence = source && typeof source === 'object' ? source : {};
2124
+ const endpointClosureRelationships = arrayAt(evidence, ['endpointClosure', 'relationships']);
2125
+ const viewClosureViews = arrayAt(evidence, ['viewClosure', 'views']);
2126
+ const viewMemberRelationships = viewClosureViews.flatMap(view => (
2127
+ Array.isArray(view && view.memberRelationships) ? view.memberRelationships : []
2128
+ ));
2129
+ const evidenceElements = uniqueById([
2130
+ ...arrayAt(evidence, ['closure', 'elements']),
2131
+ ...arrayAt(evidence, ['elements']),
2132
+ ...endpointClosureRelationships.flatMap(relationship => [relationship && relationship.source, relationship && relationship.target]),
2133
+ ...viewClosureViews.flatMap(view => (
2134
+ Array.isArray(view && view.memberElements) ? view.memberElements : []
2135
+ )),
2136
+ ...viewMemberRelationships.flatMap(relationship => [relationship && relationship.source, relationship && relationship.target]),
2137
+ ], 'id');
2138
+ const evidenceRelationships = uniqueById([
2139
+ ...endpointClosureRelationships,
2140
+ ...arrayAt(evidence, ['relationships']),
2141
+ ...viewMemberRelationships,
2142
+ ], 'id');
2143
+ const evidenceViews = uniqueById([
2144
+ ...viewClosureViews,
2145
+ ...arrayAt(evidence, ['views']),
2146
+ ], 'view_id');
2147
+
2148
+ const canonicalElements = Array.isArray(canonicalDocument && canonicalDocument.elements)
2149
+ ? canonicalDocument.elements
2150
+ : [];
2151
+ const canonicalRelationships = Array.isArray(canonicalDocument && canonicalDocument.relationships)
2152
+ ? canonicalDocument.relationships
2153
+ : [];
2154
+ const canonicalViews = Array.isArray(canonicalDocument && canonicalDocument.views)
2155
+ ? canonicalDocument.views
2156
+ : [];
2157
+
2158
+ const canonicalElementById = new Map(canonicalElements.map(element => [element && element.id, element]));
2159
+ const canonicalRelationshipById = new Map(canonicalRelationships.map(relationship => [relationship && relationship.id, relationship]));
2160
+ const canonicalViewById = new Map(canonicalViews.map(view => [view && view.view_id, view]));
2161
+ const evidenceElementCandidates = evidenceElements
2162
+ .map(item => classifyCanonicalSubsetCandidate(item, 'Element'))
2163
+ .filter(candidate => candidate && candidate.kind === 'Element');
2164
+ const evidenceRelationshipCandidates = evidenceRelationships
2165
+ .map(item => classifyCanonicalSubsetCandidate(item, 'ArchitectureRelationship'))
2166
+ .filter(candidate => candidate && candidate.kind === 'ArchitectureRelationship');
2167
+ const evidenceViewCandidates = evidenceViews
2168
+ .map(item => classifyCanonicalSubsetCandidate(item, 'View'))
2169
+ .filter(candidate => candidate && candidate.kind === 'View');
2170
+ const evidenceElementById = new Map(evidenceElementCandidates
2171
+ .map(candidate => [candidate.id, candidate.item]));
2172
+ const evidenceRelationshipById = new Map(evidenceRelationshipCandidates
2173
+ .map(candidate => [candidate.id, candidate.item]));
2174
+ const evidenceViewById = new Map(evidenceViewCandidates
2175
+ .map(candidate => [candidate.id, candidate.item]));
2176
+
2177
+ const elementIds = new Set([...evidenceElementById.keys()].filter(id => canonicalElementById.has(id)));
2178
+ const relationshipIds = new Set([...evidenceRelationshipById.keys()]);
2179
+ const viewIds = new Set([...evidenceViewById.keys()]);
2180
+
2181
+ const selectElement = (elementId, category, message) => {
2182
+ if (!evidenceElementById.has(elementId)) {
2183
+ return semanticSubsetError(category, message);
2184
+ }
2185
+ const element = canonicalElementById.get(elementId);
2186
+ if (!element) {
2187
+ return semanticSubsetError(category, message);
2188
+ }
2189
+ elementIds.add(elementId);
2190
+ return undefined;
2191
+ };
2192
+ const selectRelationship = (relationshipId, category, message) => {
2193
+ if (!evidenceRelationshipById.has(relationshipId)) {
2194
+ return { error: semanticSubsetError(category, message) };
2195
+ }
2196
+ const relationship = canonicalRelationshipById.get(relationshipId);
2197
+ if (!relationship) {
2198
+ return { error: semanticSubsetError(category, message) };
2199
+ }
2200
+ relationshipIds.add(relationshipId);
2201
+ return { relationship };
2202
+ };
2203
+
2204
+ for (const viewId of [...viewIds]) {
2205
+ const view = canonicalViewById.get(viewId);
2206
+ if (!view) {
2207
+ viewIds.delete(viewId);
2208
+ continue;
2209
+ }
2210
+ for (const elementId of view && Array.isArray(view.included_elements) ? view.included_elements : []) {
2211
+ const error = selectElement(
2212
+ elementId,
2213
+ 'SEMANTIC_SUBSET_VIEW_MISSING',
2214
+ `Semantic View subset is missing included Element '${elementId}'`,
2215
+ );
2216
+ if (error) {
2217
+ return error;
2218
+ }
2219
+ }
2220
+ for (const relationshipId of view && Array.isArray(view.included_relationships) ? view.included_relationships : []) {
2221
+ const selected = selectRelationship(
2222
+ relationshipId,
2223
+ 'SEMANTIC_SUBSET_VIEW_MISSING',
2224
+ `Semantic View subset is missing included Relationship '${relationshipId}'`,
2225
+ );
2226
+ if (selected.error) {
2227
+ return selected.error;
2228
+ }
2229
+ const { relationship } = selected;
2230
+ for (const endpointId of [relationship.source_id, relationship.target_id]) {
2231
+ const error = selectElement(
2232
+ endpointId,
2233
+ 'SEMANTIC_SUBSET_VIEW_MISSING',
2234
+ `Semantic View subset is missing endpoint Elements for Relationship '${relationship.id}'`,
2235
+ );
2236
+ if (error) {
2237
+ return error;
2238
+ }
2239
+ }
2240
+ }
2241
+ }
2242
+
2243
+ for (const relationshipId of [...relationshipIds]) {
2244
+ const selected = selectRelationship(
2245
+ relationshipId,
2246
+ 'SEMANTIC_SUBSET_RELATIONSHIP_MISSING',
2247
+ `Semantic Relationship subset is missing canonical Relationship '${relationshipId}'`,
2248
+ );
2249
+ if (selected.error) {
2250
+ return selected.error;
2251
+ }
2252
+ const { relationship } = selected;
2253
+ for (const endpointId of [relationship && relationship.source_id, relationship && relationship.target_id]) {
2254
+ const error = selectElement(
2255
+ endpointId,
2256
+ 'SEMANTIC_SUBSET_RELATIONSHIP_MISSING',
2257
+ `Semantic Relationship subset is missing endpoint Elements for Relationship '${relationship && relationship.id}'`,
2258
+ );
2259
+ if (error) {
2260
+ return error;
2261
+ }
2262
+ }
2263
+ }
2264
+
2265
+ const elements = [...elementIds]
2266
+ .map(id => canonicalElementById.get(id))
2267
+ .filter(Boolean);
2268
+ const relationships = [...relationshipIds]
2269
+ .map(id => canonicalRelationshipById.get(id))
2270
+ .filter(Boolean);
2271
+ const views = [...viewIds]
2272
+ .map(id => canonicalViewById.get(id))
2273
+ .filter(Boolean);
2274
+
2275
+ return {
2276
+ status: 'passed',
2277
+ document: {
2278
+ elements: elements.map(clone),
2279
+ relationships: relationships.map(clone),
2280
+ views: views.map(clone),
2281
+ },
2282
+ };
2283
+ }
2284
+
2285
+ function classifyCanonicalSubsetCandidate(item, fallbackKind) {
2286
+ if (!item || typeof item !== 'object') return undefined;
2287
+ const rawId = item.id || item.view_id || item.objectId || item.canonicalIdentity;
2288
+ if (!rawId) return undefined;
2289
+ const qualified = parseSemanticQualifiedId(rawId);
2290
+ if (qualified) {
2291
+ return { ...qualified, item };
2292
+ }
2293
+ return {
2294
+ kind: normalizeSemanticObjectKind(item.objectType || item.channel || fallbackKind),
2295
+ id: String(rawId),
2296
+ item,
2297
+ };
2298
+ }
2299
+
2300
+ function parseSemanticQualifiedId(rawId) {
2301
+ const text = String(rawId);
2302
+ for (const [prefix, kind] of [
2303
+ ['ArchitectureRelationship:', 'ArchitectureRelationship'],
2304
+ ['Relationship:', 'ArchitectureRelationship'],
2305
+ ['View:', 'View'],
2306
+ ['Element:', 'Element'],
2307
+ ]) {
2308
+ if (text.startsWith(prefix)) {
2309
+ return {
2310
+ kind,
2311
+ id: text.slice(prefix.length),
2312
+ };
2313
+ }
2314
+ }
2315
+ return undefined;
2316
+ }
2317
+
2318
+ function normalizeSemanticObjectKind(value) {
2319
+ const kind = String(value || '').toLowerCase();
2320
+ if (kind === 'architecturerelationship' || kind === 'relationship' || kind === 'relationships') {
2321
+ return 'ArchitectureRelationship';
2322
+ }
2323
+ if (kind === 'view' || kind === 'views') {
2324
+ return 'View';
2325
+ }
2326
+ return 'Element';
2327
+ }
2328
+
2329
+ function arrayAt(value, pathSegments) {
2330
+ let current = value;
2331
+ for (const segment of pathSegments) {
2332
+ current = current && current[segment];
2333
+ }
2334
+ return Array.isArray(current) ? current : [];
2335
+ }
2336
+
2337
+ function semanticSubsetError(category, message) {
2338
+ return queryError(category, message, { fullSnapshotFallback: false });
2339
+ }
2340
+
2341
+ function parseToolResponsePayload(response) {
2342
+ if (!response || typeof response !== 'object') return undefined;
2343
+ if (response.content && Array.isArray(response.content) && response.content[0] && typeof response.content[0].text === 'string') {
2344
+ try {
2345
+ return JSON.parse(response.content[0].text);
2346
+ } catch (_error) {
2347
+ return undefined;
2348
+ }
2349
+ }
2350
+ return response;
2351
+ }
2352
+
2353
+ function normalizeSemanticToolResponse(response) {
2354
+ if (response && response.content && Array.isArray(response.content)) return response;
2355
+ const payload = response && typeof response === 'object'
2356
+ ? response
2357
+ : { status: 'failed', error: { category: 'SEMANTIC_RETRIEVAL_FAILED', message: 'Semantic retrieval failed' } };
2358
+ return getSystemArchitectureResult(payload);
2359
+ }
2360
+
2361
+ function businessSummaryLimit(query) {
2362
+ const supplied = Number(query && (query.topN || query.limit || query.maxResults));
2363
+ return Number.isInteger(supplied) && supplied > 0 ? Math.min(supplied, 50) : 8;
2364
+ }
2365
+
2366
+ function summarizeSeeds(seedsByType = {}, hitReasonByKey, limit) {
2367
+ return Object.freeze(Object.fromEntries(Object.entries(seedsByType).map(([type, seeds]) => [
2368
+ type,
2369
+ Object.freeze((Array.isArray(seeds) ? seeds : [])
2370
+ .slice()
2371
+ .sort((left, right) => Number(right.score || 0) - Number(left.score || 0))
2372
+ .slice(0, limit)
2373
+ .map(seed => {
2374
+ const objectType = seed.objectType || seed.channel || inferObjectTypeFromSeedType(type);
2375
+ const objectId = seed.id || seed.objectId || seed.canonicalIdentity;
2376
+ const reasons = hitReasonByKey.get(`${objectType}:${objectId}`) || {};
2377
+ return Object.freeze({
2378
+ objectId,
2379
+ objectType,
2380
+ score: typeof seed.score === 'number' ? seed.score : undefined,
2381
+ hitReason: reasons.firstInclusionReason || 'semantic-seed',
2382
+ supplementaryReasons: Object.freeze(reasons.supplementaryReasons || []),
2383
+ });
2384
+ })),
2385
+ ])));
2386
+ }
2387
+
2388
+ function inferObjectTypeFromSeedType(type) {
2389
+ if (type === 'relationships') return 'ArchitectureRelationship';
2390
+ if (type === 'views') return 'View';
2391
+ return 'Element';
2392
+ }
2393
+
2394
+ function summarizeElements(source, hitReasonByKey, limit) {
2395
+ return uniqueById([
2396
+ ...(((source.closure && source.closure.elements) || [])),
2397
+ ...((((source.viewClosure && source.viewClosure.views) || []).flatMap(view => view.memberElements || []))),
2398
+ ...((((source.endpointClosure && source.endpointClosure.relationships) || []).flatMap(relationship => [relationship.source, relationship.target]).filter(Boolean))),
2399
+ ], 'id').slice(0, limit).map(element => summarizeElement(element, hitReasonByKey));
2400
+ }
2401
+
2402
+ function summarizeRelationships(source, hitReasonByKey, limit) {
2403
+ return uniqueById([
2404
+ ...(((source.endpointClosure && source.endpointClosure.relationships) || [])),
2405
+ ...((((source.viewClosure && source.viewClosure.views) || []).flatMap(view => view.memberRelationships || []))),
2406
+ ], 'id').slice(0, limit).map(relationship => summarizeRelationship(relationship, hitReasonByKey));
2407
+ }
2408
+
2409
+ function summarizeViews(source, hitReasonByKey, limit) {
2410
+ return uniqueById(((source.viewClosure && source.viewClosure.views) || []), 'view_id')
2411
+ .slice(0, limit)
2412
+ .map(view => summarizeView(view, hitReasonByKey));
2413
+ }
2414
+
2415
+ function summarizeElement(element, hitReasonByKey) {
2416
+ const attributes = attributesMap(element);
2417
+ const reasons = hitReasonByKey.get(`Element:${element.id}`) || {};
2418
+ return Object.freeze({
2419
+ id: element.id,
2420
+ name: element.name,
2421
+ type: element.type,
2422
+ descriptionSummary: summarizeText(element.description),
2423
+ status: attributes.deliveryStatus || attributes.status,
2424
+ functionalPoints: Object.freeze(Object.entries(attributes)
2425
+ .filter(([name]) => name.startsWith('functionalPoint'))
2426
+ .map(([, value]) => value)),
2427
+ testCoverage: summarizeTestcases(element.testcases),
2428
+ hitReason: reasons.firstInclusionReason,
2429
+ supplementaryReasons: Object.freeze(reasons.supplementaryReasons || []),
2430
+ });
2431
+ }
2432
+
2433
+ function summarizeRelationship(relationship, hitReasonByKey) {
2434
+ const reasons = hitReasonByKey.get(`ArchitectureRelationship:${relationship.id}`) || {};
2435
+ return Object.freeze({
2436
+ id: relationship.id,
2437
+ name: relationship.name,
2438
+ type: relationship.type,
2439
+ source_id: relationship.source_id,
2440
+ target_id: relationship.target_id,
2441
+ hitReason: reasons.firstInclusionReason,
2442
+ supplementaryReasons: Object.freeze(reasons.supplementaryReasons || []),
2443
+ });
2444
+ }
2445
+
2446
+ function summarizeView(view, hitReasonByKey) {
2447
+ const reasons = hitReasonByKey.get(`View:${view.view_id}`) || {};
2448
+ return Object.freeze({
2449
+ view_id: view.view_id,
2450
+ view_name: view.view_name || view.name,
2451
+ descriptionSummary: summarizeText(view.description),
2452
+ elementCount: Array.isArray(view.included_elements) ? view.included_elements.length : undefined,
2453
+ relationshipCount: Array.isArray(view.included_relationships) ? view.included_relationships.length : undefined,
2454
+ hitReason: reasons.firstInclusionReason,
2455
+ supplementaryReasons: Object.freeze(reasons.supplementaryReasons || []),
2456
+ });
2457
+ }
2458
+
2459
+ function attributesMap(value = {}) {
2460
+ const result = {};
2461
+ for (const attribute of Array.isArray(value.attributes) ? value.attributes : []) {
2462
+ if (attribute && typeof attribute.name === 'string') result[attribute.name] = attribute.value;
2463
+ }
2464
+ return result;
2465
+ }
2466
+
2467
+ function summarizeTestcases(testcases) {
2468
+ return Object.freeze((Array.isArray(testcases) ? testcases : []).map(testcase => {
2469
+ if (typeof testcase === 'string') return { name: testcase };
2470
+ return {
2471
+ name: testcase.name || testcase.id || testcase.testcasename,
2472
+ status: testcase.status,
2473
+ coverage: testcase.coverage || testcase.coveragePoint || testcase.description,
2474
+ };
2475
+ }));
2476
+ }
2477
+
2478
+ function summarizeText(text) {
2479
+ if (typeof text !== 'string') return undefined;
2480
+ const compact = text.replace(/\s+/g, ' ').trim();
2481
+ return compact.length <= 180 ? compact : `${compact.slice(0, 177)}...`;
2482
+ }
2483
+
2484
+ function uniqueById(items, idField) {
2485
+ const seen = new Set();
2486
+ const result = [];
2487
+ for (const item of items) {
2488
+ const id = item && item[idField];
2489
+ if (!id || seen.has(id)) continue;
2490
+ seen.add(id);
2491
+ result.push(item);
2492
+ }
2493
+ return result;
2494
+ }
2495
+
2496
+ async function createDefaultProductionSemanticOperatorJourney(options = {}) {
2497
+ const workspaceRoot = options.repositoryRoot || resolveWorkspaceRoot();
2498
+ const graphPath = resolveWorkspacePath(workspaceRoot, DEFAULT_GRAPH_PATH);
2499
+ const canonicalGraph = readJson(graphPath.absolutePath, graphPath.relativePath);
2500
+ const readinessStore = createProductionSemanticReadinessStore({
2501
+ repositoryRoot: workspaceRoot,
2502
+ });
2503
+ const retrieval = createDefaultSemanticRetrieval({
2504
+ canonicalGraph,
2505
+ repositoryRoot: workspaceRoot,
2506
+ readinessBoundary: readinessStore,
2507
+ });
2508
+ const runtime = createProductionGraphRagRuntime({
2509
+ canonicalGraph,
2510
+ neo4jRetrievalBoundary: retrieval,
2511
+ });
2512
+ const journey = createProductionSemanticOperatorJourney({
2513
+ initializeWorkspace: request => initializeWorkspace(request),
2514
+ syncCanonicalStructuralProjection: request => syncCanonicalStructuralProjection(request),
2515
+ resolveApprovedConfiguration: request => resolveApprovedLiveConfiguration(request),
2516
+ runSemanticBackfill: request => runtime.runSemanticBackfill(request),
2517
+ readSemanticReadiness: () => retrieval.readReadiness(),
2518
+ querySystemArchitecture: request => executeSemanticSystemArchitectureQuery(request, {
2519
+ semanticRetrievalBoundary: retrieval,
2520
+ canonicalSubsetForNoAnchor: true,
2521
+ }),
2522
+ });
2523
+ return Object.freeze({
2524
+ ...journey,
2525
+ canonicalSubsetForNoAnchor: true,
2526
+ });
2527
+ }
2528
+
2529
+ function createDefaultCanonicalSemanticInitComposition() {
2530
+ const repositoryRoot = resolveWorkspaceRoot();
2531
+ const readinessStore = createProductionSemanticReadinessStore({ repositoryRoot });
2532
+ const graphPath = resolveWorkspacePath(repositoryRoot, DEFAULT_GRAPH_PATH);
2533
+ const canonicalGraph = readJson(graphPath.absolutePath, graphPath.relativePath);
2534
+ let configurationEvidence;
2535
+ return Object.freeze({
2536
+ configurationBehavior: Object.freeze({
2537
+ readGate(name) {
2538
+ return process.env[name];
2539
+ },
2540
+ async resolve() {
2541
+ configurationEvidence = await resolveApprovedLiveConfiguration({
2542
+ repositoryRoot,
2543
+ requiredOptIns: [LIVE_PROVIDER_OPT_IN, W31_LIVE_OPT_IN],
2544
+ });
2545
+ return configurationEvidence;
2546
+ },
2547
+ }),
2548
+ productionGraphRagRuntime: Object.freeze({
2549
+ async runSemanticBackfill(request) {
2550
+ const runtime = await createDefaultProductionSemanticRuntime();
2551
+ try {
2552
+ return await runtime.runSemanticBackfill(request);
2553
+ } finally {
2554
+ if (runtime && typeof runtime.close === 'function') {
2555
+ await runtime.close();
2556
+ }
2557
+ }
2558
+ },
2559
+ }),
2560
+ finalReadiness: Object.freeze({
2561
+ async invalidate(evidence) {
2562
+ return readinessStore.invalidate(evidence);
2563
+ },
2564
+ async recordFailure(evidence) {
2565
+ return readinessStore.recordFailure(evidence);
2566
+ },
2567
+ async verifyQueryability(backfill) {
2568
+ if (!backfill || backfill.alignmentState !== 'Aligned') return false;
2569
+ const contentVersion = backfill.contentVersion || backfill.canonicalVersion;
2570
+ const indexVersion = backfill.indexVersion || backfill.canonicalVersion;
2571
+ const retrieval = createDefaultSemanticRetrieval({
2572
+ canonicalGraph,
2573
+ repositoryRoot,
2574
+ });
2575
+ await retrieval.probeQueryability(Object.freeze({
2576
+ purpose: 'implementation-design',
2577
+ intent: 'verify system architecture semantic queryability',
2578
+ }), Object.freeze({
2579
+ state: 'QueryabilityProbe',
2580
+ canonicalVersion: backfill.canonicalVersion,
2581
+ contentVersion,
2582
+ indexVersion,
2583
+ }));
2584
+ return true;
2585
+ },
2586
+ async verifyGlobalCoherence(backfill) {
2587
+ return Boolean(
2588
+ backfill
2589
+ && backfill.alignmentState === 'Aligned'
2590
+ && ['Element', 'ArchitectureRelationship', 'View'].every(channel => (
2591
+ backfill.channels
2592
+ && backfill.channels[channel]
2593
+ && backfill.channels[channel].status === 'complete'
2594
+ && backfill.channels[channel].canonicalVersion === backfill.canonicalVersion
2595
+ )),
2596
+ );
2597
+ },
2598
+ async recordAligned(evidence) {
2599
+ const profile = configurationEvidence && configurationEvidence.configuration;
2600
+ return readinessStore.recordAligned(Object.freeze({
2601
+ ...evidence,
2602
+ channels: Object.freeze((evidence.channels || []).map(channel => Object.freeze({
2603
+ ...channel,
2604
+ state: 'Aligned',
2605
+ provider: profile.embeddingProvider,
2606
+ model: profile.embeddingModel,
2607
+ modelVersion: profile.embeddingModelVersion,
2608
+ dimensions: profile.embeddingDimensions,
2609
+ queryable: true,
2610
+ coherent: true,
2611
+ }))),
2612
+ }));
2613
+ },
2614
+ }),
2615
+ });
2616
+ }
2617
+
2618
+ async function createDefaultProductionSemanticRuntime() {
2619
+ const workspaceRoot = resolveWorkspaceRoot();
2620
+ const configuration = await resolveDefaultSemanticConfiguration();
2621
+ const neo4j = require('neo4j-driver');
2622
+ const driver = neo4j.driver(
2623
+ configuration.neo4jDatabaseUrl,
2624
+ neo4j.auth.basic(
2625
+ configuration.neo4jDatabaseUsername,
2626
+ configuration.neo4jDatabasePassword,
2627
+ ),
2628
+ );
2629
+ const graphPath = resolveWorkspacePath(workspaceRoot, DEFAULT_GRAPH_PATH);
2630
+ const canonicalDocument = readJson(graphPath.absolutePath, graphPath.relativePath);
2631
+ const canonicalVersion = deriveSemanticCanonicalVersion(canonicalDocument);
2632
+ const canonicalSnapshot = Object.freeze({
2633
+ ...canonicalDocument,
2634
+ version: canonicalVersion,
2635
+ });
2636
+ const qualification = Object.freeze({
2637
+ approvedByHuman: true,
2638
+ provider: configuration.embeddingProvider,
2639
+ model: configuration.embeddingModel,
2640
+ version: configuration.embeddingModelVersion,
2641
+ dimensions: configuration.embeddingDimensions,
2642
+ source: 'explicit-human-approval',
2643
+ });
2644
+ const providerClient = createLiveEmbeddingProviderClient({
2645
+ configuration,
2646
+ transport: Object.freeze({
2647
+ request(url, options) {
2648
+ if (typeof global.fetch !== 'function') {
2649
+ const error = new Error('LIVE_PROVIDER_TRANSPORT_UNAVAILABLE');
2650
+ error.category = 'LIVE_PROVIDER_TRANSPORT_UNAVAILABLE';
2651
+ throw error;
2652
+ }
2653
+ return global.fetch(url, options);
2654
+ },
2655
+ }),
2656
+ });
2657
+
2658
+ const runtime = createProductionGraphRagRuntime({
2659
+ canonicalGraph: canonicalSnapshot,
2660
+ neo4jRetrievalBoundary: Object.freeze({
2661
+ async retrieve() {
2662
+ const error = new Error('SEMANTIC_RETRIEVAL_REQUEST_REQUIRED');
2663
+ error.category = 'SEMANTIC_RETRIEVAL_REQUEST_REQUIRED';
2664
+ throw error;
2665
+ },
2666
+ }),
2667
+ embeddingQualification: qualification,
2668
+ semanticPersistence: Object.freeze({
2669
+ canonicalSource: Object.freeze({
2670
+ async readSnapshot() {
2671
+ return canonicalSnapshot;
2672
+ },
2673
+ }),
2674
+ structuralProjection: Object.freeze({
2675
+ async requireComplete() {
2676
+ await verifyArchitectureSync({
2677
+ architecturePath: graphPath.relativePath,
2678
+ document: canonicalDocument,
2679
+ driver,
2680
+ database: configuration.neo4jDatabase,
2681
+ });
2682
+ return Object.freeze({
2683
+ status: 'complete',
2684
+ canonicalVersion,
2685
+ });
2686
+ },
2687
+ }),
2688
+ embeddingProvider: Object.freeze({
2689
+ async embedBatch(batch) {
2690
+ const vectors = [];
2691
+ const failures = [];
2692
+ for (const record of batch) {
2693
+ try {
2694
+ vectors.push(Object.freeze({
2695
+ canonicalIdentity: record.canonicalIdentity,
2696
+ vector: Object.freeze(await providerClient.embed(JSON.stringify(record.canonicalObject))),
2697
+ }));
2698
+ } catch (error) {
2699
+ failures.push(Object.freeze({
2700
+ canonicalIdentity: record.canonicalIdentity,
2701
+ category: error && error.category ? error.category : 'LIVE_PROVIDER_REQUEST_FAILED',
2702
+ }));
2703
+ }
2704
+ }
2705
+ return Object.freeze({
2706
+ vectors: Object.freeze(vectors),
2707
+ failures: Object.freeze(failures),
2708
+ });
2709
+ },
2710
+ }),
2711
+ neo4jDriver: driver,
2712
+ canonicalAuthority: Object.freeze({
2713
+ assertProjectionOnly() {
2714
+ return Object.freeze({
2715
+ authority: 'canonical-json',
2716
+ projectionRole: 'subordinate-projection-index',
2717
+ });
2718
+ },
2719
+ }),
2720
+ configuration,
2721
+ qualification,
2722
+ batchSize: 100,
2723
+ }),
2724
+ });
2725
+ return Object.freeze({
2726
+ ...runtime,
2727
+ async close() {
2728
+ await driver.close();
2729
+ },
2730
+ });
2731
+ }
2732
+
2733
+ async function resolveDefaultSemanticConfiguration() {
2734
+ let external;
2735
+ try {
2736
+ external = resolveExternalProductionConfig({
2737
+ neo4jUri: process.env.ARGO_NEO4J_DATABASE_URL,
2738
+ neo4jUsername: process.env.ARGO_NEO4J_DATABASE_USERNAME,
2739
+ neo4jPassword: process.env.ARGO_NEO4J_DATABASE_PASSWORD,
2740
+ embeddingCredential: process.env.QWEN_KEY,
2741
+ neo4jDatabase: process.env.ARGO_NEO4J_DATABASE || getDefaultSemanticNeo4jDatabaseName(),
2742
+ }, {
2743
+ operation: 'semantic-backfill',
2744
+ sourceKeys: new Map([
2745
+ ['neo4jUri', 'ARGO_NEO4J_DATABASE_URL'],
2746
+ ['neo4jUsername', 'ARGO_NEO4J_DATABASE_USERNAME'],
2747
+ ['neo4jPassword', 'ARGO_NEO4J_DATABASE_PASSWORD'],
2748
+ ['embeddingCredential', 'QWEN_KEY'],
2749
+ ]),
2750
+ });
2751
+ } catch (error) {
2752
+ if (error && error.category === 'EXTERNAL_CREDENTIALS_REQUIRED') {
2753
+ const missing = new Error('EXTERNAL_CREDENTIALS_REQUIRED');
2754
+ missing.category = 'EXTERNAL_CREDENTIALS_REQUIRED';
2755
+ missing.field = error.field;
2756
+ throw missing;
2757
+ }
2758
+ throw error;
2759
+ }
2760
+ return Object.freeze({
2761
+ embeddingBaseUrl: W31_APPROVED_PROFILE.baseUrl,
2762
+ embeddingModel: W31_APPROVED_PROFILE.model,
2763
+ embeddingProvider: W31_APPROVED_PROFILE.provider,
2764
+ embeddingModelVersion: W31_APPROVED_PROFILE.version,
2765
+ embeddingDimensions: W31_APPROVED_PROFILE.dimensions,
2766
+ neo4jDatabaseUrl: external.neo4jUri,
2767
+ neo4jDatabaseUsername: external.neo4jUsername,
2768
+ neo4jDatabasePassword: external.neo4jPassword,
2769
+ qwenKey: external.embeddingCredential,
2770
+ embeddingCredential: external.embeddingCredential,
2771
+ ...(external.neo4jDatabase === undefined ? {} : { neo4jDatabase: external.neo4jDatabase }),
2772
+ });
2773
+ }
2774
+
2775
+ function getDefaultSemanticNeo4jDatabaseName() {
2776
+ const repoName = path.basename(resolveWorkspaceRoot());
2777
+ const normalized = String(repoName)
2778
+ .toLowerCase()
2779
+ .replace(/[^a-z0-9.-]+/g, '-')
2780
+ .replace(/^-+|-+$/g, '')
2781
+ .replace(/\.{2,}/g, '.')
2782
+ .replace(/-{2,}/g, '-');
2783
+ const safe = normalized || 'workspace';
2784
+ const prefixed = /^[a-z]/.test(safe) ? safe : `db-${safe}`;
2785
+ return prefixed.slice(0, 63);
2786
+ }
2787
+
2788
+ function deriveSemanticCanonicalVersion(document) {
2789
+ return `canonical:${crypto.createHash('sha256').update(JSON.stringify({
2790
+ name: document.name || 'System',
2791
+ elements: (document.elements || []).map(element => element.id).sort(),
2792
+ relationships: (document.relationships || []).map(relationship => relationship.id).sort(),
2793
+ views: (document.views || []).map(view => view.view_id).sort(),
2794
+ })).digest('hex')}`;
2795
+ }
2796
+
2797
+ function resolveSemanticRetrievalBoundary(dependencies, context = {}) {
2798
+ if (!dependencies) {
2799
+ return createDefaultSemanticRetrievalBoundary(context);
2800
+ }
2801
+ if (
2802
+ dependencies.semanticRetrievalBoundary
2803
+ && typeof dependencies.semanticRetrievalBoundary.retrieve === 'function'
2804
+ ) {
2805
+ return dependencies.semanticRetrievalBoundary;
2806
+ }
2807
+
2808
+ const runtime = dependencies.productionGraphRagRuntime
2809
+ || (dependencies.productionGraphRagDependencies
2810
+ ? createProductionGraphRagRuntime(dependencies.productionGraphRagDependencies)
2811
+ : undefined);
2812
+ if (!runtime || typeof runtime.querySemantic !== 'function') {
2813
+ return undefined;
2814
+ }
2815
+ return {
2816
+ retrieve(request) {
2817
+ return runtime.querySemantic(request);
2818
+ },
2819
+ };
2820
+ }
2821
+
2822
+ function createDefaultSemanticRetrievalBoundary(context = {}) {
2823
+ return createDefaultSemanticRetrieval({
2824
+ canonicalGraph: context.canonicalGraph,
2825
+ });
2826
+ }
2827
+
2828
+ function attachContextWarnings(payload, context) {
2829
+ const recovery = context && context.neo4jSyncRecovery;
2830
+ if (!recovery || !recovery.attempted) {
2831
+ return payload;
2832
+ }
2833
+
2834
+ payload.neo4jRecovery = recovery;
2835
+ if (recovery.status === 'failed') {
2836
+ payload.warnings = addUnique(payload.warnings || [], [
2837
+ `Neo4j automatic resync failed before servicing ${context.graphPath.relativePath}: ${recovery.error}`,
2838
+ 'The canonical JSON graph is still being served and written. Neo4j will be retried again on the next canonical read or write, or you can run node .argo/scripts/syncSystemArchitectureToNeo4j.js manually.',
2839
+ ]);
2840
+ }
2841
+ return payload;
2842
+ }
2843
+
2844
+ function send(message) {
2845
+ process.stdout.write(`${JSON.stringify(message)}\n`);
2846
+ }
2847
+
2848
+ async function handleRequest(request, dependencies = undefined) {
2849
+ const { id, method, params } = request;
2850
+
2851
+ if (method === 'initialize') {
2852
+ return {
2853
+ jsonrpc: '2.0',
2854
+ id,
2855
+ result: {
2856
+ protocolVersion: '2024-11-05',
2857
+ capabilities: { tools: {} },
2858
+ serverInfo: {
2859
+ name: 'argo',
2860
+ version: '1.0.0',
2861
+ },
2862
+ },
2863
+ };
2864
+ }
2865
+
2866
+ if (method === 'notifications/initialized') {
2867
+ return null;
2868
+ }
2869
+
2870
+ if (method === 'tools/list') {
2871
+ return {
2872
+ jsonrpc: '2.0',
2873
+ id,
2874
+ result: { tools: TOOLS },
2875
+ };
2876
+ }
2877
+
2878
+ if (method === 'tools/call') {
2879
+ try {
2880
+ let activeDependencies = dependencies;
2881
+ if (
2882
+ !activeDependencies
2883
+ && params.name === 'getSystemArchitecture'
2884
+ && params.arguments
2885
+ && Object.prototype.hasOwnProperty.call(params.arguments, 'query')
2886
+ && params.arguments.query
2887
+ && params.arguments.query.purpose !== 'graph-tidy'
2888
+ ) {
2889
+ activeDependencies = {
2890
+ semanticOperatorJourney: await createDefaultProductionSemanticOperatorJourney(),
2891
+ canonicalSubsetForNoAnchor: true,
2892
+ };
2893
+ }
2894
+ const result = await callTool(
2895
+ params.name,
2896
+ params.arguments || {},
2897
+ activeDependencies,
2898
+ );
2899
+ return { jsonrpc: '2.0', id, result };
2900
+ } catch (error) {
2901
+ return {
2902
+ jsonrpc: '2.0',
2903
+ id,
2904
+ result: semanticOperatorErrorResult(error),
2905
+ };
2906
+ }
2907
+ }
2908
+
2909
+ if (method === 'ping') {
2910
+ return { jsonrpc: '2.0', id, result: {} };
2911
+ }
2912
+
2913
+ return {
2914
+ jsonrpc: '2.0',
2915
+ id,
2916
+ error: {
2917
+ code: -32601,
2918
+ message: `Method not found: ${method}`,
2919
+ },
2920
+ };
2921
+ }
2922
+
2923
+ async function main() {
2924
+ const rl = readline.createInterface({
2925
+ input: process.stdin,
2926
+ crlfDelay: Infinity,
2927
+ });
2928
+
2929
+ for await (const line of rl) {
2930
+ if (!line.trim()) {
2931
+ continue;
2932
+ }
2933
+ let request;
2934
+ try {
2935
+ request = JSON.parse(line);
2936
+ } catch {
2937
+ continue;
2938
+ }
2939
+ const response = await handleRequest(request);
2940
+ if (response) {
2941
+ send(response);
2942
+ }
2943
+ }
2944
+ }
2945
+
2946
+ if (require.main === module) {
2947
+ main().catch((error) => {
2948
+ console.error(error);
2949
+ process.exit(1);
2950
+ });
2951
+ }
2952
+
2953
+ module.exports = {
2954
+ GET_SYSTEM_ARCHITECTURE_OUTPUT_SCHEMA,
2955
+ TOOLS,
2956
+ applyMutations,
2957
+ callTool,
2958
+ compactMutationResponse,
2959
+ createDefaultCanonicalSemanticInitComposition,
2960
+ createDefaultProductionSemanticOperatorJourney,
2961
+ handleRequest,
2962
+ loadContext,
2963
+ main,
2964
+ validateDocument,
2965
+ };