@memoarchitect/tools 0.6.5 → 0.6.7
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 +42 -10
- package/package.json +4 -2
- package/packages/tools/lib/analysis-starters.d.ts +4 -0
- package/packages/tools/lib/analysis-starters.d.ts.map +1 -0
- package/packages/tools/lib/analysis-starters.js +425 -0
- package/packages/tools/lib/analysis-starters.js.map +1 -0
- package/packages/tools/lib/commands/ask.d.ts.map +1 -1
- package/packages/tools/lib/commands/ask.js +1 -18
- package/packages/tools/lib/commands/ask.js.map +1 -1
- package/packages/tools/lib/commands/check.d.ts.map +1 -1
- package/packages/tools/lib/commands/check.js +2 -19
- package/packages/tools/lib/commands/check.js.map +1 -1
- package/packages/tools/lib/commands/dev.d.ts +2 -0
- package/packages/tools/lib/commands/dev.d.ts.map +1 -1
- package/packages/tools/lib/commands/dev.js +17 -27
- package/packages/tools/lib/commands/dev.js.map +1 -1
- package/packages/tools/lib/commands/dhf-draft.d.ts.map +1 -1
- package/packages/tools/lib/commands/dhf-draft.js +1 -17
- package/packages/tools/lib/commands/dhf-draft.js.map +1 -1
- package/packages/tools/lib/commands/dhf.d.ts.map +1 -1
- package/packages/tools/lib/commands/dhf.js +2 -18
- package/packages/tools/lib/commands/dhf.js.map +1 -1
- package/packages/tools/lib/commands/export.d.ts.map +1 -1
- package/packages/tools/lib/commands/export.js +2 -19
- package/packages/tools/lib/commands/export.js.map +1 -1
- package/packages/tools/lib/commands/import.d.ts.map +1 -1
- package/packages/tools/lib/commands/import.js +2 -19
- package/packages/tools/lib/commands/import.js.map +1 -1
- package/packages/tools/lib/commands/init.d.ts.map +1 -1
- package/packages/tools/lib/commands/init.js +3 -0
- package/packages/tools/lib/commands/init.js.map +1 -1
- package/packages/tools/lib/commands/pack.d.ts.map +1 -1
- package/packages/tools/lib/commands/pack.js +5 -2
- package/packages/tools/lib/commands/pack.js.map +1 -1
- package/packages/tools/lib/commands/plugin.d.ts.map +1 -1
- package/packages/tools/lib/commands/plugin.js +2 -17
- package/packages/tools/lib/commands/plugin.js.map +1 -1
- package/packages/tools/lib/commands/roundtrip.d.ts.map +1 -1
- package/packages/tools/lib/commands/roundtrip.js +2 -19
- package/packages/tools/lib/commands/roundtrip.js.map +1 -1
- package/packages/tools/lib/commands/rules.d.ts.map +1 -1
- package/packages/tools/lib/commands/rules.js +1 -19
- 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 +2 -22
- package/packages/tools/lib/commands/validate.js.map +1 -1
- package/packages/tools/lib/language/generated/ast.js +1 -1
- 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 +7 -0
- package/packages/tools/lib/language/generated/grammar.js.map +1 -1
- package/packages/tools/lib/mcp/model-loader.d.ts +2 -1
- package/packages/tools/lib/mcp/model-loader.d.ts.map +1 -1
- package/packages/tools/lib/mcp/model-loader.js +7 -22
- package/packages/tools/lib/mcp/model-loader.js.map +1 -1
- package/packages/tools/lib/model/builder.d.ts.map +1 -1
- package/packages/tools/lib/model/builder.js +33 -6
- package/packages/tools/lib/model/builder.js.map +1 -1
- package/packages/tools/lib/model/kind-registry.d.ts +14 -0
- package/packages/tools/lib/model/kind-registry.d.ts.map +1 -1
- package/packages/tools/lib/model/kind-registry.js +66 -0
- package/packages/tools/lib/model/kind-registry.js.map +1 -1
- package/packages/tools/lib/model/package-registry.d.ts.map +1 -1
- package/packages/tools/lib/model/package-registry.js +11 -0
- package/packages/tools/lib/model/package-registry.js.map +1 -1
- package/packages/tools/lib/model/relationship-legality.d.ts +2 -0
- package/packages/tools/lib/model/relationship-legality.d.ts.map +1 -1
- package/packages/tools/lib/model/relationship-legality.js.map +1 -1
- package/packages/tools/lib/model/semantic.d.ts +21 -0
- package/packages/tools/lib/model/semantic.d.ts.map +1 -1
- package/packages/tools/lib/model/semantic.js.map +1 -1
- package/packages/tools/lib/model/short-id.d.ts +8 -7
- package/packages/tools/lib/model/short-id.d.ts.map +1 -1
- package/packages/tools/lib/model/short-id.js +24 -17
- package/packages/tools/lib/model/short-id.js.map +1 -1
- package/packages/tools/lib/model/sysml-files.d.ts +12 -0
- package/packages/tools/lib/model/sysml-files.d.ts.map +1 -0
- package/packages/tools/lib/model/sysml-files.js +71 -0
- package/packages/tools/lib/model/sysml-files.js.map +1 -0
- package/packages/tools/lib/model/view-deriver.d.ts.map +1 -1
- package/packages/tools/lib/model/view-deriver.js +31 -0
- 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 +1 -19
- package/packages/tools/lib/operations/project-snapshot.js.map +1 -1
- package/packages/tools/lib/protocol/messages.d.ts +13 -0
- package/packages/tools/lib/protocol/messages.d.ts.map +1 -1
- package/packages/tools/lib/server/dev-server.d.ts +8 -0
- package/packages/tools/lib/server/dev-server.d.ts.map +1 -1
- package/packages/tools/lib/server/dev-server.js +18 -3
- package/packages/tools/lib/server/dev-server.js.map +1 -1
- package/packages/tools/lib/server/relationship-writer.d.ts +1 -1
- package/packages/tools/lib/server/relationship-writer.d.ts.map +1 -1
- package/packages/tools/lib/server/relationship-writer.js +8 -4
- package/packages/tools/lib/server/relationship-writer.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 +2 -0
- package/packages/tools/lib/server/view-layout-store.js.map +1 -1
- package/packages/tools/lib/validator/architecture-validator.d.ts +10 -0
- package/packages/tools/lib/validator/architecture-validator.d.ts.map +1 -0
- package/packages/tools/lib/validator/architecture-validator.js +77 -0
- package/packages/tools/lib/validator/architecture-validator.js.map +1 -0
- 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 +3 -1
package/README.md
CHANGED
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<code>memo-tools 0.6.
|
|
12
|
+
<code>memo-tools 0.6.6</code> · MIT · SysML v2 · ISO 14971 · IEC 62304 · ISO/IEC/IEEE 42010
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="https://memoarchitect.com/memo-tools.html">Product website</a> ·
|
|
17
|
+
<a href="https://memoarchitect.com/memo-tools/">Documentation</a> ·
|
|
18
|
+
<a href="https://github.com/memoarchitect/memo-tools">GitHub repository</a> ·
|
|
19
|
+
<a href="https://www.npmjs.com/package/@memoarchitect/tools">npm package</a>
|
|
13
20
|
</p>
|
|
14
21
|
|
|
15
22
|
---
|
|
@@ -21,9 +28,14 @@
|
|
|
21
28
|
## Version compatibility
|
|
22
29
|
|
|
23
30
|
`memo`, `memo-tools`, and `memo-architect` share a `MAJOR.MINOR` compatibility
|
|
24
|
-
line. Any `0.
|
|
31
|
+
line. Any `0.6.x` release is intended to work with the other `0.6.x` products;
|
|
25
32
|
patch versions may advance independently for fixes and additive changes.
|
|
26
33
|
|
|
34
|
+
`VERSION` is the sole release-version source for this repository. Run
|
|
35
|
+
`pnpm version:sync` after changing it; CI rejects unsynchronized generated
|
|
36
|
+
version fields and automatically creates the matching `v<version>` tag when
|
|
37
|
+
the change reaches `main`.
|
|
38
|
+
|
|
27
39
|
## The meMO stack
|
|
28
40
|
|
|
29
41
|
meMO is a four-layer stack — adopt what you need
|
|
@@ -31,10 +43,10 @@ meMO is a four-layer stack — adopt what you need
|
|
|
31
43
|
|
|
32
44
|
| Layer | What | Where |
|
|
33
45
|
|---|---|---|
|
|
34
|
-
| 01 Ontology | Typed SysML v2 elements
|
|
35
|
-
| 02 Methodology | Profiles, viewpoints, rules, workflow gates | [memoarchitect/memo](https://github.com/memoarchitect/memo) |
|
|
36
|
-
| **03 Tools** | **Model engine + `memo` CLI — this repo** | memoarchitect/memo-tools |
|
|
37
|
-
| 04 Architect | Web workbench over the same model | [memoarchitect/memo-architect](https://github.com/memoarchitect/memo-architect) |
|
|
46
|
+
| 01 Ontology | Typed SysML v2 elements and architecture definitions | [Docs](https://memoarchitect.com/memo/) · [Repository](https://github.com/memoarchitect/memo) |
|
|
47
|
+
| 02 Methodology | Profiles, viewpoints, rules, workflow gates | [Docs](https://memoarchitect.com/memo/) · [Repository](https://github.com/memoarchitect/memo) |
|
|
48
|
+
| **03 Tools** | **Model engine + `memo` CLI — this repo** | [Docs](https://memoarchitect.com/memo-tools/) · [Repository](https://github.com/memoarchitect/memo-tools) |
|
|
49
|
+
| 04 Architect | Web workbench over the same model | [Docs](https://memoarchitect.com/memo-architect/) · [Repository](https://github.com/memoarchitect/memo-architect) |
|
|
38
50
|
|
|
39
51
|
## Supported tools
|
|
40
52
|
|
|
@@ -77,11 +89,31 @@ memo rules coverage
|
|
|
77
89
|
|
|
78
90
|
Run `memo --help` or `memo <command> --help` for the complete command surface.
|
|
79
91
|
|
|
92
|
+
### Jupyter analysis samples
|
|
93
|
+
|
|
94
|
+
Every `memo init` project includes `analysis/Samples/` with seven portable
|
|
95
|
+
Syside notebooks. They cover model overview and quality, architecture hotspots,
|
|
96
|
+
change-impact exploration, semantic charts, an ownership graph, and an HTML/CSV
|
|
97
|
+
inventory table. The notebooks discover either `model/` or `src/` automatically,
|
|
98
|
+
so the same samples work with default templates and worked examples.
|
|
99
|
+
|
|
100
|
+
Run them in a licensed Syside environment:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python -m venv analysis/.venv
|
|
104
|
+
source analysis/.venv/bin/activate
|
|
105
|
+
python -m pip install jupyterlab syside
|
|
106
|
+
cd analysis
|
|
107
|
+
jupyter lab --port 8888
|
|
108
|
+
```
|
|
109
|
+
|
|
80
110
|
## Documentation
|
|
81
111
|
|
|
82
|
-
The [
|
|
112
|
+
The [published documentation](https://memoarchitect.com/memo-tools/)
|
|
113
|
+
starts with installation and a
|
|
83
114
|
first useful workflow, then covers validation, import, export, CI, configuration,
|
|
84
|
-
and the library API.
|
|
115
|
+
and the library API. Its source starts at [`docs/index.md`](docs/index.md). Build
|
|
116
|
+
it locally with:
|
|
85
117
|
|
|
86
118
|
```bash
|
|
87
119
|
python3 -m pip install "mkdocs>=1.6" "mkdocs-material>=9.5" "pymdown-extensions>=10.0"
|
|
@@ -125,7 +157,7 @@ React does not import core internals directly.
|
|
|
125
157
|
import { buildMemoModel } from '@memoarchitect/tools';
|
|
126
158
|
```
|
|
127
159
|
|
|
128
|
-
The API is pre-stable. Pin an exact `0.
|
|
160
|
+
The API is pre-stable. Pin an exact `0.6.x` patch when embedding it directly.
|
|
129
161
|
|
|
130
162
|
### Maintainer checks
|
|
131
163
|
|
|
@@ -154,7 +186,7 @@ dependency to the sibling `memo` checkout for iterative development.
|
|
|
154
186
|
|
|
155
187
|
## Current repository relationship
|
|
156
188
|
|
|
157
|
-
- A standalone clone installs `@memoarchitect/ontology@0.6.
|
|
189
|
+
- A standalone clone installs `@memoarchitect/ontology@0.6.3` from npm.
|
|
158
190
|
- The Tools repository contains no ontology git submodule.
|
|
159
191
|
- The private `memo-meta` workspace keeps `memo` and `memo-tools` as sibling
|
|
160
192
|
submodules and applies local pnpm overrides for cross-repository work.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memoarchitect/tools",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "MEMO tools — SysML v2 parser, semantic model, validation, project operations, and CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./packages/tools/lib/index.js",
|
|
@@ -38,11 +38,13 @@
|
|
|
38
38
|
"example:validate": "node scripts/validate-gpca-example.mjs",
|
|
39
39
|
"docs:serve": "python3 -m mkdocs serve",
|
|
40
40
|
"docs:build": "python3 -m mkdocs build --strict",
|
|
41
|
+
"version:sync": "node scripts/sync-version.mjs",
|
|
42
|
+
"version:check": "node scripts/sync-version.mjs --check",
|
|
41
43
|
"ontology:lint": "node tools/ontology-tools/lint.mjs",
|
|
42
44
|
"ontology:compat": "node tools/ontology-tools/syside-compat.mjs"
|
|
43
45
|
},
|
|
44
46
|
"dependencies": {
|
|
45
|
-
"@memoarchitect/ontology": "0.6.
|
|
47
|
+
"@memoarchitect/ontology": "0.6.5",
|
|
46
48
|
"chalk": "^5.3.0",
|
|
47
49
|
"chokidar": "^4.0.0",
|
|
48
50
|
"commander": "^12.1.0",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis-starters.d.ts","sourceRoot":"","sources":["../src/analysis-starters.ts"],"names":[],"mappings":"AAibA,4EAA4E;AAC5E,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAShE;AAED,eAAO,MAAM,mBAAmB,UAA4C,CAAC"}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
const LOAD_MODEL = `from pathlib import Path
|
|
4
|
+
from collections import Counter, defaultdict
|
|
5
|
+
import syside
|
|
6
|
+
|
|
7
|
+
def find_sysml_root(start=Path.cwd()):
|
|
8
|
+
"""Find the nearest model/ or src/ folder containing textual SysML."""
|
|
9
|
+
for directory in (start, *start.parents):
|
|
10
|
+
for folder_name in ('model', 'src'):
|
|
11
|
+
candidate = directory / folder_name
|
|
12
|
+
if candidate.is_dir() and next(candidate.rglob('*.sysml'), None):
|
|
13
|
+
return candidate
|
|
14
|
+
raise FileNotFoundError('No model/ or src/ directory containing .sysml files was found')
|
|
15
|
+
|
|
16
|
+
SYSML_ROOT = find_sysml_root()
|
|
17
|
+
SYSML_FILES = sorted(SYSML_ROOT.rglob('*.sysml'))
|
|
18
|
+
model, diagnostics = syside.try_load_model([str(path) for path in SYSML_FILES])
|
|
19
|
+
print(f'Loaded {len(SYSML_FILES)} SysML files from {SYSML_ROOT}')`;
|
|
20
|
+
const SAMPLES_README = `# MEMO analysis samples
|
|
21
|
+
|
|
22
|
+
These notebooks are created by \`memo init\` and are designed to work with any
|
|
23
|
+
MEMO textual SysML v2 project. They locate the nearest \`model/\` or \`src/\`
|
|
24
|
+
directory automatically and query it through the typed Syside API.
|
|
25
|
+
|
|
26
|
+
## Prerequisites
|
|
27
|
+
|
|
28
|
+
Use Python 3.12 or newer with JupyterLab and Syside installed and licensed:
|
|
29
|
+
|
|
30
|
+
\`\`\`bash
|
|
31
|
+
python -m venv analysis/.venv
|
|
32
|
+
source analysis/.venv/bin/activate
|
|
33
|
+
python -m pip install jupyterlab syside
|
|
34
|
+
cd analysis
|
|
35
|
+
jupyter lab --port 8888
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
Configure the Syside license using its supported keyring or environment setup.
|
|
39
|
+
MEMO Architect's **Analysis → Jupyter Notebooks** menu opens the local server at
|
|
40
|
+
\`http://127.0.0.1:8888\`.
|
|
41
|
+
|
|
42
|
+
## Included notebooks
|
|
43
|
+
|
|
44
|
+
| Notebook | Purpose |
|
|
45
|
+
|---|---|
|
|
46
|
+
| \`01-model-overview.ipynb\` | Model size, semantic composition, and diagnostic summary. |
|
|
47
|
+
| \`02-architecture-hotspots.ipynb\` | Busy containers, deep ownership paths, and dominant types. |
|
|
48
|
+
| \`03-model-quality.ipynb\` | Diagnostics, unnamed elements, repeated names, and empty definitions. |
|
|
49
|
+
| \`04-change-impact-explorer.ipynb\` | Search an element and inspect its structural neighborhood. |
|
|
50
|
+
| \`05-model-charts.ipynb\` | Bar and donut charts rendered without extra plotting packages. |
|
|
51
|
+
| \`06-ownership-graph.ipynb\` | SVG network graph of model ownership relationships. |
|
|
52
|
+
| \`07-model-inventory-table.ipynb\` | Filterable inventory parameters, HTML table, and CSV export. |
|
|
53
|
+
|
|
54
|
+
The notebooks use \`syside.try_load_model()\`, so partial analysis remains
|
|
55
|
+
available while the project contains parser or semantic diagnostics. Review the
|
|
56
|
+
reported diagnostics before treating analysis results as release evidence.
|
|
57
|
+
`;
|
|
58
|
+
const STARTERS = [
|
|
59
|
+
{
|
|
60
|
+
filename: '01-model-overview.ipynb',
|
|
61
|
+
title: 'Model overview',
|
|
62
|
+
description: 'Visual summary of model size, semantic content, and diagnostics.',
|
|
63
|
+
codeCells: [
|
|
64
|
+
`from IPython.display import HTML, display
|
|
65
|
+
|
|
66
|
+
semantic_types = [
|
|
67
|
+
'Package', 'PartDefinition', 'PartUsage', 'RequirementDefinition',
|
|
68
|
+
'RequirementUsage', 'ActionDefinition', 'ActionUsage', 'PortDefinition',
|
|
69
|
+
'PortUsage', 'ConnectionDefinition', 'ConnectionUsage', 'StateDefinition',
|
|
70
|
+
'StateUsage', 'AttributeDefinition', 'AttributeUsage'
|
|
71
|
+
]
|
|
72
|
+
counts = {}
|
|
73
|
+
for type_name in semantic_types:
|
|
74
|
+
node_type = getattr(syside, type_name, None)
|
|
75
|
+
if node_type is not None:
|
|
76
|
+
counts[type_name] = len(list(model.elements(node_type)))
|
|
77
|
+
|
|
78
|
+
largest = max(counts.values(), default=1)
|
|
79
|
+
rows = ''.join(
|
|
80
|
+
f'<tr><td>{name}</td><td>{count:,}</td><td><div style="background:#2d8d70;height:12px;width:{max(2, count * 320 / largest):.0f}px"></div></td></tr>'
|
|
81
|
+
for name, count in sorted(counts.items(), key=lambda item: item[1], reverse=True)
|
|
82
|
+
)
|
|
83
|
+
display(HTML(f'<h3>Semantic inventory</h3><table><tr><th>Element type</th><th>Count</th><th>Relative size</th></tr>{rows}</table>'))
|
|
84
|
+
|
|
85
|
+
{
|
|
86
|
+
'SysML files': len(SYSML_FILES),
|
|
87
|
+
'Errors': len(list(diagnostics.errors)),
|
|
88
|
+
'Warnings': len(list(diagnostics.warnings)),
|
|
89
|
+
'Information': len(list(diagnostics.infos)),
|
|
90
|
+
}`,
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
filename: '02-architecture-hotspots.ipynb',
|
|
95
|
+
title: 'Architecture hotspots',
|
|
96
|
+
description: 'Find busy containers, deep ownership paths, and dominant semantic types.',
|
|
97
|
+
codeCells: [
|
|
98
|
+
`from IPython.display import display
|
|
99
|
+
|
|
100
|
+
semantic_elements = (
|
|
101
|
+
list(model.elements(syside.Usage, include_subtypes=True))
|
|
102
|
+
+ list(model.elements(syside.Definition, include_subtypes=True))
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def safe_name(element):
|
|
106
|
+
value = element.qualified_name or element.name or element.declared_name
|
|
107
|
+
return str(value) if value else '<unnamed>'
|
|
108
|
+
|
|
109
|
+
def ownership_depth(element):
|
|
110
|
+
depth, current, seen = 0, element.owner, set()
|
|
111
|
+
while current is not None and id(current) not in seen:
|
|
112
|
+
seen.add(id(current))
|
|
113
|
+
depth += 1
|
|
114
|
+
current = getattr(current, 'owner', None)
|
|
115
|
+
return depth
|
|
116
|
+
|
|
117
|
+
containers = []
|
|
118
|
+
for element in semantic_elements:
|
|
119
|
+
children = [child for child in element.owned_elements if isinstance(child, (syside.Usage, syside.Definition))]
|
|
120
|
+
if children:
|
|
121
|
+
containers.append((len(children), type(element).__name__, safe_name(element)))
|
|
122
|
+
|
|
123
|
+
print('Top containers by directly owned semantic elements')
|
|
124
|
+
display(sorted(containers, reverse=True)[:25])
|
|
125
|
+
|
|
126
|
+
print('Deepest ownership paths')
|
|
127
|
+
display(sorted(
|
|
128
|
+
((ownership_depth(item), type(item).__name__, safe_name(item)) for item in semantic_elements),
|
|
129
|
+
reverse=True,
|
|
130
|
+
)[:25])
|
|
131
|
+
|
|
132
|
+
print('Most common semantic types')
|
|
133
|
+
Counter(type(item).__name__ for item in semantic_elements).most_common(30)`,
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
filename: '03-model-quality.ipynb',
|
|
138
|
+
title: 'Model quality review',
|
|
139
|
+
description: 'Review diagnostics, unnamed elements, repeated names, and empty definitions.',
|
|
140
|
+
codeCells: [
|
|
141
|
+
`from IPython.display import display
|
|
142
|
+
|
|
143
|
+
usages = list(model.elements(syside.Usage, include_subtypes=True))
|
|
144
|
+
definitions = list(model.elements(syside.Definition, include_subtypes=True))
|
|
145
|
+
semantic_elements = usages + definitions
|
|
146
|
+
|
|
147
|
+
unnamed = [item for item in semantic_elements if not (item.name or item.declared_name)]
|
|
148
|
+
by_name = defaultdict(list)
|
|
149
|
+
for item in semantic_elements:
|
|
150
|
+
value = item.qualified_name or item.name or item.declared_name
|
|
151
|
+
name = str(value) if value else None
|
|
152
|
+
if name:
|
|
153
|
+
by_name[name].append(item)
|
|
154
|
+
duplicates = [(name, len(items), sorted({type(item).__name__ for item in items})) for name, items in by_name.items() if len(items) > 1]
|
|
155
|
+
empty_definitions = [
|
|
156
|
+
item for item in definitions
|
|
157
|
+
if not any(isinstance(child, (syside.Usage, syside.Definition)) for child in item.owned_elements)
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
display({
|
|
161
|
+
'parser/semantic errors': len(list(diagnostics.errors)),
|
|
162
|
+
'warnings': len(list(diagnostics.warnings)),
|
|
163
|
+
'unnamed usages or definitions': len(unnamed),
|
|
164
|
+
'repeated qualified names': len(duplicates),
|
|
165
|
+
'definitions with no owned usages/definitions': len(empty_definitions),
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
print('Repeated qualified names (first 30)')
|
|
169
|
+
display(sorted(duplicates, key=lambda item: item[1], reverse=True)[:30])
|
|
170
|
+
|
|
171
|
+
print('Empty definition candidates (first 30)')
|
|
172
|
+
[(type(item).__name__, item.qualified_name or item.name) for item in empty_definitions[:30]]`,
|
|
173
|
+
`print('First diagnostics to investigate')
|
|
174
|
+
for diagnostic in list(diagnostics.errors)[:20] + list(diagnostics.warnings)[:20]:
|
|
175
|
+
print(diagnostic)`,
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
filename: '04-change-impact-explorer.ipynb',
|
|
180
|
+
title: 'Change-impact explorer',
|
|
181
|
+
description: 'Search any element and inspect its owner, children, and directly related elements.',
|
|
182
|
+
codeCells: [
|
|
183
|
+
`QUERY = '' # Example: 'pump', 'controller', 'safety', or a requirement name
|
|
184
|
+
|
|
185
|
+
semantic_elements = (
|
|
186
|
+
list(model.elements(syside.Usage, include_subtypes=True))
|
|
187
|
+
+ list(model.elements(syside.Definition, include_subtypes=True))
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def label(element):
|
|
191
|
+
value = element.qualified_name or element.name or element.declared_name
|
|
192
|
+
return str(value) if value else '<unnamed>'
|
|
193
|
+
|
|
194
|
+
if not QUERY:
|
|
195
|
+
print('Set QUERY to a name fragment. Suggestions:')
|
|
196
|
+
for suggestion in [label(item) for item in semantic_elements if item.name][:20]:
|
|
197
|
+
print(' -', suggestion)
|
|
198
|
+
matches = []
|
|
199
|
+
else:
|
|
200
|
+
matches = [item for item in semantic_elements if QUERY.casefold() in label(item).casefold()]
|
|
201
|
+
print(f'{len(matches)} match(es) for {QUERY!r}')
|
|
202
|
+
|
|
203
|
+
[(type(item).__name__, label(item)) for item in matches[:100]]`,
|
|
204
|
+
`def neighborhood(element):
|
|
205
|
+
owner = getattr(element, 'owner', None)
|
|
206
|
+
children = [child for child in element.owned_elements if isinstance(child, (syside.Usage, syside.Definition))]
|
|
207
|
+
related = []
|
|
208
|
+
for relationship in element.owned_relationships:
|
|
209
|
+
for candidate in relationship.related_elements:
|
|
210
|
+
if candidate is not element:
|
|
211
|
+
related.append(candidate)
|
|
212
|
+
return {
|
|
213
|
+
'selected': (type(element).__name__, label(element)),
|
|
214
|
+
'owner': None if owner is None else (type(owner).__name__, label(owner)),
|
|
215
|
+
'children': [(type(child).__name__, label(child)) for child in children[:50]],
|
|
216
|
+
'directly related': [(type(item).__name__, label(item)) for item in related[:50]],
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
[neighborhood(item) for item in matches[:10]]`,
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
filename: '05-model-charts.ipynb',
|
|
224
|
+
title: 'Model charts',
|
|
225
|
+
description: 'Render semantic composition and diagnostic charts without external plotting packages.',
|
|
226
|
+
codeCells: [
|
|
227
|
+
`from html import escape
|
|
228
|
+
from IPython.display import HTML, display
|
|
229
|
+
|
|
230
|
+
type_names = [
|
|
231
|
+
'PartUsage', 'RequirementUsage', 'ActionUsage', 'PortUsage',
|
|
232
|
+
'ConnectionUsage', 'StateUsage', 'AttributeUsage', 'ItemUsage',
|
|
233
|
+
'PartDefinition', 'RequirementDefinition', 'ActionDefinition', 'PortDefinition'
|
|
234
|
+
]
|
|
235
|
+
counts = {}
|
|
236
|
+
for type_name in type_names:
|
|
237
|
+
node_type = getattr(syside, type_name, None)
|
|
238
|
+
if node_type is not None:
|
|
239
|
+
counts[type_name] = len(list(model.elements(node_type)))
|
|
240
|
+
|
|
241
|
+
palette = ['#2d8d70', '#3887c7', '#e58a2b', '#8357b6', '#cf5c62', '#4f9d9d']
|
|
242
|
+
largest = max(counts.values(), default=1)
|
|
243
|
+
bars = []
|
|
244
|
+
for index, (name, count) in enumerate(sorted(counts.items(), key=lambda item: item[1], reverse=True)):
|
|
245
|
+
width = max(1, count * 100 / largest)
|
|
246
|
+
color = palette[index % len(palette)]
|
|
247
|
+
bars.append(f'''<div style="display:grid;grid-template-columns:180px 1fr 70px;gap:12px;align-items:center;margin:7px 0">
|
|
248
|
+
<div>{escape(name)}</div><div style="background:#edf2f4;border-radius:6px;overflow:hidden"><div style="width:{width:.1f}%;height:20px;background:{color}"></div></div><strong>{count:,}</strong>
|
|
249
|
+
</div>''')
|
|
250
|
+
|
|
251
|
+
error_count = len(list(diagnostics.errors))
|
|
252
|
+
warning_count = len(list(diagnostics.warnings))
|
|
253
|
+
info_count = len(list(diagnostics.infos))
|
|
254
|
+
total_diagnostics = max(1, error_count + warning_count + info_count)
|
|
255
|
+
diagnostic_gradient = (
|
|
256
|
+
f'#cf5c62 0 {error_count / total_diagnostics * 100:.1f}%, '
|
|
257
|
+
f'#e5a52b {error_count / total_diagnostics * 100:.1f}% {(error_count + warning_count) / total_diagnostics * 100:.1f}%, '
|
|
258
|
+
f'#3887c7 {(error_count + warning_count) / total_diagnostics * 100:.1f}% 100%'
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
display(HTML(f'''<div style="font-family:system-ui;max-width:900px">
|
|
262
|
+
<h2>Semantic composition</h2>{''.join(bars)}
|
|
263
|
+
<h2 style="margin-top:28px">Diagnostics</h2>
|
|
264
|
+
<div style="display:flex;align-items:center;gap:24px">
|
|
265
|
+
<div style="width:150px;height:150px;border-radius:50%;background:conic-gradient({diagnostic_gradient});position:relative">
|
|
266
|
+
<div style="position:absolute;inset:28px;background:white;border-radius:50%;display:grid;place-items:center;font-size:24px;font-weight:700">{error_count + warning_count + info_count}</div>
|
|
267
|
+
</div>
|
|
268
|
+
<table><tr><th>Severity</th><th>Count</th></tr><tr><td>Errors</td><td>{error_count}</td></tr><tr><td>Warnings</td><td>{warning_count}</td></tr><tr><td>Information</td><td>{info_count}</td></tr></table>
|
|
269
|
+
</div>
|
|
270
|
+
</div>'''))`,
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
filename: '06-ownership-graph.ipynb',
|
|
275
|
+
title: 'Ownership graph',
|
|
276
|
+
description: 'Draw an SVG network of model elements and their ownership relationships.',
|
|
277
|
+
codeCells: [
|
|
278
|
+
`from html import escape
|
|
279
|
+
from IPython.display import SVG, display
|
|
280
|
+
|
|
281
|
+
semantic_elements = (
|
|
282
|
+
list(model.elements(syside.Usage, include_subtypes=True))
|
|
283
|
+
+ list(model.elements(syside.Definition, include_subtypes=True))
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def node_name(element):
|
|
287
|
+
value = element.name or element.declared_name or element.qualified_name
|
|
288
|
+
return str(value) if value else '<unnamed>'
|
|
289
|
+
|
|
290
|
+
# Prefer named elements with semantic children, then fill with other named elements.
|
|
291
|
+
ranked = sorted(
|
|
292
|
+
(item for item in semantic_elements if item.name or item.declared_name),
|
|
293
|
+
key=lambda item: sum(isinstance(child, (syside.Usage, syside.Definition)) for child in item.owned_elements),
|
|
294
|
+
reverse=True,
|
|
295
|
+
)
|
|
296
|
+
selected = ranked[:60]
|
|
297
|
+
selected_ids = {id(item) for item in selected}
|
|
298
|
+
|
|
299
|
+
def depth(element):
|
|
300
|
+
value, current, seen = 0, element.owner, set()
|
|
301
|
+
while current is not None and id(current) not in seen:
|
|
302
|
+
seen.add(id(current)); value += 1; current = getattr(current, 'owner', None)
|
|
303
|
+
return value
|
|
304
|
+
|
|
305
|
+
columns = defaultdict(list)
|
|
306
|
+
for item in selected:
|
|
307
|
+
columns[depth(item)].append(item)
|
|
308
|
+
depths = sorted(columns)
|
|
309
|
+
positions = {}
|
|
310
|
+
for column_index, level in enumerate(depths):
|
|
311
|
+
for row_index, item in enumerate(columns[level]):
|
|
312
|
+
positions[id(item)] = (30 + column_index * 240, 30 + row_index * 58)
|
|
313
|
+
|
|
314
|
+
width = max(500, 260 * max(1, len(depths)))
|
|
315
|
+
height = max(240, 70 + 58 * max((len(items) for items in columns.values()), default=1))
|
|
316
|
+
edges = []
|
|
317
|
+
nodes = []
|
|
318
|
+
colors = {'PartUsage':'#dff4ee', 'RequirementUsage':'#e8eefb', 'ActionUsage':'#fff0dc', 'PortUsage':'#efe5fa'}
|
|
319
|
+
for item in selected:
|
|
320
|
+
x, y = positions[id(item)]
|
|
321
|
+
owner = getattr(item, 'owner', None)
|
|
322
|
+
if owner is not None and id(owner) in selected_ids:
|
|
323
|
+
ox, oy = positions[id(owner)]
|
|
324
|
+
edges.append(f'<path d="M {ox + 190} {oy + 20} C {ox + 215} {oy + 20}, {x - 25} {y + 20}, {x} {y + 20}" fill="none" stroke="#91a7b3" stroke-width="1.5"/>')
|
|
325
|
+
kind = type(item).__name__
|
|
326
|
+
fill = colors.get(kind, '#f4f6f7')
|
|
327
|
+
label = escape(node_name(item)[:27])
|
|
328
|
+
nodes.append(f'<g><rect x="{x}" y="{y}" width="190" height="40" rx="7" fill="{fill}" stroke="#547180"/><text x="{x + 9}" y="{y + 17}" font-size="11" font-weight="700">{label}</text><text x="{x + 9}" y="{y + 32}" font-size="9" fill="#587487">{kind}</text></g>')
|
|
329
|
+
|
|
330
|
+
svg = f'''<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 {width} {height}" style="background:#fbfcfc;border:1px solid #d9e3e8;border-radius:10px">{''.join(edges)}{''.join(nodes)}</svg>'''
|
|
331
|
+
display(SVG(svg))
|
|
332
|
+
print(f'Displayed {len(selected)} elements and {len(edges)} ownership edges')`,
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
filename: '07-model-inventory-table.ipynb',
|
|
337
|
+
title: 'Model inventory table',
|
|
338
|
+
description: 'Filter a model-independent element table and export the current result to CSV.',
|
|
339
|
+
codeCells: [
|
|
340
|
+
`from html import escape
|
|
341
|
+
from IPython.display import HTML, display
|
|
342
|
+
|
|
343
|
+
KIND_FILTER = '' # Example: 'PartUsage' or 'Requirement'
|
|
344
|
+
NAME_FILTER = '' # Case-insensitive name fragment
|
|
345
|
+
ROW_LIMIT = 250
|
|
346
|
+
|
|
347
|
+
semantic_elements = (
|
|
348
|
+
list(model.elements(syside.Usage, include_subtypes=True))
|
|
349
|
+
+ list(model.elements(syside.Definition, include_subtypes=True))
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
def text(value):
|
|
353
|
+
return str(value) if value else ''
|
|
354
|
+
|
|
355
|
+
records = []
|
|
356
|
+
for item in semantic_elements:
|
|
357
|
+
kind = type(item).__name__
|
|
358
|
+
name = text(item.name or item.declared_name)
|
|
359
|
+
qualified_name = text(item.qualified_name)
|
|
360
|
+
owner = getattr(item, 'owner', None)
|
|
361
|
+
owner_name = text(getattr(owner, 'qualified_name', None) or getattr(owner, 'name', None))
|
|
362
|
+
if KIND_FILTER and KIND_FILTER.casefold() not in kind.casefold():
|
|
363
|
+
continue
|
|
364
|
+
if NAME_FILTER and NAME_FILTER.casefold() not in (name + ' ' + qualified_name).casefold():
|
|
365
|
+
continue
|
|
366
|
+
records.append({'kind': kind, 'name': name, 'qualified_name': qualified_name, 'owner': owner_name})
|
|
367
|
+
|
|
368
|
+
records.sort(key=lambda row: (row['kind'], row['qualified_name'], row['name']))
|
|
369
|
+
visible = records[:ROW_LIMIT]
|
|
370
|
+
rows = ''.join(
|
|
371
|
+
'<tr>' + ''.join(f'<td>{escape(row[key])}</td>' for key in ('kind', 'name', 'qualified_name', 'owner')) + '</tr>'
|
|
372
|
+
for row in visible
|
|
373
|
+
)
|
|
374
|
+
display(HTML(f'''<p><strong>{len(records):,}</strong> matching elements; showing {len(visible):,}.</p>
|
|
375
|
+
<div style="max-height:620px;overflow:auto"><table style="font-size:12px"><thead style="position:sticky;top:0;background:white"><tr><th>Kind</th><th>Name</th><th>Qualified name</th><th>Owner</th></tr></thead><tbody>{rows}</tbody></table></div>'''))`,
|
|
376
|
+
`import csv
|
|
377
|
+
|
|
378
|
+
OUTPUT = Path('model-inventory.csv')
|
|
379
|
+
with OUTPUT.open('w', newline='', encoding='utf-8') as stream:
|
|
380
|
+
writer = csv.DictWriter(stream, fieldnames=['kind', 'name', 'qualified_name', 'owner'])
|
|
381
|
+
writer.writeheader()
|
|
382
|
+
writer.writerows(records)
|
|
383
|
+
print(f'Exported {len(records):,} rows to {OUTPUT.resolve()}')`,
|
|
384
|
+
],
|
|
385
|
+
},
|
|
386
|
+
];
|
|
387
|
+
function markdownCell(starter) {
|
|
388
|
+
return {
|
|
389
|
+
cell_type: 'markdown',
|
|
390
|
+
metadata: {},
|
|
391
|
+
source: [`# ${starter.title}\n\n${starter.description}\n\nThis sample uses only standard SysML v2 concepts and automatically discovers the project's \`model/\` or \`src/\` directory.`],
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function codeCell(source) {
|
|
395
|
+
return { cell_type: 'code', metadata: {}, source: [source], execution_count: null, outputs: [] };
|
|
396
|
+
}
|
|
397
|
+
function notebook(starter) {
|
|
398
|
+
const cells = [markdownCell(starter), codeCell(LOAD_MODEL), ...starter.codeCells.map(codeCell)]
|
|
399
|
+
.map((cell, index) => ({ ...cell, id: `memo-${index + 1}` }));
|
|
400
|
+
return {
|
|
401
|
+
cells,
|
|
402
|
+
metadata: {
|
|
403
|
+
kernelspec: { display_name: 'Python 3 (ipykernel)', language: 'python', name: 'python3' },
|
|
404
|
+
language_info: { name: 'python', version: '3' },
|
|
405
|
+
memo: { starter: true, description: starter.description },
|
|
406
|
+
},
|
|
407
|
+
nbformat: 4,
|
|
408
|
+
nbformat_minor: 5,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/** Add model-independent Jupyter samples to a newly initialized project. */
|
|
412
|
+
export function scaffoldAnalysisSamples(projectDir) {
|
|
413
|
+
const samplesDir = resolve(projectDir, 'analysis', 'Samples');
|
|
414
|
+
mkdirSync(samplesDir, { recursive: true });
|
|
415
|
+
const readmePath = resolve(samplesDir, 'README.md');
|
|
416
|
+
if (!existsSync(readmePath))
|
|
417
|
+
writeFileSync(readmePath, SAMPLES_README);
|
|
418
|
+
for (const starter of STARTERS) {
|
|
419
|
+
const path = resolve(samplesDir, starter.filename);
|
|
420
|
+
if (!existsSync(path))
|
|
421
|
+
writeFileSync(path, JSON.stringify(notebook(starter), null, 2) + '\n');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
export const analysisSampleNames = STARTERS.map(starter => starter.filename);
|
|
425
|
+
//# sourceMappingURL=analysis-starters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis-starters.js","sourceRoot":"","sources":["../src/analysis-starters.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../../src/commands/ask.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../../src/commands/ask.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,iBAAiB;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqD5F"}
|
|
@@ -6,27 +6,10 @@
|
|
|
6
6
|
// memo ask "Show trace from REQ-001 to verification"
|
|
7
7
|
// memo ask "Which layers are least complete?"
|
|
8
8
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
-
import { readdirSync } from 'node:fs';
|
|
10
|
-
import { resolve } from 'node:path';
|
|
11
9
|
import chalk from 'chalk';
|
|
12
10
|
import { findConfigFile, parseFiles, buildMemoModel, loadOntologyRegistries, validateModel, computeCompleteness, createQueryContext, resolveLLMConfig, createProvider, askModel, } from '@memoarchitect/tools';
|
|
13
11
|
import { loadAndResolveConfig } from '../server/config-resolver.js';
|
|
14
|
-
|
|
15
|
-
const files = [];
|
|
16
|
-
try {
|
|
17
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
18
|
-
const full = resolve(dir, entry.name);
|
|
19
|
-
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.memo') {
|
|
20
|
-
files.push(...findSysmlFiles(full));
|
|
21
|
-
}
|
|
22
|
-
else if (entry.name.endsWith('.sysml')) {
|
|
23
|
-
files.push(full);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
catch { /* skip */ }
|
|
28
|
-
return files;
|
|
29
|
-
}
|
|
12
|
+
import { findSysmlFiles } from '../model/sysml-files.js';
|
|
30
13
|
export async function askCommand(question, options) {
|
|
31
14
|
// 1. Resolve LLM provider
|
|
32
15
|
const llmConfig = resolveLLMConfig();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask.js","sourceRoot":"","sources":["../../src/commands/ask.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,uDAAuD;AACvD,YAAY;AACZ,mDAAmD;AACnD,uDAAuD;AACvD,gDAAgD;AAChD,gFAAgF;
|
|
1
|
+
{"version":3,"file":"ask.js","sourceRoot":"","sources":["../../src/commands/ask.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAiIA,wBAAsB,YAAY,CAC9B,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/E,OAAO,CAAC,IAAI,CAAC,CA8Gf"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { findConfigFile, parseFiles, buildMemoModel, loadOntologyRegistries } from '@memoarchitect/tools';
|
|
5
5
|
import { loadAndResolveConfig } from '../server/config-resolver.js';
|
|
6
|
+
import { findSysmlFiles } from '../model/sysml-files.js';
|
|
6
7
|
const STANDARD_CONSTRUCTS = new Set([
|
|
7
8
|
'part', 'requirement', 'action', 'item', 'port',
|
|
8
9
|
'interface', 'connection', 'attribute', 'constraint',
|
|
@@ -14,24 +15,6 @@ const STANDARD_KEYWORDS = new Set([
|
|
|
14
15
|
'viewpoint', 'view', 'flow', 'allocate', 'connect', 'first', 'then',
|
|
15
16
|
'in', 'out', 'inout', 'end', 'ref', 'doc', 'library',
|
|
16
17
|
]);
|
|
17
|
-
function findSysmlFiles(dir) {
|
|
18
|
-
const files = [];
|
|
19
|
-
try {
|
|
20
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
21
|
-
const full = resolve(dir, entry.name);
|
|
22
|
-
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.memo') {
|
|
23
|
-
files.push(...findSysmlFiles(full));
|
|
24
|
-
}
|
|
25
|
-
else if (entry.name.endsWith('.sysml')) {
|
|
26
|
-
files.push(full);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
// skip
|
|
32
|
-
}
|
|
33
|
-
return files;
|
|
34
|
-
}
|
|
35
18
|
function checkExplicitMultiplicity(sysmlFiles) {
|
|
36
19
|
const findings = [];
|
|
37
20
|
const attrRegex = /attribute\s+(\w+)\s*:\s*(\w+)\s*(?:\[([^\]]*)\])?\s*[;{=]/g;
|