bc-atlas 0.8.0 → 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.
Files changed (42) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +1 -1
  3. package/THIRD_PARTY_NOTICES.md +3 -2
  4. package/package.json +11 -7
  5. package/src/app/service.js +5 -0
  6. package/src/app/snapshot.js +3 -1
  7. package/src/app/ui/app.css +57 -0
  8. package/src/app/ui/app.js +533 -11
  9. package/src/app/workspace.js +32 -4
  10. package/src/architecture.js +6 -5
  11. package/src/blueprint/cluster.js +650 -0
  12. package/src/blueprint/components.js +13 -3
  13. package/src/blueprint/drilldown.js +195 -0
  14. package/src/blueprint/evaluate.js +1 -1
  15. package/src/blueprint/propose.js +205 -139
  16. package/src/blueprint/report.js +31 -1
  17. package/src/capabilities.js +3 -3
  18. package/src/commands/export.js +3 -2
  19. package/src/commands/report.js +21 -2
  20. package/src/docs/al-ui-source.js +275 -45
  21. package/src/docs/al-ui-writer.js +48 -24
  22. package/src/docs/captions.js +2 -2
  23. package/src/docs/cli.js +13 -3
  24. package/src/docs/export.js +1 -1
  25. package/src/docs/model.js +4 -1
  26. package/src/docs/phrases.js +78 -2
  27. package/src/docs/render/dita.js +3 -2
  28. package/src/docs/render/html.js +8 -1
  29. package/src/docs/render/markdown.js +26 -13
  30. package/src/docs/steps.js +109 -9
  31. package/src/docs/tags.js +2 -1
  32. package/src/docs/task.js +13 -4
  33. package/src/graph/coupling.js +26 -3
  34. package/src/graph/document.js +57 -2
  35. package/src/graph/html.js +2 -7
  36. package/src/graph/lenses/dependency.js +10 -3
  37. package/src/graph/viewer/viewer.css +26 -0
  38. package/src/graph/viewer/viewer.js +365 -47
  39. package/src/mcp.js +1 -1
  40. package/src/parse-cache.js +98 -0
  41. package/vendor/README.md +15 -1
  42. package/vendor/graph-viewer.js +5498 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,137 @@ 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.11.0 - 2026-10-29
8
+
9
+ ### Changed
10
+
11
+ - Dependency explorer: without an architecture, objects are colored and
12
+ grouped by their relation clusters (the clustering of the architecture
13
+ proposal) instead of one color per app. Clusters float like galaxies
14
+ around their app candidate and their app, clusters of one app candidate
15
+ share a family of colors, each cluster shows its name at its center, links
16
+ inside a cluster take its color, and links between groups are faint arcs.
17
+ Labels that would overlap a more important one are hidden. On a dark
18
+ background the graph glows (**Glow** turns it off); dense graphs get
19
+ fainter links and less glow.
20
+ - Dependency explorer: a **Clusters** filter lists the clusters by app
21
+ candidate; **only** shows one cluster (or one app) and frames it, and so
22
+ does a click on a cluster name in the graph or in the legend, which lists
23
+ the largest clusters on screen. A second click or `Esc` shows all again.
24
+ Neighbors in other clusters stay visible in gray.
25
+ - Dependency explorer: the glow fades when you zoom in close, so large
26
+ spheres no longer bloom into white discs.
27
+ - The graph document (`bca export --view explorer --format json`) carries
28
+ the clusters: `clusters` with name, size, and app candidate, and `cluster`
29
+ on every workspace object.
30
+ - Clustering by relations is about twice as fast (4.5 instead of 9 seconds for
31
+ the Base Application) with the same result, and the explorer and the
32
+ architecture proposal share it: after a saved file, only the apps and
33
+ modules whose objects or relations changed are clustered again.
34
+ - The explorer's browser bundle (3d-force-graph with three.js and its bloom
35
+ pass) is built into `vendor/graph-viewer.js` (`npm run update:graph-vendor`);
36
+ 3d-force-graph is no longer installed with the package.
37
+
38
+ ### Fixed
39
+
40
+ - `bca docs set` and `unset` understand Microsoft's tags: `[SCENARIO 218101]`
41
+ and `[FEATURE] [A] [B]` are edited in place (the work item stays, one
42
+ feature of a list can be removed), setting `SCENARIO` again replaces the
43
+ line instead of adding a second one, and a value with a line break is
44
+ rejected instead of being written as AL code. Removing a feature that is
45
+ set for the whole codeunit says to change it in `OnRun`.
46
+ - Markdown guides and test cases show the author's text as written: `<Posted>`,
47
+ `<b>`, `*now*`, `_done_`, and `|` are escaped instead of disappearing or
48
+ turning into formatting.
49
+
50
+ ## 0.10.0 - 2026-10-29
51
+
52
+ The npm package skips 0.9.0 so it shares its version with the VS Code
53
+ extension, whose previous release was 0.9.0.
54
+
55
+ ### Added
56
+
57
+ - Shared core: objects that many components use but none owns (setup
58
+ tables, master data) form a component of their own instead of gluing the
59
+ others together.
60
+ - App candidates and namespaces: components that are coupled more to each
61
+ other than to the rest could become one app together. Every proposed
62
+ component gets a role (app, component, shared core) and a suggested
63
+ namespace, in the JSON (`proposal.recommendations`), the reports, the
64
+ component's description, and the Architecture page.
65
+ - `id:50100..50199` selects objects by ID range; proposals use it for code
66
+ without namespaces or functional folders.
67
+ - A page per component (click it on the Architecture page): its role, layer,
68
+ the components it may use, suggested namespace, app candidate, and
69
+ selectors; the groups inside it (sub-domains clustered by coupling and
70
+ named after their namespace, folder, or shared words); a graph of its
71
+ objects with one island per group and, on request, its links to other
72
+ components in green (allowed) and red (wrong way); per other component
73
+ whether it may be used and how often it is; every object; and its work
74
+ items. Also in the exported app.
75
+ - Dependencies between components, should vs. is: a map of the components in
76
+ their layers with green arrows for allowed and red arrows for wrong-way
77
+ dependencies, and a matrix that tints every pair by the rules and counts
78
+ today's object links in it.
79
+ - `bca docs` turns more TestPage code into steps: `Field.Value :=`,
80
+ `Filter.SetFilter`, `GoToKey`, the `OK`, `Cancel`, `Yes`, and `No` buttons,
81
+ `First`/`Last`/`Next`/`Previous`, `Lookup`, `Drilldown`, `AssistEdit`,
82
+ `Page.Trap()` ("The ... page opens."), `asserterror` ("Business Central
83
+ shows an error message."), calls over several lines, and the pages and
84
+ confirmations a test answers in `[ModalPageHandler]`, `[PageHandler]`,
85
+ `[RequestPageHandler]`, and `[ConfirmHandler]` procedures ("If the ... page
86
+ opens, ...").
87
+
88
+ ### Changed
89
+
90
+ - The architecture proposal comes from the relations, not from namespaces.
91
+ Relations are typed (structural, data, behavioral, loose, administrative)
92
+ and weighted; inside each app or module, the objects that are coupled more
93
+ to each other than to the rest form a component (the best of several
94
+ Louvain runs, refined object by object, with today's namespaces and
95
+ folders competing as candidates). It works the same for apps without
96
+ namespaces (type folders, ID blocks) and for a mix. `--group-by coupling`
97
+ is now the same as `auto`; `namespace` and `folder` let the structure
98
+ decide and place objects without one by their relations.
99
+ - A page or query and its source table are a relation of their own
100
+ (`source`, weight 4) instead of a read.
101
+ - VS Code and `bca export --view app` keep the parse results of every AL file
102
+ on disk (`BCA_CACHE_DIR`, default `<temp>/bc-atlas/parse`) and across
103
+ changes, so reopening a project or saving a file parses only what changed.
104
+ The Docs page and the explorer reuse them instead of parsing the app again.
105
+ On a 2,931-object app the overview opens in about 8 seconds instead of 15,
106
+ and the Docs page in about 11 instead of 25.
107
+ - The npm package and the VS Code extension are released under one version
108
+ number from now on, starting with 0.10.0; `npm run version:set -- <version>`
109
+ sets both, and the tests and publish workflows refuse a mismatch.
110
+ - Dependencies: zod 4.6.7, web-tree-sitter 0.27.1, and the MCP SDK 2.4.0.
111
+
112
+ ### Fixed
113
+
114
+ - The report no longer writes an absolute path into README.md when a view has
115
+ too many nodes for the bundled SVG renderer: it keeps the view as D2, links
116
+ it relative to the README, removes an SVG left by an earlier run, and the
117
+ MCP report tool marks it `d2Only`.
118
+ - Opening a scenario or use case on the Docs page no longer leaves the page
119
+ blank in VS Code (or loads the whole app into the frame in an exported
120
+ app): the guide is embedded through `srcdoc`, where `#links` resolve
121
+ against the host page, so it now scrolls to the target itself.
122
+ - `bca docs` reads AL tests written the way Microsoft writes the Business
123
+ Central tests: `[SCENARIO 218101]` with a work item number (kept as a
124
+ reference), `[FEATURE] [Sales] [Invoice]` lists (`[UI]` and `[UT]` are left
125
+ out), the codeunit's `[FEATURE]` in its `OnRun` trigger, and `[VERIFY]` as
126
+ an expected result. Such test codeunits used to give no scenarios at all.
127
+ - A documented test ends where its procedure ends: tags and TestPage calls of
128
+ helper procedures after it (and, for the last test, the codeunit's global
129
+ variables) no longer leak into its guide and its fingerprint.
130
+ - Directory runs of `bca docs` leave out `[SCENARIO]` tests without any UI
131
+ step (unless they have a `[DOC-ID]`) and report how many; they used to
132
+ become empty guides.
133
+ - Guides for descriptive scenarios ("Users can post ...") no longer read
134
+ "Use this guide to users can ...", empty values no longer show as `****`,
135
+ the note about example values only appears when the steps show any, and
136
+ test case tables keep the context of loops and dialogs in one row.
137
+
7
138
  ## 0.8.0 - 2026-10-29
8
139
 
9
140
  ### 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.8.0 is available": release notes, one-step update, and regenerated hooks, tasks, and pipelines. Dependabot pull requests on request. |
47
+ | | `bca update` | "BC Atlas 0.12.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
@@ -28,8 +28,9 @@ executable for PNG and PDF output.
28
28
  authors. Both licensed under the MIT License. Sources:
29
29
  <https://github.com/vasturiano/3d-force-graph>, <https://github.com/mrdoob/three.js>
30
30
 
31
- The 3d-force-graph browser bundle, which includes three.js, is embedded in the
32
- explorer HTML files written by `bca export` and shown by the VS Code extension.
31
+ A browser bundle of 3d-force-graph and three.js (including its bloom pass),
32
+ `vendor/graph-viewer.js`, is embedded in the explorer HTML files written by
33
+ `bca export` and shown by the VS Code extension.
33
34
 
34
35
  ## graphology
35
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bc-atlas",
3
- "version": "0.8.0",
3
+ "version": "0.11.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,9 @@
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",
41
+ "update:graph-vendor": "node ./scripts/build-graph-vendor.js"
40
42
  },
41
43
  "keywords": [
42
44
  "al",
@@ -59,19 +61,21 @@
59
61
  "node": ">=22"
60
62
  },
61
63
  "dependencies": {
62
- "@modelcontextprotocol/server": "^2.3.0",
64
+ "@modelcontextprotocol/server": "^2.4.0",
63
65
  "@terrastruct/d2": "^0.1.33",
64
- "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.0",
70
- "zod": "^4.6.5"
70
+ "web-tree-sitter": "^0.27.1",
71
+ "zod": "^4.6.7"
71
72
  },
72
73
  "devDependencies": {
73
- "@modelcontextprotocol/client": "^2.3.0",
74
+ "3d-force-graph": "^1.80.0",
75
+ "@modelcontextprotocol/client": "^2.4.0",
74
76
  "bpmn-moddle": "^10.3.1",
77
+ "esbuild": "^0.28.2",
78
+ "three": "^0.186.1",
75
79
  "vitepress": "2.0.0-alpha.20"
76
80
  }
77
81
  }
@@ -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 ?? ""));
@@ -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,
@@ -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; }