@memoarchitect/tools 0.8.8 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +3 -3
- package/packages/tools/lib/analysis/dsm.d.ts.map +1 -1
- package/packages/tools/lib/analysis/dsm.js +9 -4
- package/packages/tools/lib/analysis/dsm.js.map +1 -1
- package/packages/tools/lib/commands/export.js +1 -1
- package/packages/tools/lib/commands/export.js.map +1 -1
- package/packages/tools/lib/commands/init.d.ts.map +1 -1
- package/packages/tools/lib/commands/init.js +16 -5
- package/packages/tools/lib/commands/init.js.map +1 -1
- package/packages/tools/lib/commands/rules.d.ts.map +1 -1
- package/packages/tools/lib/commands/rules.js +3 -2
- package/packages/tools/lib/commands/rules.js.map +1 -1
- package/packages/tools/lib/commands/validate.d.ts.map +1 -1
- package/packages/tools/lib/commands/validate.js +71 -4
- package/packages/tools/lib/commands/validate.js.map +1 -1
- package/packages/tools/lib/completeness/tracker.d.ts.map +1 -1
- package/packages/tools/lib/completeness/tracker.js +11 -4
- package/packages/tools/lib/completeness/tracker.js.map +1 -1
- package/packages/tools/lib/dhf/document-registry.js +1 -1
- package/packages/tools/lib/dhf/document-registry.js.map +1 -1
- package/packages/tools/lib/dhf/query-executor.d.ts +1 -1
- package/packages/tools/lib/dhf/query-executor.d.ts.map +1 -1
- package/packages/tools/lib/dhf/query-executor.js +4 -4
- package/packages/tools/lib/dhf/query-executor.js.map +1 -1
- package/packages/tools/lib/dhf/standards-library.d.ts.map +1 -1
- package/packages/tools/lib/dhf/standards-library.js +53 -10
- package/packages/tools/lib/dhf/standards-library.js.map +1 -1
- package/packages/tools/lib/dhf/template-lint.js +1 -1
- package/packages/tools/lib/dhf/template-lint.js.map +1 -1
- package/packages/tools/lib/language/generated/ast.d.ts +75 -34
- package/packages/tools/lib/language/generated/ast.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/ast.js +36 -2
- package/packages/tools/lib/language/generated/ast.js.map +1 -1
- package/packages/tools/lib/language/generated/grammar.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/grammar.js +516 -384
- package/packages/tools/lib/language/generated/grammar.js.map +1 -1
- package/packages/tools/lib/mcp/tools.d.ts.map +1 -1
- package/packages/tools/lib/mcp/tools.js +2 -1
- package/packages/tools/lib/mcp/tools.js.map +1 -1
- package/packages/tools/lib/model/builder.d.ts +2 -0
- package/packages/tools/lib/model/builder.d.ts.map +1 -1
- package/packages/tools/lib/model/builder.js +303 -42
- package/packages/tools/lib/model/builder.js.map +1 -1
- package/packages/tools/lib/model/config-loader.d.ts.map +1 -1
- package/packages/tools/lib/model/config-loader.js +7 -1
- package/packages/tools/lib/model/config-loader.js.map +1 -1
- package/packages/tools/lib/model/config.d.ts +9 -0
- package/packages/tools/lib/model/config.d.ts.map +1 -1
- package/packages/tools/lib/model/identity-registry.d.ts +39 -0
- package/packages/tools/lib/model/identity-registry.d.ts.map +1 -0
- package/packages/tools/lib/model/identity-registry.js +118 -0
- package/packages/tools/lib/model/identity-registry.js.map +1 -0
- package/packages/tools/lib/model/native-project.d.ts +6 -0
- package/packages/tools/lib/model/native-project.d.ts.map +1 -1
- package/packages/tools/lib/model/native-project.js +6 -0
- package/packages/tools/lib/model/native-project.js.map +1 -1
- package/packages/tools/lib/model/relationship-registry.d.ts.map +1 -1
- package/packages/tools/lib/model/relationship-registry.js +22 -0
- package/packages/tools/lib/model/relationship-registry.js.map +1 -1
- package/packages/tools/lib/model/semantic.d.ts +57 -2
- package/packages/tools/lib/model/semantic.d.ts.map +1 -1
- package/packages/tools/lib/model/semantic.js +38 -0
- package/packages/tools/lib/model/semantic.js.map +1 -1
- package/packages/tools/lib/model/short-id.d.ts +22 -4
- package/packages/tools/lib/model/short-id.d.ts.map +1 -1
- package/packages/tools/lib/model/short-id.js +46 -7
- package/packages/tools/lib/model/short-id.js.map +1 -1
- package/packages/tools/lib/model/view-deriver.d.ts.map +1 -1
- package/packages/tools/lib/model/view-deriver.js +71 -22
- package/packages/tools/lib/model/view-deriver.js.map +1 -1
- package/packages/tools/lib/operations/project-snapshot.d.ts.map +1 -1
- package/packages/tools/lib/operations/project-snapshot.js +24 -2
- package/packages/tools/lib/operations/project-snapshot.js.map +1 -1
- package/packages/tools/lib/protocol/messages.d.ts +29 -0
- package/packages/tools/lib/protocol/messages.d.ts.map +1 -1
- package/packages/tools/lib/server/config-resolver.d.ts.map +1 -1
- package/packages/tools/lib/server/config-resolver.js +6 -1
- package/packages/tools/lib/server/config-resolver.js.map +1 -1
- package/packages/tools/lib/server/dev-server.d.ts.map +1 -1
- package/packages/tools/lib/server/dev-server.js +40 -4
- package/packages/tools/lib/server/dev-server.js.map +1 -1
- package/packages/tools/lib/server/dhf-doc-store.d.ts.map +1 -1
- package/packages/tools/lib/server/dhf-doc-store.js +10 -2
- package/packages/tools/lib/server/dhf-doc-store.js.map +1 -1
- package/packages/tools/lib/server/package-source.d.ts +20 -1
- package/packages/tools/lib/server/package-source.d.ts.map +1 -1
- package/packages/tools/lib/server/package-source.js +23 -4
- package/packages/tools/lib/server/package-source.js.map +1 -1
- package/packages/tools/lib/server/package-writer.d.ts +18 -0
- package/packages/tools/lib/server/package-writer.d.ts.map +1 -1
- package/packages/tools/lib/server/package-writer.js +58 -5
- package/packages/tools/lib/server/package-writer.js.map +1 -1
- package/packages/tools/lib/server/persistor.d.ts.map +1 -1
- package/packages/tools/lib/server/persistor.js +4 -1
- package/packages/tools/lib/server/persistor.js.map +1 -1
- package/packages/tools/lib/server/view-layout-store.d.ts.map +1 -1
- package/packages/tools/lib/server/view-layout-store.js +6 -3
- package/packages/tools/lib/server/view-layout-store.js.map +1 -1
- package/packages/tools/lib/validator/behavior-validator.d.ts +3 -5
- package/packages/tools/lib/validator/behavior-validator.d.ts.map +1 -1
- package/packages/tools/lib/validator/behavior-validator.js +28 -54
- package/packages/tools/lib/validator/behavior-validator.js.map +1 -1
- package/packages/tools/lib/validator/builtin-rules.js +12 -12
- package/packages/tools/lib/validator/builtin-rules.js.map +1 -1
- package/packages/tools/lib/validator/constraint-eval.d.ts +39 -2
- package/packages/tools/lib/validator/constraint-eval.d.ts.map +1 -1
- package/packages/tools/lib/validator/constraint-eval.js +104 -4
- package/packages/tools/lib/validator/constraint-eval.js.map +1 -1
- package/packages/tools/lib/validator/constraint-loader.js +37 -2
- package/packages/tools/lib/validator/constraint-loader.js.map +1 -1
- package/packages/tools/lib/validator/rule-engine.d.ts +3 -1
- package/packages/tools/lib/validator/rule-engine.d.ts.map +1 -1
- package/packages/tools/lib/validator/rule-engine.js +4 -2
- package/packages/tools/lib/validator/rule-engine.js.map +1 -1
- package/packages/tools/src/grammar/memo-sysml.langium +41 -6
|
@@ -37,6 +37,7 @@ import { AstUtils } from 'langium';
|
|
|
37
37
|
import { isRequirementVerificationMember, isSatisfyRequirementUsage, isPerformActionUsage, isIncludeUseCaseUsage, isExhibitStateUsage, isFramedConcernMember, isSubjectMember, isDependency, isVariantMember, isActorMember, } from '../language/generated/ast.js';
|
|
38
38
|
import { PackageRegistry } from './package-registry.js';
|
|
39
39
|
import { assignSequentialShortIds, kindToPrefix } from './short-id.js';
|
|
40
|
+
import { identityKey, priorShortIds } from './identity-registry.js';
|
|
40
41
|
import { LANGUAGE_NATIVE_RELATIONS, REFINEMENT_METADATA, pascalToCamelCase } from './relationship-registry.js';
|
|
41
42
|
import { indexKinds, kindConformsTo } from './relationship-legality.js';
|
|
42
43
|
import { CONTROL_KIND_METACLASS } from './activity-notation.js';
|
|
@@ -142,12 +143,13 @@ export function buildMemoModel(documents, config, parseErrors = [], registries)
|
|
|
142
143
|
// so emit one per nesting. A model that nests natively then looks identical
|
|
143
144
|
// to one that wrote `Composes`, which is what lets R10-S7 delete the
|
|
144
145
|
// relationship without the containment vanishing. Ports carry `owner` too,
|
|
145
|
-
// so this
|
|
146
|
+
// so this covers part and item nesting, and nothing else.
|
|
147
|
+
const CONTAINING_CONSTRUCTS = new Set(['part', 'item']);
|
|
146
148
|
for (const el of elements.values()) {
|
|
147
|
-
if (el.construct
|
|
149
|
+
if (!CONTAINING_CONSTRUCTS.has(el.construct) || !el.owner)
|
|
148
150
|
continue;
|
|
149
151
|
const parent = elements.get(el.owner);
|
|
150
|
-
if (!parent || parent.construct
|
|
152
|
+
if (!parent || !CONTAINING_CONSTRUCTS.has(parent.construct))
|
|
151
153
|
continue;
|
|
152
154
|
relationships.push({
|
|
153
155
|
id: `rel-${++relationshipCounter}`,
|
|
@@ -215,15 +217,27 @@ export function buildMemoModel(documents, config, parseErrors = [], registries)
|
|
|
215
217
|
prefixElements.push(element);
|
|
216
218
|
elementsByPrefix.set(prefix, prefixElements);
|
|
217
219
|
}
|
|
220
|
+
// Identities assigned on earlier builds win. Both of these are quoted
|
|
221
|
+
// outside the model, so recomputing them from current content — which is
|
|
222
|
+
// what happened before `memo.identity.yaml` — silently repointed every
|
|
223
|
+
// external reference whenever a file moved or an element was inserted.
|
|
224
|
+
const priorIdentities = config.priorIdentities ?? {};
|
|
225
|
+
const priorShortIdsByElement = priorShortIds(priorIdentities);
|
|
218
226
|
for (const [prefix, prefixElements] of elementsByPrefix) {
|
|
219
|
-
const idToShortId = assignSequentialShortIds(prefix, prefixElements.map(e => e.id));
|
|
227
|
+
const idToShortId = assignSequentialShortIds(prefix, prefixElements.map(e => e.id), priorShortIdsByElement);
|
|
220
228
|
for (const el of prefixElements) {
|
|
221
229
|
el.shortId = idToShortId.get(el.id);
|
|
222
230
|
// `uuid` is part of the ontology's identification core, so a model
|
|
223
231
|
// may author it. An authored value wins: it is the whole point of
|
|
224
232
|
// writing one — the element keeps its identity across a file move
|
|
225
233
|
// or an id change, which the derived hash cannot survive.
|
|
226
|
-
|
|
234
|
+
// Precedence: an authored uuid, then the one this project already
|
|
235
|
+
// assigned, then a fresh derivation. The middle term is what makes a
|
|
236
|
+
// uuid survive a file move — the derivation hashes the file path, so
|
|
237
|
+
// without it the same element gets a new identity when it moves.
|
|
238
|
+
el.uuid = el.attributes.uuid?.trim()
|
|
239
|
+
|| priorIdentities[identityKey(el)]?.uuid
|
|
240
|
+
|| stableElementUuid(el);
|
|
227
241
|
}
|
|
228
242
|
}
|
|
229
243
|
// Phase 4: Validate relationship end types (warnings only).
|
|
@@ -393,7 +407,10 @@ function extractFromPackage(pkg, filePath, parentPackage, config, elements, defe
|
|
|
393
407
|
break;
|
|
394
408
|
case 'UseCaseDeclaration': {
|
|
395
409
|
const useCase = member;
|
|
396
|
-
if (
|
|
410
|
+
if (useCase.isDefinition) {
|
|
411
|
+
extractDefinitionElement({ name: useCase.name, specialization: useCase.specialization, body: useCase.body ?? useCase.usageBody }, 'use case', filePath, packageName, config, elements, registry, registries);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
397
414
|
extractUsage({ name: useCase.name, type: useCase.type, body: useCase.usageBody }, 'use case', filePath, packageName, config, elements, registry, registries);
|
|
398
415
|
}
|
|
399
416
|
break;
|
|
@@ -416,6 +433,9 @@ function extractFromPackage(pkg, filePath, parentPackage, config, elements, defe
|
|
|
416
433
|
case 'ViewUsage':
|
|
417
434
|
extractUsage(member, 'view', filePath, packageName, config, elements, registry, registries);
|
|
418
435
|
break;
|
|
436
|
+
case 'ViewpointUsage':
|
|
437
|
+
extractViewpointUsage(member, filePath, packageName, config, elements, registry, registries);
|
|
438
|
+
break;
|
|
419
439
|
case 'ConnectionUsage':
|
|
420
440
|
// Defer connection resolution until all elements are extracted
|
|
421
441
|
deferredConnections.push({
|
|
@@ -500,10 +520,24 @@ function extractFromPackage(pkg, filePath, parentPackage, config, elements, defe
|
|
|
500
520
|
case 'PortDefinition':
|
|
501
521
|
case 'InterfaceDefinition':
|
|
502
522
|
case 'ConnectionDefinition':
|
|
523
|
+
extractDefinitionElement(member, DEFINITION_CONSTRUCTS[member.$type], filePath, packageName, config, elements, registry, registries);
|
|
503
524
|
extractDefinitionPorts(member, filePath, packageName, config, elements, registry, registries);
|
|
525
|
+
// A definition's body is composition: `part def Pump { part
|
|
526
|
+
// motor : Motor; }` is what a BDD draws and what an IBD looks
|
|
527
|
+
// inside. Nested usages become elements owned by the
|
|
528
|
+
// definition, exactly as they do under a part usage.
|
|
529
|
+
extractNestedParts(member, filePath, packageName, config, elements, registry, registries);
|
|
504
530
|
break;
|
|
505
|
-
|
|
506
|
-
|
|
531
|
+
case 'RequirementDefinition':
|
|
532
|
+
case 'VerificationDefinition':
|
|
533
|
+
case 'StateDefinition':
|
|
534
|
+
case 'AttributeDefinition':
|
|
535
|
+
case 'EnumDefinition':
|
|
536
|
+
extractDefinitionElement(member, DEFINITION_CONSTRUCTS[member.$type], filePath, packageName, config, elements, registry, registries);
|
|
537
|
+
break;
|
|
538
|
+
// A `view def` / `viewpoint def` stays out: those are presentation
|
|
539
|
+
// apparatus, read through the viewpoint tree rather than as model
|
|
540
|
+
// content.
|
|
507
541
|
}
|
|
508
542
|
}
|
|
509
543
|
}
|
|
@@ -625,8 +659,12 @@ function extractUsage(usage, construct, filePath, packageName, config, elements,
|
|
|
625
659
|
// Without this, an inline-authored port silently has no element: it
|
|
626
660
|
// parses, every relationship endpoint referencing it resolves to a
|
|
627
661
|
// dangling id, and nothing downstream reports why.
|
|
628
|
-
|
|
662
|
+
// Items nest too — a standard contains its clauses — so the nested-usage
|
|
663
|
+
// walk runs for both. Ports stay part-only: an item has no boundary.
|
|
664
|
+
if (construct === 'part' || construct === 'item') {
|
|
629
665
|
extractNestedParts(usage, filePath, packageName, config, elements, registry, registries);
|
|
666
|
+
}
|
|
667
|
+
if (construct === 'part') {
|
|
630
668
|
extractDefinitionPorts(usage, filePath, packageName, config, elements, registry, registries);
|
|
631
669
|
}
|
|
632
670
|
// A native `transition` lives inside the state machine that owns it, which
|
|
@@ -710,13 +748,30 @@ function extractActionDefinition(actionDef, filePath, packageName, config, eleme
|
|
|
710
748
|
const attributes = extractAttributes(bodyMembers);
|
|
711
749
|
const doc = extractDocComment(bodyMembers);
|
|
712
750
|
const behaviorKind = actionDef.behaviorKind ?? 'action';
|
|
751
|
+
// `action def AcquireSensorData :> FunctionalAction` defines a
|
|
752
|
+
// FunctionalAction. Stamping it `ActionDefinition` named the SysML
|
|
753
|
+
// METACLASS as though it were a MEMO kind — the ontology declares no such
|
|
754
|
+
// kind, so every project action def landed in the explorer's "not in
|
|
755
|
+
// ontology" bucket, and a validator had to look it up by that literal.
|
|
756
|
+
// The metaclass is what `construct` and `isDefinition` say; the kind is
|
|
757
|
+
// what the definition specializes.
|
|
758
|
+
const superType = actionDef.specialization?.superType;
|
|
759
|
+
const resolved = superType ? resolveKindDef(superType, config, registries) : undefined;
|
|
760
|
+
const ontologyKind = resolved?.kindDef && resolved.kindDef.layer && resolved.kindDef.layer !== 'unknown'
|
|
761
|
+
? resolved
|
|
762
|
+
: undefined;
|
|
713
763
|
const element = {
|
|
714
764
|
id,
|
|
715
765
|
name: id,
|
|
716
|
-
|
|
717
|
-
|
|
766
|
+
// Only where nothing resolves does the metaclass stand in, and there it
|
|
767
|
+
// is the truthful answer: this is a SysML ActionDefinition and nothing
|
|
768
|
+
// more is known about it.
|
|
769
|
+
kind: ontologyKind?.resolvedKind
|
|
770
|
+
?? (behaviorKind === 'operator' ? 'OperatorDefinition'
|
|
771
|
+
: behaviorKind === 'function' ? 'FunctionDefinition' : 'ActionDefinition'),
|
|
718
772
|
construct: 'action',
|
|
719
|
-
|
|
773
|
+
isDefinition: true,
|
|
774
|
+
layer: ontologyKind?.kindDef?.layer ?? 'behavior',
|
|
720
775
|
file: filePath,
|
|
721
776
|
package: packageName || undefined,
|
|
722
777
|
attributes: { ...attributes, behaviorKind },
|
|
@@ -802,6 +857,7 @@ function extractItemDefinition(itemDef, filePath, packageName, config, elements,
|
|
|
802
857
|
name: id,
|
|
803
858
|
kind: kindDef?.layer && kindDef.layer !== 'unknown' ? resolvedKind : 'ItemDefinition',
|
|
804
859
|
construct: 'item',
|
|
860
|
+
isDefinition: true,
|
|
805
861
|
layer: kindDef?.layer && kindDef.layer !== 'unknown' ? kindDef.layer : 'behavior',
|
|
806
862
|
file: filePath,
|
|
807
863
|
package: packageName || undefined,
|
|
@@ -826,6 +882,72 @@ function extractItemDefinition(itemDef, filePath, packageName, config, elements,
|
|
|
826
882
|
* or a state. So the native form was the only correct one, and it was the one
|
|
827
883
|
* that did not work.
|
|
828
884
|
*/
|
|
885
|
+
/**
|
|
886
|
+
* The SysML construct each definition declares, in the spelling its usages
|
|
887
|
+
* carry — a `part def` and a `part` are the same construct, one defining and
|
|
888
|
+
* one using.
|
|
889
|
+
*/
|
|
890
|
+
const DEFINITION_CONSTRUCTS = {
|
|
891
|
+
PartDefinition: 'part',
|
|
892
|
+
PortDefinition: 'port',
|
|
893
|
+
InterfaceDefinition: 'interface',
|
|
894
|
+
ConnectionDefinition: 'connection',
|
|
895
|
+
RequirementDefinition: 'requirement',
|
|
896
|
+
VerificationDefinition: 'verification',
|
|
897
|
+
StateDefinition: 'state',
|
|
898
|
+
AttributeDefinition: 'attribute',
|
|
899
|
+
EnumDefinition: 'enumeration',
|
|
900
|
+
};
|
|
901
|
+
/**
|
|
902
|
+
* A definition the PROJECT declares, as a model element.
|
|
903
|
+
*
|
|
904
|
+
* `part def Pump :> LogicalComponent` defines a Pump. It was registered as a
|
|
905
|
+
* kind and nothing else, so no view could select it and no BDD or IBD could
|
|
906
|
+
* draw it — which is most of what a definition is for. It is an element now,
|
|
907
|
+
* and its usages read beneath it: a usage's `kind` is the definition's name,
|
|
908
|
+
* which is the link every explorer already follows.
|
|
909
|
+
*
|
|
910
|
+
* The kind it carries is the ONTOLOGY kind it specializes, so a view, a
|
|
911
|
+
* viewpoint filter and a palette that admit `LogicalComponent` admit the
|
|
912
|
+
* definition of one without being edited. `isDefinition` is what tells the two
|
|
913
|
+
* apart afterwards — a definition is a type, not an instance of one, and
|
|
914
|
+
* anything counting instances must say so.
|
|
915
|
+
*
|
|
916
|
+
* This is only ever project content: the ontology's own definitions are read
|
|
917
|
+
* by the kind registry and are never parsed into the model.
|
|
918
|
+
*/
|
|
919
|
+
function extractDefinitionElement(definition, construct, filePath, packageName, config, elements, registry, registries) {
|
|
920
|
+
const id = definition.name;
|
|
921
|
+
if (!id || elements.has(id))
|
|
922
|
+
return;
|
|
923
|
+
// A `library package` is how a project writes ontology-style content: its
|
|
924
|
+
// definitions are types for other models to use, not this model's content.
|
|
925
|
+
// The user's distinction exactly — model definitions become elements,
|
|
926
|
+
// library ones stay kinds, which is what they already were.
|
|
927
|
+
if (registry.isLibraryPackage(packageName))
|
|
928
|
+
return;
|
|
929
|
+
const superType = definition.specialization?.superType;
|
|
930
|
+
const { kindDef, resolvedKind } = superType
|
|
931
|
+
? resolveKindDef(superType, config, registries)
|
|
932
|
+
: { kindDef: undefined, resolvedKind: undefined };
|
|
933
|
+
const attributes = extractAttributes(definition.body ?? []);
|
|
934
|
+
const doc = extractDocComment(definition.body ?? []);
|
|
935
|
+
elements.set(id, {
|
|
936
|
+
id,
|
|
937
|
+
name: attributes['name'] || attributes['title'] || id,
|
|
938
|
+
// A definition that specializes nothing the ontology knows is still a
|
|
939
|
+
// definition; it says so rather than borrowing a kind it has not got.
|
|
940
|
+
kind: resolvedKind ?? `${construct.charAt(0).toUpperCase()}${construct.slice(1)}Definition`,
|
|
941
|
+
construct,
|
|
942
|
+
layer: kindDef?.layer || 'unknown',
|
|
943
|
+
file: filePath,
|
|
944
|
+
package: packageName || undefined,
|
|
945
|
+
attributes,
|
|
946
|
+
doc,
|
|
947
|
+
isDefinition: true,
|
|
948
|
+
});
|
|
949
|
+
registry.registerElement(id, packageName);
|
|
950
|
+
}
|
|
829
951
|
function extractDefinitionPorts(def, filePath, packageName, config, elements, registry, registries) {
|
|
830
952
|
const ownerId = def.name;
|
|
831
953
|
const body = def.body || [];
|
|
@@ -849,8 +971,8 @@ function extractDefinitionPorts(def, filePath, packageName, config, elements, re
|
|
|
849
971
|
}
|
|
850
972
|
}
|
|
851
973
|
/**
|
|
852
|
-
* Walk a
|
|
853
|
-
* `owner` on each child.
|
|
974
|
+
* Walk a usage's body to extract nested parts AND items as owned elements,
|
|
975
|
+
* setting `owner` on each child.
|
|
854
976
|
*
|
|
855
977
|
* `extractUsage` flattens the `PartMember` shapes that are view metadata — a
|
|
856
978
|
* reference binding (`part viewpoint :> vp;`) and an untyped body
|
|
@@ -864,20 +986,117 @@ function extractDefinitionPorts(def, filePath, packageName, config, elements, re
|
|
|
864
986
|
function extractNestedParts(owner, filePath, packageName, config, elements, registry, registries) {
|
|
865
987
|
const ownerId = owner.name;
|
|
866
988
|
for (const member of owner.body || []) {
|
|
867
|
-
if (member.$type !== 'PartMember')
|
|
868
|
-
continue;
|
|
869
989
|
const pm = member;
|
|
870
|
-
|
|
990
|
+
const isPart = pm.$type === 'PartMember';
|
|
991
|
+
// A nested ITEM is containment too — `item iec62304 { item clause4 …}`
|
|
992
|
+
// is how every standard declares its clauses. Without this branch the
|
|
993
|
+
// child parses cleanly in both parsers and never becomes an element at
|
|
994
|
+
// all: not a missing edge, a missing element. That is the
|
|
995
|
+
// BindingUsage / ExposeMember / untyped-flow shape again.
|
|
996
|
+
//
|
|
997
|
+
// A DIRECTED item is excluded: `in item payload : BubbleSignal;` is a
|
|
998
|
+
// port's payload parameter, not something the port contains. The same
|
|
999
|
+
// `direction` test already separates the two at the action-parameter
|
|
1000
|
+
// and flow sites.
|
|
1001
|
+
const isItem = pm.$type === 'ItemUsage' && !pm.direction;
|
|
1002
|
+
if (!isPart && !isItem)
|
|
1003
|
+
continue;
|
|
1004
|
+
// Only a TYPED nested usage is containment. Untyped bodies (view
|
|
871
1005
|
// selectionQuery) and reference bindings are view metadata, flattened
|
|
872
1006
|
// to attributes in extractUsage — skip them here or a view's metadata
|
|
873
1007
|
// becomes a phantom element and its selection query is lost.
|
|
874
1008
|
if (!pm.type || pm.boundRef || !pm.name)
|
|
875
1009
|
continue;
|
|
876
|
-
extractUsage({ name: pm.name, type: pm.type, body: pm.body }, 'part', filePath, packageName, config, elements, registry, registries);
|
|
1010
|
+
extractUsage({ name: pm.name, type: pm.type, body: pm.body }, isPart ? 'part' : 'item', filePath, packageName, config, elements, registry, registries);
|
|
877
1011
|
const childEl = elements.get(pm.name);
|
|
878
1012
|
if (childEl)
|
|
879
1013
|
childEl.owner = ownerId;
|
|
880
1014
|
}
|
|
1015
|
+
extractNestedPackages(owner, filePath, packageName, config, elements, registry, registries);
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* A `package` nested inside a usage body — `part scrHome : UIElement { package
|
|
1019
|
+
* grpHeader { part btnBack : UIElement; } }`.
|
|
1020
|
+
*
|
|
1021
|
+
* A package GROUPS; it does not decompose. So the group becomes an element (the
|
|
1022
|
+
* explorer needs a node to render, and the user asked to create these to
|
|
1023
|
+
* organise a screen or a leaf of the model), but the things inside it stay
|
|
1024
|
+
* contained by the OWNER — putting them under the package would assert a part
|
|
1025
|
+
* decomposition the author did not write, and would make the containment
|
|
1026
|
+
* family rules see a package "containing" software.
|
|
1027
|
+
*
|
|
1028
|
+
* Without this the package and everything in it parses in both parsers and
|
|
1029
|
+
* never becomes an element: the BindingUsage / ExposeMember / nested-item shape
|
|
1030
|
+
* for the fourth time.
|
|
1031
|
+
*/
|
|
1032
|
+
function extractNestedPackages(owner, filePath, packageName, config, elements, registry, registries) {
|
|
1033
|
+
for (const member of owner.body || []) {
|
|
1034
|
+
const pm = member;
|
|
1035
|
+
if (pm.$type !== 'PackageDeclaration' || !pm.name)
|
|
1036
|
+
continue;
|
|
1037
|
+
elements.set(pm.name, {
|
|
1038
|
+
id: pm.name,
|
|
1039
|
+
name: pm.name,
|
|
1040
|
+
kind: ELEMENT_PACKAGE_KIND,
|
|
1041
|
+
construct: 'package',
|
|
1042
|
+
layer: elements.get(owner.name)?.layer ?? 'unknown',
|
|
1043
|
+
file: filePath,
|
|
1044
|
+
package: packageName,
|
|
1045
|
+
attributes: {},
|
|
1046
|
+
owner: owner.name,
|
|
1047
|
+
});
|
|
1048
|
+
for (const grouped of pm.members ?? []) {
|
|
1049
|
+
const gm = grouped;
|
|
1050
|
+
// `part btnBack : UIElement;` parses as a PartMember and
|
|
1051
|
+
// `part btnBack : UIElement { … }` as a PartUsage — the same
|
|
1052
|
+
// declaration with and without a body. Accepting only the first
|
|
1053
|
+
// dropped every grouped element that declares anything, which is
|
|
1054
|
+
// most of them.
|
|
1055
|
+
const isPart = gm.$type === 'PartMember' || gm.$type === 'PartUsage';
|
|
1056
|
+
const isItem = gm.$type === 'ItemUsage' && !gm.direction;
|
|
1057
|
+
if (!isPart && !isItem)
|
|
1058
|
+
continue;
|
|
1059
|
+
if (!gm.type || gm.boundRef || !gm.name)
|
|
1060
|
+
continue;
|
|
1061
|
+
extractUsage({ name: gm.name, type: gm.type, body: gm.body }, isPart ? 'part' : 'item', filePath, packageName, config, elements, registry, registries);
|
|
1062
|
+
const childEl = elements.get(gm.name);
|
|
1063
|
+
if (childEl) {
|
|
1064
|
+
// Contained by the OWNER; the package only labels the grouping.
|
|
1065
|
+
childEl.owner = owner.name;
|
|
1066
|
+
childEl.attributes['elementPackage'] = pm.name;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
/** The kind a nested grouping package becomes, distinct from a namespace package. */
|
|
1072
|
+
export const ELEMENT_PACKAGE_KIND = 'ElementPackage';
|
|
1073
|
+
/**
|
|
1074
|
+
* A `viewpoint` usage, and any viewpoint declared inside it.
|
|
1075
|
+
*
|
|
1076
|
+
* A viewpoint nests for system-of-systems modelling: the viewpoint that frames
|
|
1077
|
+
* the whole system of systems declares one viewpoint per constituent system,
|
|
1078
|
+
* which is how ISO 42010 composes a description of a description. Native SysML
|
|
1079
|
+
* already permits it (`ViewpointUsage` is a `UsageBodyMember`) and the grammar
|
|
1080
|
+
* already parsed it — nothing extracted it, so a nested viewpoint became no
|
|
1081
|
+
* element at all and every consumer saw one flat list.
|
|
1082
|
+
*
|
|
1083
|
+
* The nested viewpoint carries its parent in `owner`, the same way a nested
|
|
1084
|
+
* part does. Unlike a part, no `composes` edge is synthesized: a viewpoint is
|
|
1085
|
+
* not a `part` construct, and framing is not containment of hardware.
|
|
1086
|
+
*/
|
|
1087
|
+
function extractViewpointUsage(usage, filePath, packageName, config, elements, registry, registries, ownerId) {
|
|
1088
|
+
if (!usage.name)
|
|
1089
|
+
return;
|
|
1090
|
+
extractUsage({ name: usage.name, type: usage.type, body: usage.body }, 'viewpoint', filePath, packageName, config, elements, registry, registries);
|
|
1091
|
+
const element = elements.get(usage.name);
|
|
1092
|
+
if (element && ownerId)
|
|
1093
|
+
element.owner = ownerId;
|
|
1094
|
+
for (const member of usage.body || []) {
|
|
1095
|
+
const nested = member;
|
|
1096
|
+
if (nested.$type !== 'ViewpointUsage')
|
|
1097
|
+
continue;
|
|
1098
|
+
extractViewpointUsage(nested, filePath, packageName, config, elements, registry, registries, usage.name);
|
|
1099
|
+
}
|
|
881
1100
|
}
|
|
882
1101
|
/**
|
|
883
1102
|
* Extract an ActionUsage, including nested actions, flows, and successions.
|
|
@@ -1001,14 +1220,18 @@ function resolveConnection(conn, filePath, packageName, config, elements, relati
|
|
|
1001
1220
|
return;
|
|
1002
1221
|
// A named connection usage carries its own stable ID; anonymous ones fall
|
|
1003
1222
|
// back to a positional counter that shifts whenever the file changes.
|
|
1004
|
-
|
|
1223
|
+
// `extractAttributes` lifts metadata applications itself now, so the
|
|
1224
|
+
// connection path no longer needs its own second pass.
|
|
1225
|
+
const attributes = extractAttributes(conn.body);
|
|
1005
1226
|
const rel = {
|
|
1006
1227
|
id: conn.name || `rel-${++relationshipCounter}`,
|
|
1007
1228
|
type: normalizedType,
|
|
1008
1229
|
sourceId,
|
|
1009
|
-
|
|
1230
|
+
// `connect a to b` omits the end names; the ends are then the inherited
|
|
1231
|
+
// `source`/`target` participants. csv-io already defaults the same way.
|
|
1232
|
+
sourceEnd: conn.source.endName ?? 'source',
|
|
1010
1233
|
targetId,
|
|
1011
|
-
targetEnd: conn.target.endName,
|
|
1234
|
+
targetEnd: conn.target.endName ?? 'target',
|
|
1012
1235
|
file: filePath,
|
|
1013
1236
|
sourceRange: conn.$cstNode
|
|
1014
1237
|
? { offset: conn.$cstNode.offset, length: conn.$cstNode.length }
|
|
@@ -1046,7 +1269,15 @@ function resolveConnection(conn, filePath, packageName, config, elements, relati
|
|
|
1046
1269
|
* mismatch on whichever edge happened to carry a different item from its
|
|
1047
1270
|
* parent's.
|
|
1048
1271
|
*
|
|
1049
|
-
* Walk the path through
|
|
1272
|
+
* Walk the path through the parent link and take the deepest real element.
|
|
1273
|
+
*
|
|
1274
|
+
* A nested part or port records its parent in `owner`; a nested ACTION records
|
|
1275
|
+
* it in `parentAction`, because the two are set by separate extraction paths.
|
|
1276
|
+
* Reading only `owner` broke the walk at the first hop of every function path:
|
|
1277
|
+
* `flow from fnDeliverTherapy.fnMeterFluid.fnMeasureDelivered to …` became a
|
|
1278
|
+
* self-loop on `fnDeliverTherapy`, which is a plausible-looking edge pointing
|
|
1279
|
+
* at the wrong function — the exact failure the comment above describes, one
|
|
1280
|
+
* field away from where it was fixed for ports.
|
|
1050
1281
|
*/
|
|
1051
1282
|
function resolveEndpoint(ref, elements) {
|
|
1052
1283
|
const parts = ref.split('.');
|
|
@@ -1054,7 +1285,7 @@ function resolveEndpoint(ref, elements) {
|
|
|
1054
1285
|
let i = 1;
|
|
1055
1286
|
while (i < parts.length) {
|
|
1056
1287
|
const child = elements.get(parts[i]);
|
|
1057
|
-
if (!child || child.owner !== id)
|
|
1288
|
+
if (!child || (child.owner !== id && child.parentAction !== id))
|
|
1058
1289
|
break;
|
|
1059
1290
|
id = parts[i];
|
|
1060
1291
|
i++;
|
|
@@ -1125,7 +1356,13 @@ function resolveFlowConnection(flow, filePath, packageName, parentActionId, rela
|
|
|
1125
1356
|
}
|
|
1126
1357
|
/**
|
|
1127
1358
|
* Resolve a succession usage into pairs of MemoRelationships.
|
|
1128
|
-
* "first
|
|
1359
|
+
* "first A then B" → (A→B).
|
|
1360
|
+
*
|
|
1361
|
+
* The grammar admits exactly one `then` per statement, matching KerML
|
|
1362
|
+
* `SuccessionDeclaration` (:845), so `steps` is always a pair here. The loop
|
|
1363
|
+
* below is still written over the whole array: it costs nothing, and it keeps
|
|
1364
|
+
* this function correct for the chained input the deferred-succession path at
|
|
1365
|
+
* the call site synthesizes.
|
|
1129
1366
|
*/
|
|
1130
1367
|
function resolveSuccession(succession, filePath, packageName, parentActionId, relationships, allElementIds) {
|
|
1131
1368
|
const steps = succession.steps || [];
|
|
@@ -1408,8 +1645,9 @@ function resolveActorParticipation(actor, filePath, relationships, registry, all
|
|
|
1408
1645
|
* name/doc, not in a relationship type.
|
|
1409
1646
|
*/
|
|
1410
1647
|
function resolveDependency(dependency, filePath, relationships, registry, allElementIds) {
|
|
1411
|
-
const
|
|
1412
|
-
.
|
|
1648
|
+
const prefixes = (dependency.prefixMetadata ?? [])
|
|
1649
|
+
.map(annotation => annotation.type?.split('::').pop());
|
|
1650
|
+
const isRefinement = prefixes.includes(REFINEMENT_METADATA);
|
|
1413
1651
|
const { packageName } = nativeUsageContext(dependency);
|
|
1414
1652
|
for (const client of dependency.clients ?? []) {
|
|
1415
1653
|
const sourceId = resolveEndpointId(client, packageName, registry, allElementIds);
|
|
@@ -1547,33 +1785,56 @@ function extractAttributes(body) {
|
|
|
1547
1785
|
}
|
|
1548
1786
|
}
|
|
1549
1787
|
}
|
|
1788
|
+
// Metadata applications last, so an annotation overrides a directly declared
|
|
1789
|
+
// attribute of the same name. That order is what R10-S5 established for
|
|
1790
|
+
// connections and it is preserved here rather than re-litigated: while the
|
|
1791
|
+
// identification core is migrating off the base defs, an element may carry
|
|
1792
|
+
// both spellings, and the annotation is the newer statement of intent.
|
|
1793
|
+
Object.assign(attrs, extractMetadataApplications(body));
|
|
1550
1794
|
return attrs;
|
|
1551
1795
|
}
|
|
1552
1796
|
/**
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
* `
|
|
1797
|
+
* Standard metadata whose field name differs from the flat key MEMO reads.
|
|
1798
|
+
* Everything else lifts under its own field name, which is what makes one
|
|
1799
|
+
* `metadata def` usable as a carrier for any element.
|
|
1800
|
+
*/
|
|
1801
|
+
const METADATA_KEY_ALIASES = {
|
|
1802
|
+
Rationale: { text: 'rationale' },
|
|
1803
|
+
};
|
|
1804
|
+
/**
|
|
1805
|
+
* Lift every `@Foo { ... }` metadata application in a body into the flat
|
|
1806
|
+
* attribute map, under each field's own name.
|
|
1807
|
+
*
|
|
1808
|
+
* `extractAttributes`'s member walk covers `AttributeMember`/`RefMember`/
|
|
1809
|
+
* `MetadataFeatureMember`; a `MetadataApplication` is a different node type and
|
|
1810
|
+
* is invisible to it, so without this an annotation parses cleanly in both
|
|
1811
|
+
* parsers and then silently vanishes from the model — the BindingUsage /
|
|
1812
|
+
* ExposeMember / untyped-flow shape this codebase has already hit three times.
|
|
1556
1813
|
*
|
|
1557
|
-
*
|
|
1558
|
-
* `
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1561
|
-
*
|
|
1814
|
+
* This started (R10-S5, 2026-08-13) as a connection-only reader hardcoded to
|
|
1815
|
+
* `Rationale.text` and `StatusInfo.status`, because those were the two base-type
|
|
1816
|
+
* attributes being deleted at the time. It is general now for one reason: a
|
|
1817
|
+
* `metadata def` is the only carrier in SysML v2 that attaches to ANY metaclass,
|
|
1818
|
+
* so it is how MEMO can hold common fields (`id`, `uuid`, `sourceReference`, …)
|
|
1819
|
+
* without re-declaring them per metaclass. A carrier the builder cannot read is
|
|
1820
|
+
* not a carrier, so the reader has to be as general as the mechanism.
|
|
1821
|
+
*
|
|
1822
|
+
* Fields lift under their own names; `METADATA_KEY_ALIASES` covers the standard
|
|
1823
|
+
* definitions whose field name differs from the key MEMO reads.
|
|
1562
1824
|
*/
|
|
1563
|
-
function
|
|
1825
|
+
function extractMetadataApplications(body) {
|
|
1564
1826
|
if (!body)
|
|
1565
1827
|
return {};
|
|
1566
1828
|
const attrs = {};
|
|
1567
1829
|
for (const member of body) {
|
|
1568
1830
|
if (member.$type !== 'MetadataApplication')
|
|
1569
1831
|
continue;
|
|
1570
|
-
const metadataType = member.type?.split('::').pop();
|
|
1571
|
-
const
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
attrs.status = fields.status;
|
|
1832
|
+
const metadataType = member.type?.split('::').pop() ?? '';
|
|
1833
|
+
const aliases = METADATA_KEY_ALIASES[metadataType] ?? {};
|
|
1834
|
+
for (const [field, value] of Object.entries(extractAttributes(member.body))) {
|
|
1835
|
+
if (value === '')
|
|
1836
|
+
continue;
|
|
1837
|
+
attrs[aliases[field] ?? field] = value;
|
|
1577
1838
|
}
|
|
1578
1839
|
}
|
|
1579
1840
|
return attrs;
|