bc-atlas 0.8.0 → 0.10.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/CHANGELOG.md +88 -0
- package/README.md +1 -1
- package/package.json +7 -6
- package/src/app/service.js +5 -0
- package/src/app/snapshot.js +3 -1
- package/src/app/ui/app.css +57 -0
- package/src/app/ui/app.js +533 -11
- package/src/app/workspace.js +32 -4
- package/src/architecture.js +6 -5
- package/src/blueprint/cluster.js +576 -0
- package/src/blueprint/components.js +13 -3
- package/src/blueprint/drilldown.js +195 -0
- package/src/blueprint/evaluate.js +1 -1
- package/src/blueprint/propose.js +205 -139
- package/src/blueprint/report.js +31 -1
- package/src/capabilities.js +3 -3
- package/src/commands/export.js +3 -2
- package/src/commands/report.js +21 -2
- package/src/docs/al-ui-source.js +275 -45
- package/src/docs/captions.js +2 -2
- package/src/docs/cli.js +13 -3
- package/src/docs/export.js +1 -1
- package/src/docs/model.js +4 -1
- package/src/docs/phrases.js +78 -2
- package/src/docs/render/dita.js +3 -2
- package/src/docs/render/html.js +8 -1
- package/src/docs/render/markdown.js +5 -2
- package/src/docs/steps.js +109 -9
- package/src/docs/tags.js +2 -1
- package/src/docs/task.js +13 -4
- package/src/graph/coupling.js +26 -3
- package/src/graph/document.js +2 -2
- package/src/graph/lenses/dependency.js +10 -3
- package/src/mcp.js +1 -1
- package/src/parse-cache.js +98 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,94 @@ All notable changes to BC Atlas will be documented here. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
|
|
5
5
|
follows [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## 0.10.0 - 2026-10-29
|
|
8
|
+
|
|
9
|
+
The npm package skips 0.9.0 so it shares its version with the VS Code
|
|
10
|
+
extension, whose previous release was 0.9.0.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Shared core: objects that many components use but none owns (setup
|
|
15
|
+
tables, master data) form a component of their own instead of gluing the
|
|
16
|
+
others together.
|
|
17
|
+
- App candidates and namespaces: components that are coupled more to each
|
|
18
|
+
other than to the rest could become one app together. Every proposed
|
|
19
|
+
component gets a role (app, component, shared core) and a suggested
|
|
20
|
+
namespace, in the JSON (`proposal.recommendations`), the reports, the
|
|
21
|
+
component's description, and the Architecture page.
|
|
22
|
+
- `id:50100..50199` selects objects by ID range; proposals use it for code
|
|
23
|
+
without namespaces or functional folders.
|
|
24
|
+
- A page per component (click it on the Architecture page): its role, layer,
|
|
25
|
+
the components it may use, suggested namespace, app candidate, and
|
|
26
|
+
selectors; the groups inside it (sub-domains clustered by coupling and
|
|
27
|
+
named after their namespace, folder, or shared words); a graph of its
|
|
28
|
+
objects with one island per group and, on request, its links to other
|
|
29
|
+
components in green (allowed) and red (wrong way); per other component
|
|
30
|
+
whether it may be used and how often it is; every object; and its work
|
|
31
|
+
items. Also in the exported app.
|
|
32
|
+
- Dependencies between components, should vs. is: a map of the components in
|
|
33
|
+
their layers with green arrows for allowed and red arrows for wrong-way
|
|
34
|
+
dependencies, and a matrix that tints every pair by the rules and counts
|
|
35
|
+
today's object links in it.
|
|
36
|
+
- `bca docs` turns more TestPage code into steps: `Field.Value :=`,
|
|
37
|
+
`Filter.SetFilter`, `GoToKey`, the `OK`, `Cancel`, `Yes`, and `No` buttons,
|
|
38
|
+
`First`/`Last`/`Next`/`Previous`, `Lookup`, `Drilldown`, `AssistEdit`,
|
|
39
|
+
`Page.Trap()` ("The ... page opens."), `asserterror` ("Business Central
|
|
40
|
+
shows an error message."), calls over several lines, and the pages and
|
|
41
|
+
confirmations a test answers in `[ModalPageHandler]`, `[PageHandler]`,
|
|
42
|
+
`[RequestPageHandler]`, and `[ConfirmHandler]` procedures ("If the ... page
|
|
43
|
+
opens, ...").
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- The architecture proposal comes from the relations, not from namespaces.
|
|
48
|
+
Relations are typed (structural, data, behavioral, loose, administrative)
|
|
49
|
+
and weighted; inside each app or module, the objects that are coupled more
|
|
50
|
+
to each other than to the rest form a component (the best of several
|
|
51
|
+
Louvain runs, refined object by object, with today's namespaces and
|
|
52
|
+
folders competing as candidates). It works the same for apps without
|
|
53
|
+
namespaces (type folders, ID blocks) and for a mix. `--group-by coupling`
|
|
54
|
+
is now the same as `auto`; `namespace` and `folder` let the structure
|
|
55
|
+
decide and place objects without one by their relations.
|
|
56
|
+
- A page or query and its source table are a relation of their own
|
|
57
|
+
(`source`, weight 4) instead of a read.
|
|
58
|
+
- VS Code and `bca export --view app` keep the parse results of every AL file
|
|
59
|
+
on disk (`BCA_CACHE_DIR`, default `<temp>/bc-atlas/parse`) and across
|
|
60
|
+
changes, so reopening a project or saving a file parses only what changed.
|
|
61
|
+
The Docs page and the explorer reuse them instead of parsing the app again.
|
|
62
|
+
On a 2,931-object app the overview opens in about 8 seconds instead of 15,
|
|
63
|
+
and the Docs page in about 11 instead of 25.
|
|
64
|
+
- The npm package and the VS Code extension are released under one version
|
|
65
|
+
number from now on, starting with 0.10.0; `npm run version:set -- <version>`
|
|
66
|
+
sets both, and the tests and publish workflows refuse a mismatch.
|
|
67
|
+
- Dependencies: zod 4.6.7, web-tree-sitter 0.27.1, and the MCP SDK 2.4.0.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- The report no longer writes an absolute path into README.md when a view has
|
|
72
|
+
too many nodes for the bundled SVG renderer: it keeps the view as D2, links
|
|
73
|
+
it relative to the README, removes an SVG left by an earlier run, and the
|
|
74
|
+
MCP report tool marks it `d2Only`.
|
|
75
|
+
- Opening a scenario or use case on the Docs page no longer leaves the page
|
|
76
|
+
blank in VS Code (or loads the whole app into the frame in an exported
|
|
77
|
+
app): the guide is embedded through `srcdoc`, where `#links` resolve
|
|
78
|
+
against the host page, so it now scrolls to the target itself.
|
|
79
|
+
- `bca docs` reads AL tests written the way Microsoft writes the Business
|
|
80
|
+
Central tests: `[SCENARIO 218101]` with a work item number (kept as a
|
|
81
|
+
reference), `[FEATURE] [Sales] [Invoice]` lists (`[UI]` and `[UT]` are left
|
|
82
|
+
out), the codeunit's `[FEATURE]` in its `OnRun` trigger, and `[VERIFY]` as
|
|
83
|
+
an expected result. Such test codeunits used to give no scenarios at all.
|
|
84
|
+
- A documented test ends where its procedure ends: tags and TestPage calls of
|
|
85
|
+
helper procedures after it (and, for the last test, the codeunit's global
|
|
86
|
+
variables) no longer leak into its guide and its fingerprint.
|
|
87
|
+
- Directory runs of `bca docs` leave out `[SCENARIO]` tests without any UI
|
|
88
|
+
step (unless they have a `[DOC-ID]`) and report how many; they used to
|
|
89
|
+
become empty guides.
|
|
90
|
+
- Guides for descriptive scenarios ("Users can post ...") no longer read
|
|
91
|
+
"Use this guide to users can ...", empty values no longer show as `****`,
|
|
92
|
+
the note about example values only appears when the steps show any, and
|
|
93
|
+
test case tables keep the context of loops and dialogs in one row.
|
|
94
|
+
|
|
7
95
|
## 0.8.0 - 2026-10-29
|
|
8
96
|
|
|
9
97
|
### Added
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ In VS Code, install the **BC Atlas** extension and open an AL project:
|
|
|
44
44
|
| **Export** | `bca export` | Everything the extension shows, as files: the BC Atlas pages as one offline HTML file to share (default), the Markdown report ([example](./examples/report/README.md)), the 3D explorer, one Markdown page per object, any of ten diagram views as D2, SVG, PNG, PDF, or JSON, with `--watch`. |
|
|
45
45
|
| | `bca docs` | User guides and test cases from AL UI tests (`[SCENARIO]`, `[GIVEN]`, `[WHEN]`, `[THEN]`) as Markdown, HTML, DITA 1.3, or Azure DevOps CSV, with real captions and translations, a flow diagram per scenario, and BPMN 2.0 processes per use case. [Example](./examples/docs/index.md) |
|
|
46
46
|
| **Integrate** | `bca run` | One entry point for Git hooks, schedules, and pipelines; verifies, stages, or publishes generated files. Also available as a [GitHub Action](./docs/automation-and-ci.md#github-actions). |
|
|
47
|
-
| | `bca update` | "BC Atlas 0.
|
|
47
|
+
| | `bca update` | "BC Atlas 0.11.0 is available": release notes, one-step update, and regenerated hooks, tasks, and pipelines. Dependabot pull requests on request. |
|
|
48
48
|
| | `bca mcp` | A Model Context Protocol server for AI agents; `bca help --format json` is the versioned command contract. |
|
|
49
49
|
|
|
50
50
|
Run `bca` for the same overview in your terminal and `bca help <command>` for
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bc-atlas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "BC Atlas maps Microsoft Dynamics 365 Business Central AL projects: architecture diagrams, health checks, reports, and documentation generated from source.",
|
|
5
5
|
"homepage": "https://github.com/SchulzOli/BC-Atlas#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"update:grammar": "node ./scripts/update-grammar.js",
|
|
37
37
|
"site:dev": "vitepress dev website",
|
|
38
38
|
"site:build": "vitepress build website && node ./scripts/site-assets.js",
|
|
39
|
-
"site:preview": "vitepress preview website"
|
|
39
|
+
"site:preview": "vitepress preview website",
|
|
40
|
+
"version:set": "node ./scripts/set-version.js"
|
|
40
41
|
},
|
|
41
42
|
"keywords": [
|
|
42
43
|
"al",
|
|
@@ -59,18 +60,18 @@
|
|
|
59
60
|
"node": ">=22"
|
|
60
61
|
},
|
|
61
62
|
"dependencies": {
|
|
62
|
-
"@modelcontextprotocol/server": "^2.
|
|
63
|
+
"@modelcontextprotocol/server": "^2.4.0",
|
|
63
64
|
"@terrastruct/d2": "^0.1.33",
|
|
64
65
|
"3d-force-graph": "^1.80.0",
|
|
65
66
|
"fflate": "^0.8.3",
|
|
66
67
|
"graphology": "^0.26.0",
|
|
67
68
|
"graphology-communities-louvain": "^2.0.2",
|
|
68
69
|
"graphology-metrics": "^2.4.1",
|
|
69
|
-
"web-tree-sitter": "^0.27.
|
|
70
|
-
"zod": "^4.6.
|
|
70
|
+
"web-tree-sitter": "^0.27.1",
|
|
71
|
+
"zod": "^4.6.7"
|
|
71
72
|
},
|
|
72
73
|
"devDependencies": {
|
|
73
|
-
"@modelcontextprotocol/client": "^2.
|
|
74
|
+
"@modelcontextprotocol/client": "^2.4.0",
|
|
74
75
|
"bpmn-moddle": "^10.3.1",
|
|
75
76
|
"vitepress": "2.0.0-alpha.20"
|
|
76
77
|
}
|
package/src/app/service.js
CHANGED
|
@@ -25,6 +25,11 @@ export function createService(workspace) {
|
|
|
25
25
|
overview: () => workspace.overview(),
|
|
26
26
|
health: () => workspace.healthReport(),
|
|
27
27
|
architecture: (params) => workspace.architecture(architectureSettings(params)),
|
|
28
|
+
component: async ({ name, ...params } = {}) => {
|
|
29
|
+
const component = await workspace.component(String(name ?? ""), architectureSettings(params));
|
|
30
|
+
if (!component) throw new Error("component not found");
|
|
31
|
+
return component;
|
|
32
|
+
},
|
|
28
33
|
objects: () => workspace.objects(),
|
|
29
34
|
object: async ({ id } = {}) => {
|
|
30
35
|
const object = await workspace.object(String(id ?? ""));
|
package/src/app/snapshot.js
CHANGED
|
@@ -39,9 +39,10 @@ function compactDetails(index) {
|
|
|
39
39
|
|
|
40
40
|
export async function createSnapshot(workspace, { log = () => {}, history = false, last, title } = {}) {
|
|
41
41
|
const overview = { ...await workspace.overview(), ...(title ? { title } : {}) };
|
|
42
|
-
const [health, architecture, index, docsHtml] = await Promise.all([
|
|
42
|
+
const [health, architecture, components, index, docsHtml] = await Promise.all([
|
|
43
43
|
workspace.healthReport(),
|
|
44
44
|
workspace.architecture(),
|
|
45
|
+
workspace.components(),
|
|
45
46
|
workspace.objectIndex(),
|
|
46
47
|
workspace.docsHtml()
|
|
47
48
|
]);
|
|
@@ -66,6 +67,7 @@ export async function createSnapshot(workspace, { log = () => {}, history = fals
|
|
|
66
67
|
overview: { ...overview, live: false, root: undefined, projectRoot: undefined },
|
|
67
68
|
health,
|
|
68
69
|
architecture,
|
|
70
|
+
components,
|
|
69
71
|
objects: index.list,
|
|
70
72
|
nodes,
|
|
71
73
|
details,
|
package/src/app/ui/app.css
CHANGED
|
@@ -214,6 +214,63 @@ details.steps ol, details.steps ul { margin: 6px 0 2px; padding-left: 18px; }
|
|
|
214
214
|
.legend .bad::before { background: var(--error); }
|
|
215
215
|
.controls { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end; }
|
|
216
216
|
|
|
217
|
+
/* Dependencies between components: green where allowed, red where against the architecture */
|
|
218
|
+
.dependency-map { overflow: auto; margin-top: 8px; }
|
|
219
|
+
.dependency-map svg { width: 100%; height: auto; display: block; margin: 0 auto; }
|
|
220
|
+
.dependency-map .dep { fill: none; opacity: 0.75; transition: opacity 0.15s; }
|
|
221
|
+
.dependency-map .dep.allowed, .dependency-map .tip.allowed { stroke: var(--ok); fill: var(--ok); }
|
|
222
|
+
.dependency-map .dep.allowed { fill: none; }
|
|
223
|
+
.dependency-map .dep.wrong, .dependency-map .tip.wrong { stroke: var(--error); fill: var(--error); }
|
|
224
|
+
.dependency-map .dep.wrong { fill: none; }
|
|
225
|
+
.dependency-map svg.focused .dep { opacity: 0.06; }
|
|
226
|
+
.dependency-map svg.focused .dep.on { opacity: 1; }
|
|
227
|
+
.dependency-map.hide-allowed .dep.allowed, .dependency-map.hide-wrong .dep.wrong { display: none; }
|
|
228
|
+
.dependency-map .dep-node { cursor: pointer; }
|
|
229
|
+
.dependency-map .dep-node rect { fill: var(--surface); stroke: var(--border-strong); }
|
|
230
|
+
.dependency-map .dep-node.extractable rect { stroke: var(--ok); }
|
|
231
|
+
.dependency-map .dep-node.blocked rect { stroke: var(--error); }
|
|
232
|
+
.dependency-map .dep-node:hover rect { stroke-width: 2; }
|
|
233
|
+
.dependency-map .dep-node text { fill: var(--text); font-size: 12.5px; font-weight: 600; }
|
|
234
|
+
.dependency-map .dep-node .count { fill: var(--muted); font-weight: 400; }
|
|
235
|
+
.dependency-map .layer-name { fill: var(--muted); font-size: 12px; }
|
|
236
|
+
table.matrix { border-collapse: collapse; font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
237
|
+
table.matrix th { font-weight: 600; color: var(--muted); padding: 4px; text-align: center; position: sticky; top: 0; background: var(--surface); z-index: 1; }
|
|
238
|
+
table.matrix th.row-head { text-align: left; white-space: nowrap; position: sticky; left: 0; padding-right: 10px; z-index: 0; }
|
|
239
|
+
table.matrix td { min-width: 34px; height: 26px; text-align: center; border: 1px solid var(--surface); }
|
|
240
|
+
table.matrix td.allowed { background: color-mix(in srgb, var(--ok) 16%, transparent); }
|
|
241
|
+
table.matrix td.forbidden { background: color-mix(in srgb, var(--error) 6%, transparent); color: var(--muted); }
|
|
242
|
+
table.matrix td.wrong { background: color-mix(in srgb, var(--error) 26%, transparent); color: var(--error); font-weight: 700; }
|
|
243
|
+
table.matrix td.self { background: var(--surface-2); }
|
|
244
|
+
.matrix-legend .swatch { margin-left: 4px; }
|
|
245
|
+
.swatch { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 6px; vertical-align: -1px; }
|
|
246
|
+
.swatch.allowed { background: color-mix(in srgb, var(--ok) 40%, transparent); }
|
|
247
|
+
.swatch.forbidden { background: color-mix(in srgb, var(--error) 14%, transparent); }
|
|
248
|
+
.swatch.wrong { background: color-mix(in srgb, var(--error) 60%, transparent); }
|
|
249
|
+
|
|
250
|
+
/* Component page */
|
|
251
|
+
.group-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
|
|
252
|
+
.group-chips .swatch { margin-right: 0; }
|
|
253
|
+
.toolbar.padded { padding: 10px 18px 6px; }
|
|
254
|
+
.canvas.component-canvas { height: 66vh; min-height: 420px; background: var(--surface-2); }
|
|
255
|
+
.component-graph .link { stroke: var(--muted); stroke-opacity: 0.28; stroke-width: 0.8; }
|
|
256
|
+
.component-graph .ext { stroke-opacity: 0.55; }
|
|
257
|
+
.component-graph .ext.allowed { stroke: var(--ok); }
|
|
258
|
+
.component-graph .ext.wrong { stroke: var(--error); stroke-opacity: 0.75; }
|
|
259
|
+
.component-graph .ext.in { stroke-dasharray: 5 4; }
|
|
260
|
+
.component-graph .obj circle { stroke: var(--surface); stroke-width: 1.2; }
|
|
261
|
+
.component-graph .obj circle.flagged { stroke: var(--error); stroke-width: 2.5; }
|
|
262
|
+
.component-graph .obj:hover circle { stroke: var(--text); stroke-width: 2; }
|
|
263
|
+
.component-graph .obj text { fill: var(--text); font-size: 10px; pointer-events: none; }
|
|
264
|
+
.component-graph .island { fill-opacity: 0.07; stroke-opacity: 0.35; stroke-width: 1; stroke-dasharray: 4 4; }
|
|
265
|
+
.component-graph .group-name { font-size: 15px; font-weight: 700; paint-order: stroke; stroke: var(--surface-2); stroke-width: 4px; pointer-events: none; }
|
|
266
|
+
.component-graph .group-name .count { fill: var(--muted); font-weight: 400; }
|
|
267
|
+
.component-graph .other circle { fill: var(--surface); stroke-width: 2.5; }
|
|
268
|
+
.component-graph .other.allowed circle { stroke: var(--ok); }
|
|
269
|
+
.component-graph .other.wrong circle { stroke: var(--error); }
|
|
270
|
+
.component-graph .other text { fill: var(--text); font-size: 12px; font-weight: 600; paint-order: stroke; stroke: var(--surface); stroke-width: 3px; }
|
|
271
|
+
.component-graph.focused [data-group], .component-graph.focused [data-groups] { opacity: 0.12; }
|
|
272
|
+
.component-graph.focused .on { opacity: 1; }
|
|
273
|
+
|
|
217
274
|
/* Diagrams */
|
|
218
275
|
.diagram-layout { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 18px; align-items: start; }
|
|
219
276
|
.diagram-form { display: grid; gap: 12px; position: sticky; top: 90px; }
|