@domainlang/language 0.12.0 → 0.13.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 (90) hide show
  1. package/out/ast-augmentation.d.ts +7 -2
  2. package/out/diagram/context-map-diagram-generator.d.ts +9 -2
  3. package/out/diagram/context-map-diagram-generator.js +112 -63
  4. package/out/diagram/context-map-diagram-generator.js.map +1 -1
  5. package/out/generated/ast.d.ts +323 -51
  6. package/out/generated/ast.js +194 -33
  7. package/out/generated/ast.js.map +1 -1
  8. package/out/generated/grammar.js +418 -172
  9. package/out/generated/grammar.js.map +1 -1
  10. package/out/lsp/domain-lang-completion.js +39 -15
  11. package/out/lsp/domain-lang-completion.js.map +1 -1
  12. package/out/lsp/domain-lang-formatter.js +32 -0
  13. package/out/lsp/domain-lang-formatter.js.map +1 -1
  14. package/out/lsp/domain-lang-index-manager.d.ts +2 -3
  15. package/out/lsp/domain-lang-index-manager.js +5 -8
  16. package/out/lsp/domain-lang-index-manager.js.map +1 -1
  17. package/out/lsp/domain-lang-workspace-manager.d.ts +1 -1
  18. package/out/lsp/domain-lang-workspace-manager.js +2 -26
  19. package/out/lsp/domain-lang-workspace-manager.js.map +1 -1
  20. package/out/lsp/explain.js +9 -3
  21. package/out/lsp/explain.js.map +1 -1
  22. package/out/lsp/hover/domain-lang-hover.js +13 -11
  23. package/out/lsp/hover/domain-lang-hover.js.map +1 -1
  24. package/out/lsp/hover/domain-lang-keywords.js +29 -26
  25. package/out/lsp/hover/domain-lang-keywords.js.map +1 -1
  26. package/out/sdk/ast-augmentation.d.ts +29 -21
  27. package/out/sdk/ast-augmentation.js +11 -7
  28. package/out/sdk/ast-augmentation.js.map +1 -1
  29. package/out/sdk/index.d.ts +2 -2
  30. package/out/sdk/index.js +1 -1
  31. package/out/sdk/index.js.map +1 -1
  32. package/out/sdk/loader-node.js +2 -2
  33. package/out/sdk/loader-node.js.map +1 -1
  34. package/out/sdk/patterns.d.ts +50 -61
  35. package/out/sdk/patterns.js +92 -62
  36. package/out/sdk/patterns.js.map +1 -1
  37. package/out/sdk/query.js +54 -43
  38. package/out/sdk/query.js.map +1 -1
  39. package/out/sdk/serializers.js +20 -7
  40. package/out/sdk/serializers.js.map +1 -1
  41. package/out/sdk/types.d.ts +87 -18
  42. package/out/sdk/types.js.map +1 -1
  43. package/out/sdk/validator.js +48 -64
  44. package/out/sdk/validator.js.map +1 -1
  45. package/out/services/performance-optimizer.d.ts +3 -3
  46. package/out/services/performance-optimizer.js +1 -3
  47. package/out/services/performance-optimizer.js.map +1 -1
  48. package/out/services/relationship-inference.d.ts +4 -4
  49. package/out/services/relationship-inference.js +34 -46
  50. package/out/services/relationship-inference.js.map +1 -1
  51. package/out/syntaxes/domain-lang.monarch.js +1 -1
  52. package/out/syntaxes/domain-lang.monarch.js.map +1 -1
  53. package/out/utils/import-utils.d.ts +6 -20
  54. package/out/utils/import-utils.js +3 -63
  55. package/out/utils/import-utils.js.map +1 -1
  56. package/out/validation/constants.d.ts +23 -6
  57. package/out/validation/constants.js +24 -7
  58. package/out/validation/constants.js.map +1 -1
  59. package/out/validation/maps.js +10 -4
  60. package/out/validation/maps.js.map +1 -1
  61. package/out/validation/relationships.d.ts +4 -8
  62. package/out/validation/relationships.js +96 -48
  63. package/out/validation/relationships.js.map +1 -1
  64. package/package.json +1 -1
  65. package/src/ast-augmentation.ts +7 -2
  66. package/src/diagram/context-map-diagram-generator.ts +132 -70
  67. package/src/domain-lang.langium +62 -26
  68. package/src/generated/ast.ts +413 -63
  69. package/src/generated/grammar.ts +418 -172
  70. package/src/lsp/domain-lang-completion.ts +42 -15
  71. package/src/lsp/domain-lang-formatter.ts +34 -0
  72. package/src/lsp/domain-lang-index-manager.ts +6 -9
  73. package/src/lsp/domain-lang-workspace-manager.ts +3 -29
  74. package/src/lsp/explain.ts +10 -2
  75. package/src/lsp/hover/domain-lang-hover.ts +10 -8
  76. package/src/lsp/hover/domain-lang-keywords.ts +27 -24
  77. package/src/sdk/ast-augmentation.ts +30 -21
  78. package/src/sdk/index.ts +11 -1
  79. package/src/sdk/loader-node.ts +2 -2
  80. package/src/sdk/patterns.ts +114 -76
  81. package/src/sdk/query.ts +57 -48
  82. package/src/sdk/serializers.ts +20 -7
  83. package/src/sdk/types.ts +92 -17
  84. package/src/sdk/validator.ts +52 -69
  85. package/src/services/performance-optimizer.ts +4 -6
  86. package/src/services/relationship-inference.ts +43 -54
  87. package/src/utils/import-utils.ts +9 -74
  88. package/src/validation/constants.ts +32 -9
  89. package/src/validation/maps.ts +12 -4
  90. package/src/validation/relationships.ts +150 -71
@@ -1193,11 +1193,20 @@ export class DomainLangCompletionProvider extends DefaultCompletionProvider {
1193
1193
  acceptor(context, {
1194
1194
  label: 'relationship (with patterns)',
1195
1195
  kind: CompletionItemKind.Snippet,
1196
- insertText: '[${1|OHS,PL,ACL,CF,P,SK|}] ${2:Context1} -> [${3|OHS,PL,ACL,CF,P,SK|}] ${4:Context2}',
1196
+ insertText: '${1:Context1} [${2|OHS,PL,ACL,CF,S,BBoM|}] -> [${3|CF,ACL,C,BBoM|}] ${4:Context2}',
1197
1197
  insertTextFormat: InsertTextFormat.Snippet,
1198
- documentation: 'Add a relationship with integration patterns',
1198
+ documentation: 'Add a directional relationship with side patterns',
1199
1199
  sortText: '1_relationship_patterns'
1200
1200
  });
1201
+
1202
+ acceptor(context, {
1203
+ label: 'relationship (symmetric)',
1204
+ kind: CompletionItemKind.Snippet,
1205
+ insertText: '${1:Context1} [${2|SK,P,SW|}] ${3:Context2}',
1206
+ insertTextFormat: InsertTextFormat.Snippet,
1207
+ documentation: 'Add a symmetric relationship (Shared Kernel, Partnership, or Separate Ways)',
1208
+ sortText: '1_relationship_symmetric'
1209
+ });
1201
1210
  }
1202
1211
 
1203
1212
  /**
@@ -1230,32 +1239,50 @@ export class DomainLangCompletionProvider extends DefaultCompletionProvider {
1230
1239
  acceptor: CompletionAcceptor,
1231
1240
  context: CompletionContext
1232
1241
  ): void {
1233
- // Integration pattern completions
1234
- const patterns = [
1235
- { label: 'OHS (Open Host Service)', insertText: '[OHS]', doc: 'Open Host Service pattern' },
1236
- { label: 'PL (Published Language)', insertText: '[PL]', doc: 'Published Language pattern' },
1237
- { label: 'ACL (Anti-Corruption Layer)', insertText: '[ACL]', doc: 'Anti-Corruption Layer pattern' },
1238
- { label: 'CF (Conformist)', insertText: '[CF]', doc: 'Conformist pattern' },
1239
- { label: 'P (Partnership)', insertText: '[P]', doc: 'Partnership pattern' },
1240
- { label: 'SK (Shared Kernel)', insertText: '[SK]', doc: 'Shared Kernel pattern' }
1242
+ // Side patterns (for directional relationships)
1243
+ const sidePatterns = [
1244
+ { label: 'OHS (Open Host Service)', insertText: '[OHS]', doc: 'Open Host Service — upstream side pattern' },
1245
+ { label: 'PL (Published Language)', insertText: '[PL]', doc: 'Published Language — upstream side pattern' },
1246
+ { label: 'CF (Conformist)', insertText: '[CF]', doc: 'Conformist downstream side pattern' },
1247
+ { label: 'ACL (Anti-Corruption Layer)', insertText: '[ACL]', doc: 'Anti-Corruption Layer — downstream side pattern' },
1248
+ { label: 'S (Supplier)', insertText: '[S]', doc: 'Supplier — upstream side (Customer/Supplier)' },
1249
+ { label: 'C (Customer)', insertText: '[C]', doc: 'Customer downstream side (Customer/Supplier)' },
1250
+ { label: 'BBoM (Big Ball of Mud)', insertText: '[BBoM]', doc: 'Big Ball of Mud — either side' },
1251
+ ];
1252
+
1253
+ for (const pattern of sidePatterns) {
1254
+ acceptor(context, {
1255
+ label: pattern.label,
1256
+ kind: CompletionItemKind.EnumMember,
1257
+ insertText: pattern.insertText,
1258
+ documentation: pattern.doc,
1259
+ sortText: `0_side_${pattern.label}`
1260
+ });
1261
+ }
1262
+
1263
+ // Symmetric patterns (for symmetric relationships)
1264
+ const symmetricPatterns = [
1265
+ { label: 'SK (Shared Kernel)', insertText: '[SK]', doc: 'Shared Kernel — symmetric relationship' },
1266
+ { label: 'P (Partnership)', insertText: '[P]', doc: 'Partnership — symmetric relationship' },
1267
+ { label: 'SW (Separate Ways)', insertText: '[SW]', doc: 'Separate Ways — symmetric relationship' },
1241
1268
  ];
1242
1269
 
1243
- for (const pattern of patterns) {
1270
+ for (const pattern of symmetricPatterns) {
1244
1271
  acceptor(context, {
1245
1272
  label: pattern.label,
1246
1273
  kind: CompletionItemKind.EnumMember,
1247
1274
  insertText: pattern.insertText,
1248
1275
  documentation: pattern.doc,
1249
- sortText: `0_${pattern.label}`
1276
+ sortText: `0_sym_${pattern.label}`
1250
1277
  });
1251
1278
  }
1252
1279
 
1253
- // Relationship arrow completions
1280
+ // Directional arrows
1254
1281
  const arrows = [
1255
1282
  { label: '->', doc: 'Upstream to downstream' },
1256
1283
  { label: '<-', doc: 'Downstream to upstream' },
1257
- { label: '<->', doc: 'Bidirectional/Partnership' },
1258
- { label: '><', doc: 'Separate Ways' }
1284
+ { label: '<->', doc: 'Bidirectional with patterns' },
1285
+ { label: '><', doc: 'Separate Ways (arrow form)' },
1259
1286
  ];
1260
1287
 
1261
1288
  for (const arrow of arrows) {
@@ -34,6 +34,40 @@ export class DomainLangFormatter extends AbstractFormatter {
34
34
  if (ast.isDomainMap(node)) {
35
35
  this.formatBlock(node);
36
36
  }
37
+
38
+ // Directional relationships: A [OHS, PL] -> [CF, ACL] B
39
+ if (ast.isDirectionalRelationship(node)) {
40
+ const formatter = this.getNodeFormatter(node);
41
+ // No space inside bracket groups
42
+ formatter.keywords('[').append(Formatting.noSpace());
43
+ formatter.keywords(']').prepend(Formatting.noSpace());
44
+ formatter.keywords(',').append(Formatting.oneSpace());
45
+ // Space after left context name (before [ or ->)
46
+ formatter.property('left').append(Formatting.oneSpace());
47
+ // Space before right context name (after ] or ->)
48
+ formatter.property('right').prepend(Formatting.oneSpace());
49
+ // Arrow gets explicit surrounding space only from bracket-less sides
50
+ // to avoid double-spacing when bracket groups are present
51
+ if (node.leftPatterns.length > 0) {
52
+ formatter.property('arrow').prepend(Formatting.oneSpace());
53
+ }
54
+ if (node.rightPatterns.length > 0) {
55
+ formatter.property('arrow').append(Formatting.oneSpace());
56
+ }
57
+ }
58
+
59
+ // Symmetric relationships: A [SK] B OR A >< B
60
+ if (ast.isSymmetricRelationship(node)) {
61
+ const formatter = this.getNodeFormatter(node);
62
+ if (node.arrow === '><') {
63
+ formatter.property('arrow').surround(Formatting.oneSpace());
64
+ }
65
+ // [SK] form: space before [, no space inside brackets, space after ]
66
+ formatter.keywords('[').prepend(Formatting.oneSpace());
67
+ formatter.keywords('[').append(Formatting.noSpace());
68
+ formatter.keywords(']').prepend(Formatting.noSpace());
69
+ formatter.keywords(']').append(Formatting.oneSpace());
70
+ }
37
71
  } catch (error) {
38
72
  console.error('Error in format:', error);
39
73
  // Continue - don't crash formatting
@@ -1,7 +1,6 @@
1
1
  import type { LangiumDocument, LangiumSharedCoreServices, URI } from 'langium';
2
2
  import { DefaultIndexManager, DocumentState } from 'langium';
3
3
  import { CancellationToken } from 'vscode-jsonrpc';
4
- import { resolveImportPath } from '../utils/import-utils.js';
5
4
  import type { Model } from '../generated/ast.js';
6
5
  import type { ImportResolver } from '../services/import-resolver.js';
7
6
  import type { DomainLangServices } from '../domain-lang-module.js';
@@ -96,7 +95,7 @@ export class DomainLangIndexManager extends DefaultIndexManager {
96
95
  /**
97
96
  * DI-injected import resolver. Set via late-binding because
98
97
  * IndexManager (shared module) is created before ImportResolver (language module).
99
- * Falls back to standalone resolveImportPath when not set.
98
+ * Always set before any document indexing begins via `setLanguageServices()`.
100
99
  */
101
100
  private importResolver: ImportResolver | undefined;
102
101
 
@@ -117,15 +116,13 @@ export class DomainLangIndexManager extends DefaultIndexManager {
117
116
  }
118
117
 
119
118
  /**
120
- * Resolves an import path using the DI-injected ImportResolver when available,
121
- * falling back to the standalone resolver for backwards compatibility.
119
+ * Resolves an import path using the DI-injected ImportResolver.
122
120
  */
123
- private async resolveImport(document: LangiumDocument, specifier: string): Promise<URI> {
124
- if (this.importResolver) {
125
- return this.importResolver.resolveForDocument(document, specifier);
121
+ private resolveImport(document: LangiumDocument, specifier: string): Promise<URI> {
122
+ if (!this.importResolver) {
123
+ throw new Error('ImportResolver not initialised — ensure setLanguageServices() was called');
126
124
  }
127
- // Fallback for contexts where language services aren't wired (e.g., tests)
128
- return resolveImportPath(document, specifier);
125
+ return this.importResolver.resolveForDocument(document, specifier);
129
126
  }
130
127
 
131
128
  /**
@@ -66,7 +66,7 @@ export class DomainLangWorkspaceManager extends DefaultWorkspaceManager {
66
66
  /**
67
67
  * DI-injected import resolver. Set via late-binding because
68
68
  * WorkspaceManager (shared module) is created before ImportResolver (language module).
69
- * Falls back to standalone ensureImportGraphFromDocument when not set.
69
+ * Always set before any workspace loading begins via `setLanguageServices()`.
70
70
  */
71
71
  private importResolver: ImportResolver | undefined;
72
72
 
@@ -296,35 +296,9 @@ export class DomainLangWorkspaceManager extends DefaultWorkspaceManager {
296
296
  */
297
297
  private async loadImportGraph(document: LangiumDocument): Promise<Set<string>> {
298
298
  if (!this.importResolver) {
299
- // Fallback to standalone utility when DI isn't wired
300
- return ensureImportGraphFromDocument(document, this.langiumDocuments);
299
+ throw new Error('ImportResolver not initialised ensure setLanguageServices() was called');
301
300
  }
302
-
303
- const resolver = this.importResolver;
304
- const langiumDocuments = this.langiumDocuments;
305
- const visited = new Set<string>();
306
-
307
- async function visit(doc: LangiumDocument): Promise<void> {
308
- const uriString = doc.uri.toString();
309
- if (visited.has(uriString)) return;
310
- visited.add(uriString);
311
-
312
- const model = doc.parseResult.value as { imports?: Array<{ uri?: string }> };
313
- for (const imp of model.imports ?? []) {
314
- if (!imp.uri) continue;
315
-
316
- try {
317
- const resolvedUri = await resolver.resolveForDocument(doc, imp.uri);
318
- const childDoc = await langiumDocuments.getOrCreateDocument(resolvedUri);
319
- await visit(childDoc);
320
- } catch {
321
- // Import resolution failed — validation will report the error
322
- }
323
- }
324
- }
325
-
326
- await visit(document);
327
- return visited;
301
+ return ensureImportGraphFromDocument(document, this.langiumDocuments, this.importResolver);
328
302
  }
329
303
 
330
304
  // --- PRS-017 R7: Progress reporting ---
@@ -22,9 +22,11 @@ import {
22
22
  isBoundedContext,
23
23
  isClassification,
24
24
  isContextMap,
25
+ isDirectionalRelationship,
25
26
  isDomain,
26
27
  isDomainMap,
27
28
  isRelationship,
29
+ isSymmetricRelationship,
28
30
  isTeam,
29
31
  } from '../generated/ast.js';
30
32
  import {
@@ -133,9 +135,15 @@ function explainClassification(classification: Classification): string {
133
135
  function explainRelationship(relationship: Relationship): string {
134
136
  const leftName = relationship.left.link?.ref?.name ?? 'unknown';
135
137
  const rightName = relationship.right.link?.ref?.name ?? 'unknown';
136
- const arrow = relationship.arrow;
137
138
 
138
- const description = `Relationship from **${leftName}** ${arrow} **${rightName}**`;
139
+ let relationText = '↔';
140
+ if (isDirectionalRelationship(relationship)) {
141
+ relationText = relationship.arrow;
142
+ } else if (isSymmetricRelationship(relationship)) {
143
+ relationText = relationship.pattern ? `[${relationship.pattern.$type}]` : '><';
144
+ }
145
+
146
+ const description = `Relationship from **${leftName}** ${relationText} **${rightName}**`;
139
147
  return formatHoverContent('', '🔗', 'relationship', undefined, [description]);
140
148
  }
141
149
 
@@ -385,14 +385,17 @@ export class DomainLangHoverProvider extends AstNodeHoverProvider {
385
385
  private getRelationshipHover(node: AstNode, commentBlock: string, _importAlias?: string): string | undefined {
386
386
  if (!ast.isRelationship(node)) return undefined;
387
387
 
388
- const leftPatterns = node.leftPatterns.join(', ');
389
- const rightPatterns = node.rightPatterns.join(', ');
390
388
  const fields: string[] = [
391
- `${this.refLink(node.left.link)} ${node.arrow} ${this.refLink(node.right.link)}`
389
+ `${this.refLink(node.left.link)} ${node.arrow ?? '↔'} ${this.refLink(node.right.link)}`
392
390
  ];
393
- if (node.type) fields.push(`**Type:** \`${node.type}\``);
394
- if (leftPatterns) fields.push(`**Left patterns:** ${leftPatterns}`);
395
- if (rightPatterns) fields.push(`**Right patterns:** ${rightPatterns}`);
391
+ if (ast.isDirectionalRelationship(node)) {
392
+ const leftPatterns = node.leftPatterns.map(p => p.$type).join(', ');
393
+ const rightPatterns = node.rightPatterns.map(p => p.$type).join(', ');
394
+ if (leftPatterns) fields.push(`**Left patterns:** ${leftPatterns}`);
395
+ if (rightPatterns) fields.push(`**Right patterns:** ${rightPatterns}`);
396
+ } else if (ast.isSymmetricRelationship(node) && node.pattern) {
397
+ fields.push(`**Pattern:** ${node.pattern.$type}`);
398
+ }
396
399
 
397
400
  return this.formatHover(commentBlock, '🔗', 'relationship', undefined, fields);
398
401
  }
@@ -439,8 +442,7 @@ export class DomainLangHoverProvider extends AstNodeHoverProvider {
439
442
  private formatRelationshipLine(rel: ast.Relationship): string {
440
443
  const left = this.refLink(rel.left?.link);
441
444
  const right = this.refLink(rel.right?.link);
442
- const type = rel.type ? ` \`${rel.type}\`` : '';
443
- return `- ${left} ${rel.arrow} ${right}${type}`;
445
+ return `- ${left} ${rel.arrow ?? '↔'} ${right}`;
444
446
  }
445
447
 
446
448
  /**
@@ -102,35 +102,38 @@ export const keywordExplanations: Record<string, string> = {
102
102
  this: `**this** - References the current bounded context in relationships.${REL_LINK}`,
103
103
 
104
104
  // ========================================================================
105
- // DDD Integration Patterns
106
- // ========================================================================
107
- acl: `**ACL** - Anti-Corruption Layer. Protects from external models by translating between domains.${REL_LINK}`,
108
- anticorruptionlayer: `**AntiCorruptionLayer** - Protects from external models by translating between domains.${REL_LINK}`,
109
- ohs: `**OHS** - Open Host Service. Provides a well-documented API for integration.${REL_LINK}`,
110
- openhostservice: `**OpenHostService** - Provides a well-documented API for integration.${REL_LINK}`,
111
- pl: `**PL** - Published Language. Documented language for inter-context communication.${REL_LINK}`,
112
- publishedlanguage: `**PublishedLanguage** - Documented language for inter-context communication.${REL_LINK}`,
113
- cf: `**CF** - Conformist. Adopts upstream model without translation.${REL_LINK}`,
114
- conformist: `**Conformist** - Adopts upstream model without translation.${REL_LINK}`,
115
- p: `**P** - Partnership. Two teams with mutual dependency and shared goals.${REL_LINK}`,
116
- partnership: `**Partnership** - Two teams with mutual dependency and shared goals.${REL_LINK}`,
117
- sk: `**SK** - Shared Kernel. Shared code/data requiring careful coordination.${REL_LINK}`,
118
- sharedkernel: `**SharedKernel** - Shared code/data requiring careful coordination.${REL_LINK}`,
119
- bbom: `**BBoM** - Big Ball of Mud. Legacy area without clear boundaries.${REL_LINK}`,
120
- bigballofmud: `**BigBallOfMud** - Legacy area without clear boundaries.${REL_LINK}`,
105
+ // DDD Side Patterns (directional relationships)
106
+ // ========================================================================
107
+ acl: `**ACL** - Anti-Corruption Layer. Protects from external models by translating between domains. Used on the downstream side.${REL_LINK}`,
108
+ anticorruptionlayer: `**AntiCorruptionLayer** - Anti-Corruption Layer. Protects from external models by translating between domains. Used on the downstream side.${REL_LINK}`,
109
+ ohs: `**OHS** - Open Host Service. Provides a well-documented API for integration. Used on the upstream side.${REL_LINK}`,
110
+ openhostservice: `**OpenHostService** - Open Host Service. Provides a well-documented API for integration. Used on the upstream side.${REL_LINK}`,
111
+ pl: `**PL** - Published Language. Documented language for inter-context communication. Used on the upstream side.${REL_LINK}`,
112
+ publishedlanguage: `**PublishedLanguage** - Published Language. Documented language for inter-context communication. Used on the upstream side.${REL_LINK}`,
113
+ cf: `**CF** - Conformist. Adopts upstream model without translation. Used on the downstream side.${REL_LINK}`,
114
+ conformist: `**Conformist** - Conformist. Adopts upstream model without translation. Used on the downstream side.${REL_LINK}`,
115
+ s: `**S** - Supplier. Negotiated contract provider in a Customer/Supplier relationship. Must be on the upstream side.${REL_LINK}`,
116
+ supplier: `**Supplier** - Negotiated contract provider in a Customer/Supplier relationship. Must be on the upstream side.${REL_LINK}`,
117
+ c: `**C** - Customer. Negotiated contract consumer in a Customer/Supplier relationship. Must be on the downstream side.${REL_LINK}`,
118
+ customer: `**Customer** - Negotiated contract consumer in a Customer/Supplier relationship. Must be on the downstream side.${REL_LINK}`,
119
+ bbom: `**BBoM** - Big Ball of Mud. Legacy area without clear boundaries. Can appear on either side.${REL_LINK}`,
120
+ bigballofmud: `**BigBallOfMud** - Big Ball of Mud. Legacy area without clear boundaries. Can appear on either side.${REL_LINK}`,
121
121
 
122
122
  // ========================================================================
123
- // Relationship Types
123
+ // DDD Symmetric Patterns (symmetric relationships — entity [Pattern] entity)
124
124
  // ========================================================================
125
- customersupplier: `**CustomerSupplier** - Downstream depends on upstream with influence over priorities.${REL_LINK}`,
126
- upstreamdownstream: `**UpstreamDownstream** - One context depends on another's model.${REL_LINK}`,
127
- separateways: `**SeparateWays** - Contexts with no integration, solving problems independently.${REL_LINK}`,
125
+ p: `**P** - Partnership. Symmetric relationship: two teams with mutual dependency and shared goals. Usage: \`A [P] B\`${REL_LINK}`,
126
+ partnership: `**Partnership** - Symmetric relationship: two teams with mutual dependency and shared goals. Usage: \`A [Partnership] B\`${REL_LINK}`,
127
+ sk: `**SK** - Shared Kernel. Symmetric relationship: shared code/data requiring careful coordination between both contexts. Usage: \`A [SK] B\`${REL_LINK}`,
128
+ sharedkernel: `**SharedKernel** - Symmetric relationship: shared code/data requiring careful coordination between both contexts. Usage: \`A [SharedKernel] B\`${REL_LINK}`,
129
+ sw: `**SW** - Separate Ways. Symmetric relationship: contexts with no integration, solving problems independently. Usage: \`A [SW] B\` or \`A >< B\`${REL_LINK}`,
130
+ separateways: `**SeparateWays** - Symmetric relationship: contexts with no integration. Usage: \`A [SeparateWays] B\` or \`A >< B\`${REL_LINK}`,
128
131
 
129
132
  // ========================================================================
130
133
  // Relationship Arrows
131
134
  // ========================================================================
132
- '->': `**->** - Unidirectional dependency (upstream to downstream).${REL_LINK}`,
133
- '<->': `**<->** - Bidirectional dependency (mutual).${REL_LINK}`,
134
- '<-': `**<-** - Reverse unidirectional dependency (downstream to upstream).${REL_LINK}`,
135
- '><': `**><** - Separate Ways (no integration).${REL_LINK}`,
135
+ '->': `**->** - Directional: upstream (left) to downstream (right). Example: \`Orders [OHS] -> [CF] Payments\`${REL_LINK}`,
136
+ '<->': `**<->** - Bidirectional: both sides have patterns, mutual data flow. Example: \`Orders [OHS] <-> [CF] Payments\`${REL_LINK}`,
137
+ '<-': `**<-** - Reverse directional: upstream (right) to downstream (left). Example: \`Payments [ACL] <- Orders\`${REL_LINK}`,
138
+ '><': `**><** - Separate Ways: no integration between contexts. Equivalent to \`[SW]\`. Example: \`Orders >< Legacy\`${REL_LINK}`,
136
139
  };
@@ -46,15 +46,19 @@
46
46
  * - `fqn` - Computed fully qualified name
47
47
  * - `hasType(name)` - Check type matches
48
48
  *
49
- * **Properties added to Relationship:**
50
- * - `hasPattern(pattern)` - Check if pattern exists on either side
51
- * - `hasLeftPattern(pattern)` - Check left patterns
52
- * - `hasRightPattern(pattern)` - Check right patterns
49
+ * **Properties added to DirectionalRelationship:**
50
+ * - `isBidirectional` - Check if relationship is bidirectional (`<->`)
51
+ * - `leftContextName` - Resolved name of left context (handles `this`)
52
+ * - `rightContextName` - Resolved name of right context (handles `this`)
53
+ * - `hasPattern(patternType)` - Check if pattern exists on either side
54
+ * - `hasLeftPattern(patternType)` - Check left patterns
55
+ * - `hasRightPattern(patternType)` - Check right patterns
53
56
  * - `isUpstream(side)` - Check if side is upstream
54
57
  * - `isDownstream(side)` - Check if side is downstream
55
- * - `isBidirectional` - Check if relationship is bidirectional
56
- * - `leftContextName` - Resolved name of left context
57
- * - `rightContextName` - Resolved name of right context
58
+ *
59
+ * **Properties added to SymmetricRelationship:**
60
+ * - `leftContextName` - Resolved name of left context (handles `this`)
61
+ * - `rightContextName` - Resolved name of right context (handles `this`)
58
62
  *
59
63
  * @module sdk/ast-augmentation
60
64
  */
@@ -109,9 +113,9 @@ declare module '../generated/ast.js' {
109
113
  }
110
114
 
111
115
  /**
112
- * Augmented Relationship with SDK helper methods.
116
+ * Augmented DirectionalRelationship with SDK helper methods.
113
117
  */
114
- interface Relationship {
118
+ interface DirectionalRelationship {
115
119
  /** Resolved name of left context (handles 'this') */
116
120
  readonly leftContextName: string;
117
121
  /** Resolved name of right context (handles 'this') */
@@ -120,36 +124,41 @@ declare module '../generated/ast.js' {
120
124
  readonly isBidirectional: boolean;
121
125
 
122
126
  /**
123
- * Checks if the relationship has a specific integration pattern on either side.
124
- * Accepts both abbreviations (SK, ACL) and full names (SharedKernel, AntiCorruptionLayer).
125
- * @param pattern - Pattern abbreviation or full name
127
+ * Checks if the relationship has a specific side pattern on either side.
128
+ * Accepts pattern $type names like 'OpenHostService', 'Conformist', etc.
126
129
  */
127
- hasPattern(pattern: import('./patterns.js').IntegrationPattern | string): boolean;
130
+ hasPattern(patternType: string): boolean;
128
131
 
129
132
  /**
130
- * Checks if the left side has a specific integration pattern.
131
- * @param pattern - Pattern abbreviation or full name
133
+ * Checks if the left side has a specific side pattern.
132
134
  */
133
- hasLeftPattern(pattern: import('./patterns.js').IntegrationPattern | string): boolean;
135
+ hasLeftPattern(patternType: string): boolean;
134
136
 
135
137
  /**
136
- * Checks if the right side has a specific integration pattern.
137
- * @param pattern - Pattern abbreviation or full name
138
+ * Checks if the right side has a specific side pattern.
138
139
  */
139
- hasRightPattern(pattern: import('./patterns.js').IntegrationPattern | string): boolean;
140
+ hasRightPattern(patternType: string): boolean;
140
141
 
141
142
  /**
142
143
  * Checks if the specified side is upstream (provider) in this relationship.
143
- * @param side - 'left' or 'right'
144
144
  */
145
145
  isUpstream(side: 'left' | 'right'): boolean;
146
146
 
147
147
  /**
148
148
  * Checks if the specified side is downstream (consumer) in this relationship.
149
- * @param side - 'left' or 'right'
150
149
  */
151
150
  isDownstream(side: 'left' | 'right'): boolean;
152
151
  }
152
+
153
+ /**
154
+ * Augmented SymmetricRelationship with SDK helper methods.
155
+ */
156
+ interface SymmetricRelationship {
157
+ /** Resolved name of left context (handles 'this') */
158
+ readonly leftContextName: string;
159
+ /** Resolved name of right context (handles 'this') */
160
+ readonly rightContextName: string;
161
+ }
153
162
  }
154
163
 
155
164
  // Export nothing - this file is purely for type augmentation
package/src/sdk/index.ts CHANGED
@@ -116,14 +116,19 @@ export { fromModel, fromDocument, fromServices, augmentModel } from './query.js'
116
116
  export {
117
117
  Pattern,
118
118
  PatternFullName,
119
+ PatternAbbreviation,
119
120
  PatternAliases,
120
121
  matchesPattern,
121
122
  isUpstreamPattern,
122
123
  isDownstreamPattern,
123
124
  isMutualPattern,
125
+ isUpstreamSidePattern,
126
+ isDownstreamSidePattern,
127
+ isBBoMSidePattern,
128
+ getPatternAbbreviation,
124
129
  UpstreamPatterns,
125
130
  DownstreamPatterns,
126
- MutualPatterns,
131
+ SymmetricPatterns,
127
132
  } from './patterns.js';
128
133
 
129
134
  export type { IntegrationPattern } from './patterns.js';
@@ -140,6 +145,11 @@ export type {
140
145
  LoadOptions,
141
146
  BcQueryBuilder,
142
147
  RelationshipView,
148
+ RelationshipSide,
149
+ DirectionalRelationshipView,
150
+ DirectionalKind,
151
+ SymmetricRelationshipView,
152
+ SymmetricKind,
143
153
  } from './types.js';
144
154
 
145
155
  // Serializers for tool responses (browser-safe)
@@ -62,7 +62,7 @@ export async function loadModel(
62
62
  options?: LoadOptions
63
63
  ): Promise<QueryContext> {
64
64
  // Resolve absolute path
65
- const path = await import('path');
65
+ const path = await import('node:path');
66
66
  const absolutePath = path.isAbsolute(entryFile)
67
67
  ? entryFile
68
68
  : path.resolve(options?.workspaceDir ?? process.cwd(), entryFile);
@@ -82,7 +82,7 @@ export async function loadModel(
82
82
  }
83
83
 
84
84
  // Read file content and create document
85
- const fs = await import('fs/promises');
85
+ const fs = await import('node:fs/promises');
86
86
  const fileContent = await fs.readFile(absolutePath, 'utf-8');
87
87
  const uri = URI.file(absolutePath);
88
88