@ecoma-io/archkeep 0.25.0 → 0.26.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 (120) hide show
  1. package/cli.mjs +166 -506
  2. package/commands.mjs +7 -1
  3. package/package.json +1 -1
  4. package/src/architecture-intent/judge.mjs +1 -1
  5. package/src/architecture-intent/model.mjs +34 -0
  6. package/src/canonical.mjs +2 -1
  7. package/src/commands/adr-for-workspace.mjs +63 -0
  8. package/src/commands/analyze-capability.mjs +13 -0
  9. package/src/commands/change-intent.mjs +11 -0
  10. package/src/commands/change.mjs +247 -34
  11. package/src/commands/check-capability.mjs +13 -0
  12. package/src/commands/check.mjs +13 -3
  13. package/src/commands/compare-capability.mjs +20 -0
  14. package/src/commands/completeness.mjs +12 -0
  15. package/src/commands/context-command.mjs +31 -0
  16. package/src/commands/coverage-verdict.mjs +15 -2
  17. package/src/commands/debt.mjs +30 -0
  18. package/src/commands/decisions.mjs +59 -10
  19. package/src/commands/delta.mjs +263 -49
  20. package/src/commands/diff.mjs +45 -0
  21. package/src/commands/discover.mjs +37 -0
  22. package/src/commands/drift.mjs +45 -0
  23. package/src/commands/evolution.mjs +22 -2
  24. package/src/commands/explain-capability.mjs +11 -0
  25. package/src/commands/explain.mjs +24 -0
  26. package/src/commands/fitness.mjs +119 -10
  27. package/src/commands/govern-capability.mjs +16 -0
  28. package/src/commands/graph.mjs +39 -0
  29. package/src/commands/health.mjs +34 -0
  30. package/src/commands/history.mjs +85 -2
  31. package/src/commands/impact.mjs +23 -0
  32. package/src/commands/inspect-capability.mjs +16 -0
  33. package/src/commands/plan-context-command.mjs +35 -0
  34. package/src/commands/policy.mjs +42 -4
  35. package/src/commands/provenance-command.mjs +15 -24
  36. package/src/commands/report.mjs +38 -0
  37. package/src/commands/rules-capability.mjs +16 -0
  38. package/src/commands/rules.mjs +3 -1
  39. package/src/commands/scenario.mjs +23 -0
  40. package/src/commands/trajectory.mjs +35 -0
  41. package/src/commands/waivers.mjs +30 -0
  42. package/src/corpus/goldens/.gitkeep +0 -0
  43. package/src/corpus/goldens/adr.json +62 -0
  44. package/src/corpus/goldens/adr.text +6 -0
  45. package/src/corpus/goldens/change.json +98 -0
  46. package/src/corpus/goldens/change.text +6 -0
  47. package/src/corpus/goldens/check.json +90 -0
  48. package/src/corpus/goldens/check.sarif +479 -0
  49. package/src/corpus/goldens/check.text +9 -0
  50. package/src/corpus/goldens/context.json +47 -0
  51. package/src/corpus/goldens/context.text +6 -0
  52. package/src/corpus/goldens/debt.json +59 -0
  53. package/src/corpus/goldens/debt.text +7 -0
  54. package/src/corpus/goldens/decisions.json +74 -0
  55. package/src/corpus/goldens/decisions.text +7 -0
  56. package/src/corpus/goldens/delta.json +94 -0
  57. package/src/corpus/goldens/delta.sarif +472 -0
  58. package/src/corpus/goldens/delta.text +5 -0
  59. package/src/corpus/goldens/diff.json +58 -0
  60. package/src/corpus/goldens/diff.text +4 -0
  61. package/src/corpus/goldens/discover.json +77 -0
  62. package/src/corpus/goldens/discover.text +10 -0
  63. package/src/corpus/goldens/drift.json +43 -0
  64. package/src/corpus/goldens/drift.text +3 -0
  65. package/src/corpus/goldens/evolution.json +328 -0
  66. package/src/corpus/goldens/evolution.text +32 -0
  67. package/src/corpus/goldens/explain.json +60 -0
  68. package/src/corpus/goldens/explain.text +7 -0
  69. package/src/corpus/goldens/fitness.json +45 -0
  70. package/src/corpus/goldens/fitness.text +3 -0
  71. package/src/corpus/goldens/graph.json +78 -0
  72. package/src/corpus/goldens/graph.text +10 -0
  73. package/src/corpus/goldens/health.json +72 -0
  74. package/src/corpus/goldens/health.text +10 -0
  75. package/src/corpus/goldens/help.text +104 -0
  76. package/src/corpus/goldens/history.json +43 -0
  77. package/src/corpus/goldens/history.text +4 -0
  78. package/src/corpus/goldens/impact.json +327 -0
  79. package/src/corpus/goldens/impact.text +10 -0
  80. package/src/corpus/goldens/provenance.json +453 -0
  81. package/src/corpus/goldens/provenance.text +14 -0
  82. package/src/corpus/goldens/reconcile.json +168 -0
  83. package/src/corpus/goldens/reconcile.text +3 -0
  84. package/src/corpus/goldens/report.json +168 -0
  85. package/src/corpus/goldens/report.text +42 -0
  86. package/src/corpus/goldens/rules verify.json +0 -0
  87. package/src/corpus/goldens/rules verify.text +0 -0
  88. package/src/corpus/goldens/scenario.json +441 -0
  89. package/src/corpus/goldens/scenario.text +28 -0
  90. package/src/corpus/goldens/trajectory.json +83 -0
  91. package/src/corpus/goldens/trajectory.text +10 -0
  92. package/src/corpus/goldens/usage-error.text +105 -0
  93. package/src/corpus/goldens/waivers.json +40 -0
  94. package/src/corpus/goldens/waivers.text +1 -0
  95. package/src/corpus/goldens-lsp/initialize.result.json +15 -0
  96. package/src/corpus/goldens-lsp/publish-cleared-empty.json +4 -0
  97. package/src/corpus/goldens-lsp/publish-fixed-empty.json +5 -0
  98. package/src/corpus/goldens-lsp/publish-unparseable.json +54 -0
  99. package/src/corpus/goldens-lsp/publish-violation.json +22 -0
  100. package/src/corpus/goldens-lsp/register-watched-files.json +72 -0
  101. package/src/go-work.mjs +8 -23
  102. package/src/governance/evolution-event.mjs +15 -0
  103. package/src/governance/evolution-store.mjs +59 -31
  104. package/src/governance/provenance-record.mjs +0 -152
  105. package/src/governance/verdict.mjs +26 -3
  106. package/src/intent/intent-manifest.json +14 -14
  107. package/src/lsp/server.mjs +5 -8
  108. package/src/lsp/workspace-index.mjs +55 -220
  109. package/src/options.mjs +10 -0
  110. package/src/providers/native/discover.mjs +13 -12
  111. package/src/providers/native/model.mjs +8 -3
  112. package/src/providers/nx-static.mjs +231 -0
  113. package/src/report/json.mjs +3 -2
  114. package/src/report/sarif.mjs +8 -3
  115. package/src/rules/edge-constraints.mjs +9 -0
  116. package/src/rules/index.mjs +26 -10
  117. package/src/rules/messages.mjs +64 -14
  118. package/src/rules/specifiers.mjs +21 -20
  119. package/src/tsconfig-paths.mjs +8 -14
  120. package/src/verdict.mjs +127 -16
@@ -0,0 +1,104 @@
1
+ archkeep — module-boundary enforcement across every language in the workspace
2
+
3
+ Usage:
4
+ archkeep check [<path>...] Check imports against the boundary rules
5
+ archkeep graph Print the project graph as a deterministic snapshot
6
+ archkeep diff <baseline> Compare two graph snapshots edge by edge
7
+ archkeep delta <baseline> | --capture Classify how boundary violations moved between a captured baseline and head
8
+ archkeep change <baseline> --intent <file> Reconcile a declared change intent against the architectural delta
9
+ archkeep discover [--propose] Report observed facts, and optionally propose candidate architecture
10
+ archkeep drift Compare the observed architecture against the declared intent
11
+ archkeep reconcile Compare the declared intent against the observed architecture
12
+ archkeep waivers List the boundary waivers and permanent suppressions on the table
13
+ archkeep fitness Judge every declared fitness function against the workspace
14
+ archkeep history <dir> Describe how the architecture evolved across snapshots
15
+ archkeep trajectory <dir> Aggregate the deterministic drift trajectory across snapshots
16
+ archkeep evolution Describe how the architecture evolved across a Git revision range
17
+ archkeep health [<snapshot-dir>] Describe architecture health metrics and trends
18
+ archkeep report [<snapshot-dir>] One governance document: how healthy the architecture is, and why
19
+ archkeep debt <dir> Print the architecture-debt ledger across snapshots
20
+ archkeep impact <project> List projects that depend on the named project
21
+ archkeep scenario <project> Evaluate a hypothetical change against the current workspace
22
+ archkeep explain <file:line:column> Explain the judgment for one import site
23
+ archkeep context <project> [--plan [<path>...]] Show the architecture constraints that apply to a project
24
+ archkeep provenance Describe where this run's facts came from and which rows carry an origin
25
+ archkeep decisions <id> Walk the full chain behind one recorded decision — decision to bound rows, projects, findings, and its verification level
26
+ archkeep adr [<id>] List recorded architecture decisions and what each binds
27
+ archkeep rules <list|info|verify|add> [<rule-name>] List official rules, show details, verify catalog integrity, or add a rule
28
+ archkeep --help Show this message
29
+
30
+ Options:
31
+ --format text|sarif|json Terminal report (default), SARIF 2.1.0 for GitHub
32
+ code scanning, or the versioned JSON envelope
33
+ docs/reference/json-output.md documents
34
+ --output <file> Write the report to a file instead of stdout
35
+ --config <file> Read the boundary law from here instead of
36
+ <workspace root>/module-boundaries.config.mjs
37
+ --evidence-out <dir> Also write each custom rule's evidence bundle
38
+ into this existing directory, as <rule>.json —
39
+ the exact document the rule was judged over
40
+ --capture Print an evidence snapshot of the current tree
41
+ (raw import records, graph, coverage, policy
42
+ fingerprint) for a later delta run to compare against.
43
+ Without --output, the snapshot goes to stdout.
44
+ --event-out <dir> Append the delta's evolution event to this directory
45
+ (one canonical record per transition; idempotent — a
46
+ rerun over the same transition writes nothing new).
47
+ Absent: no event file is written
48
+ --intent <file> The change-intent manifest declaring the material
49
+ architectural consequences this change expects
50
+ (required; see docs/usage/change.md)
51
+ --propose Also derive candidate components, boundaries, tags and rules from
52
+ the observed facts — every candidate marked proposed and not
53
+ authoritative; nothing is ever written
54
+ --write-intent <file> Write the proposed components and rules to a valid
55
+ architecture-intent.json file. Only valid with --propose;
56
+ refuses to overwrite a file that already exists — the file
57
+ is a candidate for drift/reconcile and must be reviewed
58
+ before use
59
+ --base <rev> The baseline revision — a commit, branch, tag,
60
+ or HEAD~n; the first revision analyzed
61
+ --head <rev> The tip revision (default HEAD); must be a
62
+ linear descendant of --base with no merges between
63
+ --events <dir> Link the evolution event store in <dir> so debt entries carry
64
+ introducedBy/resolvedBy refs and a resolved list
65
+ --scenario-file <file> Path to the scenario JSON file describing the
66
+ hypothetical changes to evaluate
67
+ --plan Request the agent planning context: current
68
+ architecture, applicable policy (with Intent),
69
+ impact, current violations, drift, coverage, and
70
+ the commands that verify the change. Trailing
71
+ paths scope the change; deterministic and
72
+ never an LLM plan — Archkeep produces facts,
73
+ agents produce plans.
74
+ --history-dir <dir> Path to the workspace's history directory. When given and
75
+ the directory holds archived snapshots, the planning context
76
+ includes the architecture-debt snapshot: current violations,
77
+ exemptions, and gaps aged across the history. Used only with
78
+ `--plan`; ignored otherwise.
79
+ --catalog <path> Path to the catalog file
80
+ (default: node_modules/@ecoma-io/archkeep-rules/catalog.json)
81
+ --to <dir> Target directory for the .wasm file
82
+ (default: tools/rules/)
83
+
84
+ Projects and tags come from the project graph (archkeep.json or Nx); the rules come from
85
+ module-boundaries.config.mjs at the workspace root — the same table ESLint
86
+ reads, so both enforcers answer from one source. That filename is a
87
+ convention and can be changed per workspace: through archkeep.json's
88
+ `boundaryConfig` field, or the integration's `boundaryConfig` option in nx.json.
89
+
90
+ Naming paths scopes the run to those files. That is a fast local pre-check and
91
+ not the gate: the cycle and lazy-load rules judge the file graph as a whole, so
92
+ a scoped run can miss what a whole-workspace run would find.
93
+
94
+ A workspace with a go.work at its root also has its use list compared against
95
+ every project's go.mod, whatever paths scope the run — a module in one list
96
+ and not the other means a developer's go build and CI build different trees.
97
+
98
+ A workspace whose tsconfig declares a paths table also has each alias judged
99
+ for life: an alias whose every target points into directories that do not
100
+ exist resolves no import, so it fails the run the way a violation does. The
101
+ table itself is never re-resolved — the check reads the same parsed tsconfig
102
+ the import resolver uses.
103
+
104
+ Exit codes: 0 clean · 1 findings (violations, go.work drift, dead path aliases) · 2 usage error · 3 no verdict (a file could not be analyzed, or the run could not start)
@@ -0,0 +1,43 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "tool": {
4
+ "name": "@ecoma-io/archkeep",
5
+ "version": "0.26.0"
6
+ },
7
+ "command": "history",
8
+ "workspace": {
9
+ "root": "/tmp/archkeep-golden-fixture",
10
+ "provider": "native",
11
+ "marker": "archkeep.json",
12
+ "provenance": {
13
+ "commit": "1fd51709c377d99a6139891e1200291cf08aede9",
14
+ "remote": null,
15
+ "dirty": false
16
+ }
17
+ },
18
+ "status": "ok",
19
+ "exitCode": 0,
20
+ "coverage": {
21
+ "complete": true,
22
+ "projects": 3,
23
+ "analyzedFiles": 3,
24
+ "imports": 2,
25
+ "notAnalyzed": [],
26
+ "blindSpots": [],
27
+ "notes": [
28
+ "rule-impact cannot be recomputed from stored snapshots — snapshots carry the graph and the policy fingerprint, not the constraint table or import sites. Run `check` at any commit for the boundary verdict; run `diff` for one transition's rule-impact."
29
+ ]
30
+ },
31
+ "result": {
32
+ "dir": "/tmp/archkeep-golden-fixture/.archkeep-history",
33
+ "captured": null,
34
+ "snapshots": [
35
+ {
36
+ "name": "0001-71341a04.json",
37
+ "id": "71341a045f02564205e125994a50536459cf3114afd89c0523982de8cbfc02ae"
38
+ }
39
+ ],
40
+ "transitions": []
41
+ }
42
+ }
43
+
@@ -0,0 +1,4 @@
1
+ history /tmp/archkeep-golden-fixture/.archkeep-history
2
+ 1 snapshot, 0 transitions (2 imports in 3 files across 3 projects)
3
+ 0 0001-71341a04.json 71341a04
4
+ ✔ one snapshot, no transitions yet — capture again after an architectural change
@@ -0,0 +1,327 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "tool": {
4
+ "name": "@ecoma-io/archkeep",
5
+ "version": "0.26.0"
6
+ },
7
+ "command": "impact",
8
+ "workspace": {
9
+ "root": "/tmp/archkeep-golden-fixture",
10
+ "provider": "native",
11
+ "marker": "archkeep.json",
12
+ "provenance": {
13
+ "commit": "1fd51709c377d99a6139891e1200291cf08aede9",
14
+ "remote": null,
15
+ "dirty": false
16
+ }
17
+ },
18
+ "status": "ok",
19
+ "exitCode": 0,
20
+ "coverage": {
21
+ "complete": true,
22
+ "projects": 3,
23
+ "analyzedFiles": 3,
24
+ "imports": 2,
25
+ "notAnalyzed": [],
26
+ "blindSpots": [],
27
+ "notes": [
28
+ "per-edge violations cover only depConstraints (3 of 15 violation types). A dependent with no violations here may still violate npm-ban, circular-dependency, lazy-load, or other rules that require import-site details. Run `check` for the complete verdict."
29
+ ]
30
+ },
31
+ "result": {
32
+ "project": "core",
33
+ "direct": [
34
+ "api"
35
+ ],
36
+ "transitive": [
37
+ "app"
38
+ ],
39
+ "dependents": [
40
+ "api",
41
+ "app"
42
+ ],
43
+ "constraintImpact": [
44
+ {
45
+ "project": "api",
46
+ "edges": [
47
+ {
48
+ "target": "core",
49
+ "type": "static"
50
+ }
51
+ ],
52
+ "constraintRows": [
53
+ {
54
+ "sourceTag": "layer:api",
55
+ "onlyDependOnLibsWithTags": [
56
+ "layer:api",
57
+ "layer:core"
58
+ ]
59
+ }
60
+ ],
61
+ "violations": []
62
+ },
63
+ {
64
+ "project": "app",
65
+ "edges": [],
66
+ "constraintRows": [
67
+ {
68
+ "sourceTag": "layer:app",
69
+ "onlyDependOnLibsWithTags": [
70
+ "layer:app",
71
+ "layer:api",
72
+ "layer:core"
73
+ ]
74
+ }
75
+ ],
76
+ "violations": []
77
+ }
78
+ ],
79
+ "impactStatement": {
80
+ "project": "core",
81
+ "impact": {
82
+ "project": "core",
83
+ "direct": [
84
+ "api"
85
+ ],
86
+ "transitive": [
87
+ "app"
88
+ ],
89
+ "dependents": [
90
+ "api",
91
+ "app"
92
+ ]
93
+ },
94
+ "evolutionAlignment": {
95
+ "projects": [
96
+ "api",
97
+ "app",
98
+ "core"
99
+ ],
100
+ "boundaries": [
101
+ "api>core:static"
102
+ ],
103
+ "constraints": [
104
+ "sourceTag:layer:api",
105
+ "sourceTag:layer:app"
106
+ ],
107
+ "decisions": []
108
+ },
109
+ "findingsImpact": {
110
+ "evaluated": false,
111
+ "findings": [],
112
+ "count": 0
113
+ },
114
+ "debtImpact": {
115
+ "evaluated": false,
116
+ "debt": [],
117
+ "count": 0
118
+ },
119
+ "boundaryImpact": {
120
+ "boundaries": [
121
+ {
122
+ "source": "api",
123
+ "target": "core",
124
+ "type": "static",
125
+ "crossesLayer": true,
126
+ "violated": false,
127
+ "governingConstraintRows": 1
128
+ }
129
+ ],
130
+ "evaluated": true
131
+ },
132
+ "completeness": {
133
+ "domains": {
134
+ "structural": {
135
+ "status": "evaluated",
136
+ "evaluated": true,
137
+ "partial": false,
138
+ "notEvaluated": false,
139
+ "unsupported": false,
140
+ "refused": false,
141
+ "note": ""
142
+ },
143
+ "constraint": {
144
+ "status": "evaluated",
145
+ "evaluated": true,
146
+ "partial": false,
147
+ "notEvaluated": false,
148
+ "unsupported": false,
149
+ "refused": false,
150
+ "note": ""
151
+ },
152
+ "boundary": {
153
+ "status": "evaluated",
154
+ "evaluated": true,
155
+ "partial": false,
156
+ "notEvaluated": false,
157
+ "unsupported": false,
158
+ "refused": false,
159
+ "note": ""
160
+ },
161
+ "decision": {
162
+ "status": "evaluated",
163
+ "evaluated": true,
164
+ "partial": false,
165
+ "notEvaluated": false,
166
+ "unsupported": false,
167
+ "refused": false,
168
+ "note": ""
169
+ },
170
+ "findings": {
171
+ "status": "not_evaluated",
172
+ "evaluated": false,
173
+ "partial": false,
174
+ "notEvaluated": true,
175
+ "unsupported": false,
176
+ "refused": false,
177
+ "note": ""
178
+ },
179
+ "debt": {
180
+ "status": "not_evaluated",
181
+ "evaluated": false,
182
+ "partial": false,
183
+ "notEvaluated": true,
184
+ "unsupported": false,
185
+ "refused": false,
186
+ "note": ""
187
+ },
188
+ "governance": {
189
+ "status": "not_evaluated",
190
+ "evaluated": false,
191
+ "partial": false,
192
+ "notEvaluated": true,
193
+ "unsupported": false,
194
+ "refused": false,
195
+ "note": "Governance incomplete — findings: not_evaluated, debt: not_evaluated"
196
+ },
197
+ "evidence": {
198
+ "status": "evaluated",
199
+ "evaluated": true,
200
+ "partial": false,
201
+ "notEvaluated": false,
202
+ "unsupported": false,
203
+ "refused": false,
204
+ "note": ""
205
+ }
206
+ },
207
+ "overallComplete": false,
208
+ "overallStatus": "not_evaluated",
209
+ "evidenceComplete": {
210
+ "contractType": "canonical",
211
+ "domainCoverage": 0.625,
212
+ "claimEvidenceCoverage": 1,
213
+ "causalCoverage": 1,
214
+ "provenanceCoverage": 0,
215
+ "mutationCoverage": 0,
216
+ "surfaceParity": 0,
217
+ "hiddenGapCount": 2,
218
+ "falseCompleteCount": 0,
219
+ "baseIdentityValid": false,
220
+ "deterministic": true,
221
+ "overallStatus": "incomplete",
222
+ "overallComplete": false,
223
+ "gates": {
224
+ "domainCoverage": {
225
+ "value": 0.625,
226
+ "pass": false,
227
+ "required": true
228
+ },
229
+ "claimEvidenceCoverage": {
230
+ "value": 1,
231
+ "pass": true,
232
+ "required": true
233
+ },
234
+ "causalCoverage": {
235
+ "value": 1,
236
+ "pass": true,
237
+ "required": true
238
+ },
239
+ "provenanceCoverage": {
240
+ "value": 0,
241
+ "pass": false,
242
+ "required": true
243
+ },
244
+ "mutationCoverage": {
245
+ "value": 0,
246
+ "pass": false,
247
+ "required": false
248
+ },
249
+ "surfaceParity": {
250
+ "value": 0,
251
+ "pass": false,
252
+ "required": false
253
+ },
254
+ "hiddenGapCount": {
255
+ "value": 2,
256
+ "pass": false,
257
+ "required": true
258
+ },
259
+ "falseCompleteCount": {
260
+ "value": 0,
261
+ "pass": true,
262
+ "required": true
263
+ },
264
+ "baseIdentityValid": {
265
+ "value": false,
266
+ "pass": false,
267
+ "required": false
268
+ },
269
+ "deterministic": {
270
+ "value": true,
271
+ "pass": true,
272
+ "required": true
273
+ }
274
+ }
275
+ },
276
+ "hiddenGapCount": 2
277
+ },
278
+ "complete": false,
279
+ "notes": [
280
+ "constraint impact covers only depConstraints (3 of 15 violation types). A project with no violations here may still violate other rules that require import-site details. Run `check` for the complete verdict.",
281
+ "finding impact not evaluated — no findings data provided to impact statement. Pass findings data for complete governance evaluation.",
282
+ "debt impact not evaluated — no debt data provided to impact statement. Pass debt data for complete governance evaluation."
283
+ ],
284
+ "constraintImpact": [
285
+ {
286
+ "project": "api",
287
+ "edges": [
288
+ {
289
+ "target": "core",
290
+ "type": "static"
291
+ }
292
+ ],
293
+ "constraintRows": [
294
+ {
295
+ "sourceTag": "layer:api",
296
+ "onlyDependOnLibsWithTags": [
297
+ "layer:api",
298
+ "layer:core"
299
+ ]
300
+ }
301
+ ],
302
+ "violations": []
303
+ },
304
+ {
305
+ "project": "app",
306
+ "edges": [],
307
+ "constraintRows": [
308
+ {
309
+ "sourceTag": "layer:app",
310
+ "onlyDependOnLibsWithTags": [
311
+ "layer:app",
312
+ "layer:api",
313
+ "layer:core"
314
+ ]
315
+ }
316
+ ],
317
+ "violations": []
318
+ }
319
+ ],
320
+ "decisionImpact": {
321
+ "decisions": [],
322
+ "unresolvedDecisionRefs": []
323
+ }
324
+ }
325
+ }
326
+ }
327
+
@@ -0,0 +1,10 @@
1
+ ✔ impact complete (2 imports in 3 files across 3 projects)
2
+ Impact of core
3
+ api
4
+ app
5
+ Constraint context
6
+ ✔ api
7
+ [layer:api] only [layer:api, layer:core]
8
+ ✔ app
9
+ [layer:app] only [layer:app, layer:api, layer:core]
10
+ 2 projects depend on core (direct: 1, transitive: 1)