arkgate 2.8.3 → 2.9.1
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 +80 -0
- package/README.md +2 -1
- package/bin/ark-check.mjs +21 -3
- package/bin/ark-layer-match.mjs +88 -5
- package/bin/ark-mcp.mjs +7 -70
- package/bin/ark-shared.mjs +88 -8
- package/bin/ark.mjs +134 -22
- package/bin/lib/agent-gates.mjs +37 -3
- package/bin/lib/architecture-scan.mjs +16 -4
- package/bin/lib/config-warnings.mjs +11 -2
- package/bin/lib/doctor-plan.mjs +20 -0
- package/bin/lib/field-install.mjs +334 -0
- package/bin/lib/import-resolve.mjs +133 -0
- package/bin/lib/presets.mjs +207 -11
- package/bin/lib/remediation.mjs +15 -0
- package/bin/lib/suggestions.mjs +8 -3
- package/dist/eslint/index.cjs +63 -5
- package/dist/eslint/index.cjs.map +1 -1
- package/dist/eslint/index.d.cts +33 -1
- package/dist/eslint/index.d.ts +33 -1
- package/dist/eslint/index.js +63 -5
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.cjs +103 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -9
- package/dist/index.d.ts +21 -9
- package/dist/index.js +103 -14
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +78 -4
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/nestjs/index.js +78 -4
- package/dist/nestjs/index.js.map +1 -1
- package/dist/runtime/index.cjs +103 -14
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +103 -14
- package/dist/runtime/index.js.map +1 -1
- package/dist/{types-CSJhEOk2.d.cts → types-D6Q8WHes.d.cts} +7 -0
- package/dist/{types-CSJhEOk2.d.ts → types-D6Q8WHes.d.ts} +7 -0
- package/docs/agent-guide.md +55 -4
- package/package.json +3 -1
- package/server.json +2 -2
- package/templates/architecture-playbook.json +65 -1
- package/templates/policy-packs/enthusiast-ddd-bounded-contexts.json +19 -0
- package/templates/policy-packs/enthusiast-ui-surface.json +18 -0
- package/templates/policy-packs/enthusiast-vertical-slice.json +18 -0
- package/templates/skills/ark-adopt.md +4 -0
- package/templates/skills/ark-architect.md +5 -1
- package/templates/skills/ark-autopilot.md +10 -1
- package/templates/skills/ark-fix.md +3 -0
- package/templates/skills/ark-place.md +7 -0
- package/templates/skills/ark-think.md +43 -0
package/bin/lib/presets.mjs
CHANGED
|
@@ -19,16 +19,35 @@ export function denyUpward(names) {
|
|
|
19
19
|
return rules;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* peerIsolation matrix: deny only when importer/importee sit under different slices.
|
|
24
|
+
* Covers same-layer and cross-layer pairs (honest DDD / vertical-slice isolation).
|
|
25
|
+
*/
|
|
26
|
+
export function peerIsolationEdges(layerNames, sliceFolders, message) {
|
|
27
|
+
const rules = [];
|
|
28
|
+
for (const from of layerNames) {
|
|
29
|
+
for (const to of layerNames) {
|
|
30
|
+
rules.push({
|
|
31
|
+
from,
|
|
32
|
+
to,
|
|
33
|
+
allowed: false,
|
|
34
|
+
peerIsolation: true,
|
|
35
|
+
sliceFolders,
|
|
36
|
+
...(message ? { message } : {}),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return rules;
|
|
41
|
+
}
|
|
42
|
+
|
|
22
43
|
// Named starter configs. Globs use `**` so they fit both flat (src/domain/**) and
|
|
23
44
|
// modular (src/modules/x/domain/**) layouts. Every layer is optional, so the strict
|
|
24
45
|
// check passes on a greenfield repo and each layer switches on as its dir gains files.
|
|
25
46
|
//
|
|
26
|
-
// Framework internals
|
|
27
|
-
//
|
|
28
|
-
// (
|
|
29
|
-
|
|
30
|
-
// author who really does keep app code under kernel/ can drop the exclude.
|
|
31
|
-
export const FRAMEWORK_INTERNAL_EXCLUDE = ['**/kernel/**'];
|
|
47
|
+
// Framework internals under `src/kernel/**` are NOT application architecture — a broad
|
|
48
|
+
// `src/**/domain/**` would otherwise swallow `src/kernel/domain`. Do NOT use `**/kernel/**`
|
|
49
|
+
// (that carves out legitimate `src/shared/kernel/**` SharedKernel paths).
|
|
50
|
+
export const FRAMEWORK_INTERNAL_EXCLUDE = ['src/kernel/**', '**/src/kernel/**'];
|
|
32
51
|
export function presetWithOverlays(baseConfig, root) {
|
|
33
52
|
if (!root) return baseConfig;
|
|
34
53
|
return applyFrameworkLayoutOverlays(baseConfig, root);
|
|
@@ -153,20 +172,22 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
153
172
|
'feature-sliced': (_workspaces, root) => {
|
|
154
173
|
const order = ['App', 'Pages', 'Widgets', 'Features', 'Entities', 'Shared'];
|
|
155
174
|
const purpose = {
|
|
156
|
-
App: 'App-wide setup, providers, and routing.',
|
|
157
|
-
Pages: 'Route-level compositions.',
|
|
175
|
+
App: 'App-wide setup, providers, and routing (FSD app/ + Next app router when co-located under src/app).',
|
|
176
|
+
Pages: 'Route-level compositions (FSD pages/ and Next pages router).',
|
|
158
177
|
Widgets: 'Self-contained UI blocks composed from features and entities.',
|
|
159
178
|
Features: 'User-facing feature units.',
|
|
160
179
|
Entities: 'Business entities with their UI and logic.',
|
|
161
180
|
Shared: 'Reusable primitives with no business knowledge.',
|
|
162
181
|
};
|
|
182
|
+
// Canonical FSD under src/<layer>/**; also root <layer>/** for packages that hoist segments.
|
|
183
|
+
const fsdPatterns = (dir) => [`src/${dir}/**`, `${dir}/**`];
|
|
163
184
|
return presetWithOverlays(
|
|
164
185
|
{
|
|
165
|
-
include: ['src'],
|
|
186
|
+
include: ['src', 'app', 'pages'],
|
|
166
187
|
layers: order.map((name) => ({
|
|
167
188
|
name,
|
|
168
189
|
description: purpose[name],
|
|
169
|
-
patterns:
|
|
190
|
+
patterns: fsdPatterns(name.toLowerCase()),
|
|
170
191
|
optional: true,
|
|
171
192
|
})),
|
|
172
193
|
rules: denyUpward(order),
|
|
@@ -185,7 +206,8 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
185
206
|
const resolved = resolveIncludeRoots(root);
|
|
186
207
|
if (resolved.length > 0) include = resolved;
|
|
187
208
|
}
|
|
188
|
-
|
|
209
|
+
// Turborepo: apps/ + packages/; Nx enterprise: apps/ + libs/ (+ packages/).
|
|
210
|
+
if (include.length === 0) include = ['packages', 'apps', 'libs'];
|
|
189
211
|
return presetWithOverlays(
|
|
190
212
|
{
|
|
191
213
|
include,
|
|
@@ -377,8 +399,182 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
377
399
|
},
|
|
378
400
|
root
|
|
379
401
|
),
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Vertical Slice: feature folders own UI+logic+api; no cross-feature imports
|
|
405
|
+
* (peerIsolation). Shared primitives and pure lib/infra are the only escape hatches.
|
|
406
|
+
*/
|
|
407
|
+
'vertical-slice': (_workspaces, root) =>
|
|
408
|
+
presetWithOverlays(
|
|
409
|
+
{
|
|
410
|
+
include: ['src'],
|
|
411
|
+
layers: [
|
|
412
|
+
{
|
|
413
|
+
name: 'Features',
|
|
414
|
+
description:
|
|
415
|
+
'Feature / use-case slices (co-located API, UI, hooks, types). No import across sibling slices.',
|
|
416
|
+
patterns: ['src/features/**', 'src/modules/**'],
|
|
417
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
418
|
+
optional: true,
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: 'Shared',
|
|
422
|
+
description: 'Reusable UI primitives, utils, and types with no feature knowledge.',
|
|
423
|
+
patterns: ['src/shared/**'],
|
|
424
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
425
|
+
optional: true,
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
name: 'Lib',
|
|
429
|
+
description: 'Infrastructure clients (db, HTTP, env) shared across features.',
|
|
430
|
+
patterns: ['src/lib/**', 'src/infra/**', 'src/infrastructure/**'],
|
|
431
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
432
|
+
optional: true,
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: 'App',
|
|
436
|
+
description: 'App shell, routing, providers, composition root.',
|
|
437
|
+
patterns: ['src/app/**', 'app/**', 'src/pages/**', 'pages/**'],
|
|
438
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
439
|
+
optional: true,
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
rules: [
|
|
443
|
+
{
|
|
444
|
+
from: 'Features',
|
|
445
|
+
to: 'Features',
|
|
446
|
+
allowed: false,
|
|
447
|
+
peerIsolation: true,
|
|
448
|
+
sliceFolders: ['features', 'modules'],
|
|
449
|
+
message:
|
|
450
|
+
'Features must not import other feature slices. Extract shared code to Shared/Lib or coordinate via events.',
|
|
451
|
+
},
|
|
452
|
+
// Features must not pull in the composition root (re-coupling via App).
|
|
453
|
+
{ from: 'Features', to: 'App', allowed: false },
|
|
454
|
+
{ from: 'Shared', to: 'Features', allowed: false },
|
|
455
|
+
{ from: 'Shared', to: 'App', allowed: false },
|
|
456
|
+
{ from: 'Lib', to: 'Features', allowed: false },
|
|
457
|
+
{ from: 'Lib', to: 'Shared', allowed: false },
|
|
458
|
+
{ from: 'Lib', to: 'App', allowed: false },
|
|
459
|
+
// App may compose Features + Shared + Lib (no deny).
|
|
460
|
+
// Features may import Shared + Lib (no deny).
|
|
461
|
+
],
|
|
462
|
+
},
|
|
463
|
+
root
|
|
464
|
+
),
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* DDD bounded contexts: per-context domain/application/infra/presentation + shared kernel.
|
|
468
|
+
* peerIsolation on every pair of context-local layers blocks cross-context imports
|
|
469
|
+
* (same or cross technical layer). SharedKernel is exempt (not in the peer matrix).
|
|
470
|
+
* Classic hexagonal denies still block e.g. Domain → Persistence within a context.
|
|
471
|
+
*/
|
|
472
|
+
'ddd-bounded-contexts': (_workspaces, root) => {
|
|
473
|
+
const contextLayers = [
|
|
474
|
+
'DomainModel',
|
|
475
|
+
'ApplicationOrchestration',
|
|
476
|
+
'PresentationAdapters',
|
|
477
|
+
'PersistenceAdapters',
|
|
478
|
+
];
|
|
479
|
+
const sliceFolders = ['contexts', 'bounded-contexts'];
|
|
480
|
+
return presetWithOverlays(
|
|
481
|
+
{
|
|
482
|
+
include: ['src'],
|
|
483
|
+
layers: [
|
|
484
|
+
{
|
|
485
|
+
name: 'DomainModel',
|
|
486
|
+
description:
|
|
487
|
+
'Per-context pure domain (entities, VOs, domain events). No I/O, no framework.',
|
|
488
|
+
patterns: [
|
|
489
|
+
'src/contexts/**/domain/**',
|
|
490
|
+
'src/bounded-contexts/**/domain/**',
|
|
491
|
+
'src/**/domain/**',
|
|
492
|
+
],
|
|
493
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
494
|
+
forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS,
|
|
495
|
+
optional: true,
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
name: 'ApplicationOrchestration',
|
|
499
|
+
description: 'Per-context use cases / application services.',
|
|
500
|
+
patterns: [
|
|
501
|
+
'src/contexts/**/application/**',
|
|
502
|
+
'src/bounded-contexts/**/application/**',
|
|
503
|
+
'src/**/application/**',
|
|
504
|
+
],
|
|
505
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
506
|
+
optional: true,
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: 'PresentationAdapters',
|
|
510
|
+
description: 'Per-context controllers, HTTP, UI adapters.',
|
|
511
|
+
patterns: [
|
|
512
|
+
'src/contexts/**/presentation/**',
|
|
513
|
+
'src/contexts/**/controllers/**',
|
|
514
|
+
'src/bounded-contexts/**/presentation/**',
|
|
515
|
+
'src/**/presentation/**',
|
|
516
|
+
'src/**/controllers/**',
|
|
517
|
+
'src/**/http/**',
|
|
518
|
+
],
|
|
519
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
520
|
+
optional: true,
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: 'PersistenceAdapters',
|
|
524
|
+
description: 'Per-context infrastructure: repositories, DB, external APIs.',
|
|
525
|
+
patterns: [
|
|
526
|
+
'src/contexts/**/infrastructure/**',
|
|
527
|
+
'src/contexts/**/adapters/**',
|
|
528
|
+
'src/bounded-contexts/**/infrastructure/**',
|
|
529
|
+
'src/**/infrastructure/**',
|
|
530
|
+
'src/**/adapters/**',
|
|
531
|
+
'src/**/persistence/**',
|
|
532
|
+
'src/**/repositories/**',
|
|
533
|
+
],
|
|
534
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
535
|
+
optional: true,
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: 'SharedKernel',
|
|
539
|
+
description: 'Truly shared kernel types and primitives across contexts.',
|
|
540
|
+
patterns: ['src/shared/kernel/**', 'src/shared/**'],
|
|
541
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
542
|
+
optional: true,
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
rules: [
|
|
546
|
+
{ from: 'DomainModel', to: 'ApplicationOrchestration', allowed: false },
|
|
547
|
+
{ from: 'DomainModel', to: 'PersistenceAdapters', allowed: false },
|
|
548
|
+
{ from: 'DomainModel', to: 'PresentationAdapters', allowed: false },
|
|
549
|
+
{ from: 'ApplicationOrchestration', to: 'PersistenceAdapters', allowed: false },
|
|
550
|
+
{ from: 'ApplicationOrchestration', to: 'PresentationAdapters', allowed: false },
|
|
551
|
+
{ from: 'PresentationAdapters', to: 'PersistenceAdapters', allowed: false },
|
|
552
|
+
{ from: 'PresentationAdapters', to: 'DomainModel', allowed: false },
|
|
553
|
+
{ from: 'PersistenceAdapters', to: 'ApplicationOrchestration', allowed: false },
|
|
554
|
+
{ from: 'PersistenceAdapters', to: 'PresentationAdapters', allowed: false },
|
|
555
|
+
{ from: 'SharedKernel', to: 'DomainModel', allowed: false },
|
|
556
|
+
{ from: 'SharedKernel', to: 'ApplicationOrchestration', allowed: false },
|
|
557
|
+
{ from: 'SharedKernel', to: 'PresentationAdapters', allowed: false },
|
|
558
|
+
{ from: 'SharedKernel', to: 'PersistenceAdapters', allowed: false },
|
|
559
|
+
...peerIsolationEdges(
|
|
560
|
+
contextLayers,
|
|
561
|
+
sliceFolders,
|
|
562
|
+
'Bounded contexts must not import each other. Use shared kernel or integration events.'
|
|
563
|
+
),
|
|
564
|
+
],
|
|
565
|
+
},
|
|
566
|
+
root
|
|
567
|
+
);
|
|
568
|
+
},
|
|
380
569
|
};
|
|
381
570
|
|
|
571
|
+
// Aliases: Clean / Onion map to the hexagonal factory (same matrix + globs). Avoid dual maintenance.
|
|
572
|
+
ARCHITECTURE_PRESETS['clean-architecture'] = ARCHITECTURE_PRESETS.hexagonal;
|
|
573
|
+
ARCHITECTURE_PRESETS['onion-architecture'] = ARCHITECTURE_PRESETS.hexagonal;
|
|
574
|
+
|
|
575
|
+
/** Stable public preset keys (CLI help, score fit, docs). Order is display order. */
|
|
576
|
+
export const ARCHITECTURE_PRESET_NAMES = Object.keys(ARCHITECTURE_PRESETS);
|
|
577
|
+
|
|
382
578
|
// ── Layer suggestion engine ──────────────────────────────────────────────────
|
|
383
579
|
// Everything here is HARVESTED from Ark's own canonical sources — the 11-layer defaults
|
|
384
580
|
// (DEFAULT_LAYER_DIRECTORIES) and the named presets — so a suggestion can never drift from
|
package/bin/lib/remediation.mjs
CHANGED
|
@@ -24,6 +24,7 @@ export const MECHANICAL_SAFE_KINDS = [
|
|
|
24
24
|
export const KNOWN_FIX_CLASSES = [
|
|
25
25
|
'file-move',
|
|
26
26
|
'port-inversion',
|
|
27
|
+
'cross-slice-boundary',
|
|
27
28
|
'inject-port',
|
|
28
29
|
'registered-intent',
|
|
29
30
|
'add-source-metadata',
|
|
@@ -39,6 +40,14 @@ export const KNOWN_FIX_CLASSES = [
|
|
|
39
40
|
export function classifyRemediation(violation) {
|
|
40
41
|
const ruleId = violation?.ruleId;
|
|
41
42
|
if (ruleId === 'LAYER_IMPORT_VIOLATION') {
|
|
43
|
+
// Cross-slice peer isolation is always judgment (extract shared / events — not mechanical).
|
|
44
|
+
if (violation?.peerIsolation) {
|
|
45
|
+
return {
|
|
46
|
+
class: 'judgment',
|
|
47
|
+
confidence: 0.9,
|
|
48
|
+
rationale: 'peerIsolation blocks cross-slice imports: extract to shared, use events/ports, or redesign ownership — not a mechanical auto-fix.',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
42
51
|
// Single invariant: runtime module loads are never mechanical-safe.
|
|
43
52
|
const edgeKind = violation?.edgeKind;
|
|
44
53
|
if (edgeKind === 'require' || edgeKind === 'dynamic-import') {
|
|
@@ -131,6 +140,12 @@ export function enrichViolationWithFixClass(violation) {
|
|
|
131
140
|
? 'The imported module only exports types — use `import type` and place the type in a layer both sides may share.'
|
|
132
141
|
: 'This is a type-only import — move the type to a layer both sides may share, or relocate the file to match its role.';
|
|
133
142
|
}
|
|
143
|
+
else if (violation.peerIsolation) {
|
|
144
|
+
enriched.fixClass = 'cross-slice-boundary';
|
|
145
|
+
enriched.effort = 'medium';
|
|
146
|
+
enriched.enthusiastHint =
|
|
147
|
+
'Cross-slice import blocked (peerIsolation). Do not import another feature/context directly — extract shared code to a shared layer, or coordinate via events/ports. Moving code across slices is a judgment call, not a mechanical auto-fix.';
|
|
148
|
+
}
|
|
134
149
|
else {
|
|
135
150
|
enriched.fixClass = 'port-inversion';
|
|
136
151
|
enriched.effort = 'medium';
|
package/bin/lib/suggestions.mjs
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { DEFAULT_LAYER_DIRECTORIES } from '../ark-shared.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ARCHITECTURE_PRESETS,
|
|
8
|
+
ARCHITECTURE_PRESET_NAMES,
|
|
9
|
+
CANONICAL_LAYER_NAMES,
|
|
10
|
+
} from './presets.mjs';
|
|
7
11
|
|
|
8
12
|
export function dirSegmentsFromGlob(pattern) {
|
|
9
13
|
return String(pattern)
|
|
@@ -31,7 +35,8 @@ export function layerByDir() {
|
|
|
31
35
|
// (services→Application, components/pages→Presentation, data/infrastructure→Persistence…)
|
|
32
36
|
// map cleanly onto the same taxonomy. feature-sliced uses a different vocabulary
|
|
33
37
|
// (Widgets/Entities/…) that doesn't reduce to the 11, so it's covered by model-fit, not here.
|
|
34
|
-
for (const preset of
|
|
38
|
+
for (const preset of ARCHITECTURE_PRESET_NAMES) {
|
|
39
|
+
// feature-sliced uses a different vocabulary (App/Pages/…) — still harvest dirs.
|
|
35
40
|
for (const layer of ARCHITECTURE_PRESETS[preset]([]).layers) {
|
|
36
41
|
if (!CANONICAL_LAYER_NAMES.has(layer.name)) continue;
|
|
37
42
|
for (const pattern of layer.patterns ?? []) {
|
|
@@ -67,7 +72,7 @@ export function suggestLayerForPath(relDir) {
|
|
|
67
72
|
// `ark init --preset <name>`. null when nothing lines up.
|
|
68
73
|
export function detectBestFitModel(dirBasenames) {
|
|
69
74
|
const present = new Set(dirBasenames);
|
|
70
|
-
const scored =
|
|
75
|
+
const scored = ARCHITECTURE_PRESET_NAMES.map((name) => {
|
|
71
76
|
const segments = new Set();
|
|
72
77
|
for (const layer of ARCHITECTURE_PRESETS[name]([]).layers) {
|
|
73
78
|
for (const pattern of layer.patterns ?? []) {
|
package/dist/eslint/index.cjs
CHANGED
|
@@ -156,10 +156,64 @@ function layerForRelativePath(relPath, layers) {
|
|
|
156
156
|
}
|
|
157
157
|
return bestName;
|
|
158
158
|
}
|
|
159
|
-
function
|
|
160
|
-
if (
|
|
161
|
-
const
|
|
162
|
-
|
|
159
|
+
function sliceIdForPath(relPath, sliceFolders) {
|
|
160
|
+
if (!sliceFolders?.length) return void 0;
|
|
161
|
+
const parts = String(relPath).split(/[/\\]/).filter(Boolean);
|
|
162
|
+
const folders = new Set(sliceFolders.map((s) => String(s).toLowerCase()));
|
|
163
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
164
|
+
if (folders.has(parts[i].toLowerCase())) {
|
|
165
|
+
return `${parts[i]}/${parts[i + 1]}`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
function inferSliceFoldersFromPatterns(patterns) {
|
|
171
|
+
const out = /* @__PURE__ */ new Set();
|
|
172
|
+
for (const pattern of patterns ?? []) {
|
|
173
|
+
const glob = normalizeGlobSeparators(String(pattern));
|
|
174
|
+
const parts = glob.split("/").filter(Boolean);
|
|
175
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
176
|
+
const part = parts[i];
|
|
177
|
+
if ((part === "**" || part === "*") && i > 0) {
|
|
178
|
+
const prev = parts[i - 1];
|
|
179
|
+
if (prev && !prev.includes("*") && !prev.includes("{") && !prev.includes("}")) {
|
|
180
|
+
out.add(prev);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return [...out];
|
|
186
|
+
}
|
|
187
|
+
function resolveSliceFolders(rule, layerName, layers) {
|
|
188
|
+
if (Array.isArray(rule.sliceFolders) && rule.sliceFolders.length > 0) {
|
|
189
|
+
return rule.sliceFolders.filter((s) => typeof s === "string" && s.length > 0);
|
|
190
|
+
}
|
|
191
|
+
const layer = (layers ?? []).find((l) => l.name === layerName);
|
|
192
|
+
return inferSliceFoldersFromPatterns(layer?.patterns);
|
|
193
|
+
}
|
|
194
|
+
function findDeniedEdgeRule(rules2, from, to, options) {
|
|
195
|
+
for (const rule of rules2 ?? []) {
|
|
196
|
+
if (rule.from !== from || rule.to !== to) continue;
|
|
197
|
+
if (rule.allowed !== false) continue;
|
|
198
|
+
if (rule.peerIsolation) {
|
|
199
|
+
const fromPath = options?.fromPath;
|
|
200
|
+
const toPath = options?.toPath;
|
|
201
|
+
if (!fromPath || !toPath) continue;
|
|
202
|
+
const folders = resolveSliceFolders(rule, from, options?.layers);
|
|
203
|
+
if (folders.length === 0) continue;
|
|
204
|
+
const fromSlice = sliceIdForPath(fromPath, folders);
|
|
205
|
+
const toSlice = sliceIdForPath(toPath, folders);
|
|
206
|
+
if (!fromSlice || !toSlice) continue;
|
|
207
|
+
if (fromSlice !== toSlice) return rule;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (from === to) continue;
|
|
211
|
+
return rule;
|
|
212
|
+
}
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
function isEdgeDenied(rules2, from, to, options) {
|
|
216
|
+
return findDeniedEdgeRule(rules2, from, to, options) !== void 0;
|
|
163
217
|
}
|
|
164
218
|
|
|
165
219
|
// src/eslint/index.ts
|
|
@@ -302,7 +356,11 @@ var noDomainInfraImports = {
|
|
|
302
356
|
if (relTarget.startsWith("..")) return;
|
|
303
357
|
const toLayer = layerForRelativePath(relTarget, config.layers);
|
|
304
358
|
if (!toLayer) return;
|
|
305
|
-
if (isEdgeDenied(config.rules, fromLayer, toLayer
|
|
359
|
+
if (isEdgeDenied(config.rules, fromLayer, toLayer, {
|
|
360
|
+
fromPath: relFile,
|
|
361
|
+
toPath: relTarget,
|
|
362
|
+
layers: config.layers
|
|
363
|
+
})) {
|
|
306
364
|
context.report({
|
|
307
365
|
node,
|
|
308
366
|
messageId: "forbiddenImport",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/eslint/index.ts","../../src/domain/layerMatch.ts"],"sourcesContent":["/**\n * arkgate/eslint — editor-side architecture gate.\n *\n * Layer / import / forbidden-globals rules load `ark.config.json` from the linted\n * project (walk-up from the file) and use the same glob specificity + edge semantics\n * as ark-check. Matching primitives come from the canonical\n * `src/domain/layerMatch.ts` (CLI loads the generated `bin/ark-layer-match.mjs`) —\n * no Kernel imports.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport {\n globToRegExp,\n patternSpecificity,\n layerForRelativePath,\n isEdgeDenied,\n} from '../domain/layerMatch';\n\nexport { globToRegExp, patternSpecificity, layerForRelativePath, isEdgeDenied };\n\ntype RuleContext = {\n report(descriptor: Record<string, unknown>): void;\n /** ESLint 9+ / 10: preferred path on the context object. */\n filename?: string;\n /** ESLint 8-style physical path when linting with processors / virtual files. */\n physicalFilename?: string;\n /** ESLint ≤8 API — still present on some hosts; removed in ESLint 10. */\n getFilename?: () => string;\n options?: unknown[];\n};\n\n/** Resolve the file path being linted across ESLint 8–10 context shapes. */\nfunction lintedFilename(context: RuleContext): string {\n if (typeof context.physicalFilename === 'string' && context.physicalFilename.length > 0) {\n return context.physicalFilename;\n }\n if (typeof context.filename === 'string' && context.filename.length > 0) {\n return context.filename;\n }\n if (typeof context.getFilename === 'function') {\n try {\n const name = context.getFilename();\n if (typeof name === 'string' && name.length > 0) return name;\n } catch {\n /* ignore */\n }\n }\n return '';\n}\n\ntype RuleListener = Record<string, (node: AstNode) => void>;\n\ntype AstNode = {\n type?: string;\n name?: string;\n value?: unknown;\n source?: AstNode;\n callee?: AstNode;\n object?: AstNode;\n property?: AstNode;\n key?: AstNode;\n arguments?: AstNode[];\n properties?: AstNode[];\n importKind?: string;\n specifiers?: AstNode[];\n};\n\ntype ArkRule = {\n meta: {\n type: 'problem';\n docs: { description: string };\n messages: Record<string, string>;\n schema: unknown[];\n };\n create(context: RuleContext): RuleListener;\n};\n\ntype ArkEslintPlugin = {\n rules: Record<string, ArkRule>;\n configs?: Record<string, unknown>;\n};\n\ntype LayerConfig = {\n name: string;\n patterns?: string[];\n exclude?: string[];\n forbiddenGlobals?: string[];\n};\n\ntype EdgeRule = { from: string; to: string; allowed?: boolean };\n\ntype ArkConfig = {\n layers?: LayerConfig[];\n rules?: EdgeRule[];\n};\n\n// ── Config I/O (editor-only; matching primitives come from ark-layer-match.mjs) ──\n\nexport function findConfigPath(startFile: string): string | null {\n if (!startFile || startFile === '<input>' || startFile.startsWith('stdin')) return null;\n let dir = path.dirname(path.resolve(startFile));\n for (;;) {\n const candidate = path.join(dir, 'ark.config.json');\n if (fs.existsSync(candidate)) return candidate;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nconst _configCache = new Map<string, ArkConfig | null>();\n\nexport function loadArkConfig(configPath: string): ArkConfig | null {\n if (_configCache.has(configPath)) return _configCache.get(configPath) ?? null;\n try {\n const raw = JSON.parse(fs.readFileSync(configPath, 'utf8')) as ArkConfig;\n _configCache.set(configPath, raw);\n return raw;\n } catch {\n _configCache.set(configPath, null);\n return null;\n }\n}\n\n/** Resolve relative import specifier to an absolute path candidate (TS-oriented). */\nexport function resolveRelativeImport(fromFile: string, specifier: string): string | null {\n if (!specifier.startsWith('.')) return null;\n const base = path.resolve(path.dirname(fromFile), specifier);\n const candidates = [\n base,\n `${base}.ts`,\n `${base}.tsx`,\n `${base}.mts`,\n `${base}.cts`,\n `${base}.js`,\n `${base}.jsx`,\n path.join(base, 'index.ts'),\n path.join(base, 'index.tsx'),\n path.join(base, 'index.js'),\n ];\n for (const c of candidates) {\n try {\n if (fs.existsSync(c) && fs.statSync(c).isFile()) return c;\n } catch {\n /* continue */\n }\n }\n // Prefer .ts for layer matching when the target is not on disk yet (editor typing).\n return `${base}.ts`;\n}\n\n// ── AST helpers ────────────────────────────────────────────────────────────\n\nfunction stringValue(node: AstNode | undefined): string | undefined {\n return typeof node?.value === 'string' ? node.value : undefined;\n}\n\nfunction propertyName(node: AstNode | undefined): string | undefined {\n return node?.name ?? stringValue(node);\n}\n\nfunction calleePropertyName(node: AstNode): string | undefined {\n return propertyName(node.callee?.property);\n}\n\nfunction objectProperty(node: AstNode | undefined, name: string): AstNode | undefined {\n return node?.properties?.find((property) => propertyName(property.key) === name);\n}\n\nfunction objectHasProperty(node: AstNode | undefined, name: string): boolean {\n return objectProperty(node, name) !== undefined;\n}\n\nfunction objectHasMetadataSource(node: AstNode | undefined): boolean {\n const metadata = objectProperty(node, 'metadata')?.value as AstNode | undefined;\n return objectHasProperty(metadata, 'source');\n}\n\nfunction looksLikeIntent(value: string): boolean {\n return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\\.[A-Za-z0-9_.]+$/.test(\n value\n );\n}\n\nfunction isPublishCall(node: AstNode): boolean {\n return calleePropertyName(node) === 'publish';\n}\n\n/** Heuristic fallback when no ark.config.json (pre-contract projects). */\nfunction isDomainFileHeuristic(filename: string): boolean {\n const normalized = filename.split('\\\\').join('/').toLowerCase();\n return normalized.includes('/domain/') || normalized.endsWith('/domain.ts');\n}\n\nfunction isInfraImportHeuristic(specifier: string): boolean {\n const normalized = specifier.toLowerCase();\n return [\n 'adapter',\n 'adapters',\n 'infrastructure',\n 'persistence',\n 'repository',\n 'repositories',\n 'integration',\n 'database',\n 'db',\n ].some((token) => normalized.includes(token));\n}\n\nconst DEFAULT_FORBIDDEN_GLOBALS = ['fetch', 'process', 'Date.now', 'Math.random'];\n\n// ── Rules ──────────────────────────────────────────────────────────────────\n\n/**\n * Config-driven layer import boundary (primary editor gate).\n * Replaces path-token domain/infra heuristics when ark.config.json is present.\n * Rule id kept as `no-domain-infra-imports` for recommended-config / upgrade stability.\n */\nexport const noDomainInfraImports: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check). Falls back to domain→infra path heuristics when no config is found.',\n },\n messages: {\n forbiddenImport:\n 'Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}',\n forbiddenImportHeuristic:\n 'Domain code must not import infrastructure, adapters, repositories, or database modules.',\n },\n schema: [],\n },\n create(context) {\n const filename = lintedFilename(context);\n const configPath = findConfigPath(filename);\n const config = configPath ? loadArkConfig(configPath) : null;\n const root = configPath ? path.dirname(configPath) : null;\n\n const check = (node: AstNode) => {\n const source = stringValue(node.source);\n if (!source) return;\n\n if (config && root && filename) {\n const absFile = path.isAbsolute(filename) ? filename : path.resolve(filename);\n const relFile = path.relative(root, absFile).split(path.sep).join('/');\n const fromLayer = layerForRelativePath(relFile, config.layers);\n if (!fromLayer) return;\n\n const targetAbs = resolveRelativeImport(absFile, source);\n if (!targetAbs) return; // package import — CI resolves via TS; editor skips non-relative\n\n const relTarget = path.relative(root, targetAbs).split(path.sep).join('/');\n // Outside project or up-and-out: skip\n if (relTarget.startsWith('..')) return;\n\n const toLayer = layerForRelativePath(relTarget, config.layers);\n if (!toLayer) return;\n if (isEdgeDenied(config.rules, fromLayer, toLayer)) {\n context.report({\n node,\n messageId: 'forbiddenImport',\n data: { fromLayer, toLayer, specifier: source },\n });\n }\n return;\n }\n\n // No contract: legacy heuristic so bare domain folders still get a signal.\n if (isDomainFileHeuristic(filename) && isInfraImportHeuristic(source)) {\n context.report({ node, messageId: 'forbiddenImportHeuristic' });\n }\n };\n\n return {\n ImportDeclaration: check,\n ExportNamedDeclaration: check,\n ExportAllDeclaration: check,\n };\n },\n};\n\nexport const noRawEventPublish: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings.',\n },\n messages: {\n rawPublish:\n 'Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const firstValue = stringValue(firstArg);\n if ((firstValue && looksLikeIntent(firstValue)) || objectHasProperty(firstArg, 'intent')) {\n context.report({ node, messageId: 'rawPublish' });\n }\n },\n };\n },\n};\n\nexport const requirePublishSource: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require event bus publish calls to include source metadata.',\n },\n messages: {\n missingSource: 'Strict Ark publish calls must include metadata.source.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const metadataArg = node.arguments?.[2];\n if (objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, 'source')) {\n return;\n }\n context.report({ node, messageId: 'missingSource' });\n },\n };\n },\n};\n\nexport const noForbiddenGlobals: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow ambient globals from the layer’s forbiddenGlobals in ark.config.json (same purity surface as arkgate-check). Option `globals` overrides. Without config, defaults apply only on domain-like paths.',\n },\n messages: {\n forbiddenGlobal:\n 'Ambient global \"{{name}}\" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',\n forbiddenGlobalDefault:\n 'Ambient global \"{{name}}\" is forbidden here; inject the capability through a port instead.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n globals: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n ],\n },\n create(context) {\n const filename = lintedFilename(context);\n const option = context.options?.[0] as { globals?: string[] } | undefined;\n const configPath = findConfigPath(filename);\n const config = configPath ? loadArkConfig(configPath) : null;\n const root = configPath ? path.dirname(configPath) : null;\n\n let globals: Set<string> | null = null;\n let layerName = 'this layer';\n\n if (option?.globals) {\n globals = new Set(option.globals);\n } else if (config && root && filename) {\n const absFile = path.isAbsolute(filename) ? filename : path.resolve(filename);\n const relFile = path.relative(root, absFile).split(path.sep).join('/');\n const layer = config.layers?.find(\n (l) => l.name === layerForRelativePath(relFile, config.layers)\n );\n if (layer?.forbiddenGlobals?.length) {\n globals = new Set(layer.forbiddenGlobals);\n layerName = layer.name;\n } else {\n // Layer has no purity list — do not invent defaults (matches CI).\n globals = null;\n }\n } else if (isDomainFileHeuristic(filename)) {\n globals = new Set(DEFAULT_FORBIDDEN_GLOBALS);\n }\n\n if (!globals) {\n return {} as RuleListener;\n }\n\n const report = (node: AstNode, name: string) =>\n context.report({\n node,\n messageId: config ? 'forbiddenGlobal' : 'forbiddenGlobalDefault',\n data: { name, layer: layerName },\n });\n\n return {\n MemberExpression(node) {\n const base = node.object?.type === 'Identifier' ? node.object.name : undefined;\n if (!base) return;\n const dotted = `${base}.${propertyName(node.property) ?? ''}`;\n if (globals!.has(dotted)) report(node, dotted);\n else if (globals!.has(base)) report(node, base);\n },\n CallExpression(node) {\n const callee = node.callee?.type === 'Identifier' ? node.callee.name : undefined;\n if (callee && globals!.has(callee)) report(node, callee);\n },\n NewExpression(node) {\n const callee = node.callee?.type === 'Identifier' ? node.callee.name : undefined;\n if (callee && globals!.has(callee)) report(node, callee);\n },\n };\n },\n};\n\nconst rules = {\n 'no-domain-infra-imports': noDomainInfraImports,\n 'no-raw-event-publish': noRawEventPublish,\n 'require-publish-source': requirePublishSource,\n 'no-forbidden-globals': noForbiddenGlobals,\n};\n\nconst plugin: ArkEslintPlugin = { rules };\n\nplugin.configs = {\n recommended: {\n plugins: { ark: plugin },\n rules: {\n 'ark/no-domain-infra-imports': 'error',\n 'ark/no-raw-event-publish': 'error',\n 'ark/require-publish-source': 'error',\n 'ark/no-forbidden-globals': 'error',\n },\n },\n};\n\nexport { plugin };\nexport default plugin;\n","/**\n * Pure layer-glob matching for ark.config.json.\n *\n * **Canonical algorithm** for CLI, ESLint, and library consumers.\n * The CLI load path uses the generated `bin/ark-layer-match.mjs`\n * (`npm run generate:layer-match` / `npm run check:layer-match`).\n * Behavioral parity tests remain a safety net.\n */\n\nexport type LayerConfig = {\n name: string;\n patterns?: string[];\n exclude?: string[];\n forbiddenGlobals?: string[];\n};\n\nexport type EdgeRule = { from: string; to: string; allowed?: boolean };\n\nconst regexpCache = new Map<string, RegExp>();\n\nfunction escapeLiteral(ch: string): string {\n return /[.*+?^${}()|[\\]\\\\]/.test(ch) ? `\\\\${ch}` : ch;\n}\n\n/**\n * Normalize path separators to `/` without destroying glob escape sequences.\n * `src\\domain\\x` → `src/domain/x` (Windows paths); `src/\\{legacy\\}/**` keeps `\\{` / `\\}`.\n * A plain `pattern.split('\\\\').join('/')` would eat those escapes.\n */\nfunction normalizeGlobSeparators(pattern: string): string {\n let out = '';\n for (let i = 0; i < pattern.length; i += 1) {\n const c = pattern[i];\n if (c === '\\\\' && i + 1 < pattern.length) {\n const next = pattern[i + 1];\n // Keep escapes for glob metacharacters (and escaped backslash).\n if ('*?{}[],'.includes(next) || next === '\\\\') {\n out += '\\\\' + next;\n i += 1;\n continue;\n }\n // Otherwise treat `\\` as a path separator (Windows).\n out += '/';\n continue;\n }\n out += c;\n }\n return out;\n}\n\nfunction bracesBalanced(glob: string): boolean {\n let depth = 0;\n for (let i = 0; i < glob.length; i += 1) {\n const c = glob[i];\n if (c === '\\\\') {\n i += 1;\n continue;\n }\n if (c === '{') depth += 1;\n else if (c === '}') {\n depth -= 1;\n if (depth < 0) return false;\n }\n }\n return depth === 0;\n}\n\nexport function globToRegExp(pattern: string): RegExp {\n const cached = regexpCache.get(pattern);\n if (cached) return cached;\n\n const glob = normalizeGlobSeparators(pattern);\n const useBraces = bracesBalanced(glob);\n let out = '';\n let braceDepth = 0;\n for (let i = 0; i < glob.length; i += 1) {\n const c = glob[i];\n if (c === '\\\\' && i + 1 < glob.length) {\n out += escapeLiteral(glob[i + 1]);\n i += 1;\n } else if (c === '*') {\n if (glob[i + 1] === '*') {\n if (glob[i + 2] === '/') {\n out += '(?:.*/)?';\n i += 2;\n } else {\n out += '.*';\n i += 1;\n }\n } else {\n out += '[^/]*';\n }\n } else if (c === '?') {\n out += '[^/]';\n } else if (c === '{' && useBraces) {\n out += '(?:';\n braceDepth += 1;\n } else if (c === '}' && useBraces && braceDepth > 0) {\n out += ')';\n braceDepth -= 1;\n } else if (c === ',' && useBraces && braceDepth > 0) {\n out += '|';\n } else {\n out += escapeLiteral(c);\n }\n }\n const re = new RegExp(`^${out}$`);\n regexpCache.set(pattern, re);\n return re;\n}\n\nexport function patternSpecificity(pattern: string): number {\n const glob = normalizeGlobSeparators(String(pattern));\n const beforeWildcard = glob.split('*')[0];\n const literalSegments = beforeWildcard.split('/').filter(Boolean).length;\n const literalLength = glob.replace(/\\*/g, '').length;\n return literalSegments * 10000 + literalLength;\n}\n\nexport function layerForRelativePath(\n relPath: string,\n layers: LayerConfig[] | undefined\n): string | undefined {\n // File paths (not globs): any OS separator → posix relative.\n const rel = String(relPath).split(/[/\\\\]/).join('/');\n let bestName: string | undefined;\n let bestScore = -1;\n for (const layer of layers ?? []) {\n if ((layer.exclude ?? []).some((pattern) => globToRegExp(pattern).test(rel))) {\n continue;\n }\n for (const pattern of layer.patterns ?? []) {\n if (globToRegExp(pattern).test(rel)) {\n const score = patternSpecificity(pattern);\n if (score > bestScore) {\n bestScore = score;\n bestName = layer.name;\n }\n }\n }\n }\n return bestName;\n}\n\nexport function isEdgeDenied(\n rules: EdgeRule[] | undefined,\n from: string,\n to: string\n): boolean {\n if (from === to) return false;\n const hit = (rules ?? []).find((r) => r.from === from && r.to === to);\n return hit?.allowed === false;\n}\n\n/** Codegen globs skipped by default scan (emitted into the CLI derived matcher). */\nexport const DEFAULT_GENERATED_FILE_GLOBS = [\n '**/*.gen.ts',\n '**/*.gen.tsx',\n '**/*.generated.ts',\n '**/*.generated.tsx',\n];\n\nexport type ScanExcludeConfig = {\n exclude?: string[];\n excludeGenerated?: boolean;\n};\n\nexport function scanExcludePatterns(config?: ScanExcludeConfig | null): string[] {\n const custom = Array.isArray(config?.exclude)\n ? config!.exclude!.filter((p) => typeof p === 'string')\n : [];\n const generated =\n config?.excludeGenerated === false ? [] : DEFAULT_GENERATED_FILE_GLOBS;\n return [...generated, ...custom];\n}\n\nexport function isScanExcludedRelative(\n relPath: string,\n config?: ScanExcludeConfig | null\n): boolean {\n const rel = String(relPath).split(/[/\\\\]/).join('/');\n return scanExcludePatterns(config).some((pattern) => globToRegExp(pattern).test(rel));\n}\n"],"mappings":"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","names":["rules","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../../src/eslint/index.ts","../../src/domain/layerMatch.ts"],"sourcesContent":["/**\n * arkgate/eslint — editor-side architecture gate.\n *\n * Layer / import / forbidden-globals rules load `ark.config.json` from the linted\n * project (walk-up from the file) and use the same glob specificity + edge semantics\n * as ark-check. Matching primitives come from the canonical\n * `src/domain/layerMatch.ts` (CLI loads the generated `bin/ark-layer-match.mjs`) —\n * no Kernel imports.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport {\n globToRegExp,\n patternSpecificity,\n layerForRelativePath,\n isEdgeDenied,\n} from '../domain/layerMatch';\n\nexport { globToRegExp, patternSpecificity, layerForRelativePath, isEdgeDenied };\n\ntype RuleContext = {\n report(descriptor: Record<string, unknown>): void;\n /** ESLint 9+ / 10: preferred path on the context object. */\n filename?: string;\n /** ESLint 8-style physical path when linting with processors / virtual files. */\n physicalFilename?: string;\n /** ESLint ≤8 API — still present on some hosts; removed in ESLint 10. */\n getFilename?: () => string;\n options?: unknown[];\n};\n\n/** Resolve the file path being linted across ESLint 8–10 context shapes. */\nfunction lintedFilename(context: RuleContext): string {\n if (typeof context.physicalFilename === 'string' && context.physicalFilename.length > 0) {\n return context.physicalFilename;\n }\n if (typeof context.filename === 'string' && context.filename.length > 0) {\n return context.filename;\n }\n if (typeof context.getFilename === 'function') {\n try {\n const name = context.getFilename();\n if (typeof name === 'string' && name.length > 0) return name;\n } catch {\n /* ignore */\n }\n }\n return '';\n}\n\ntype RuleListener = Record<string, (node: AstNode) => void>;\n\ntype AstNode = {\n type?: string;\n name?: string;\n value?: unknown;\n source?: AstNode;\n callee?: AstNode;\n object?: AstNode;\n property?: AstNode;\n key?: AstNode;\n arguments?: AstNode[];\n properties?: AstNode[];\n importKind?: string;\n specifiers?: AstNode[];\n};\n\ntype ArkRule = {\n meta: {\n type: 'problem';\n docs: { description: string };\n messages: Record<string, string>;\n schema: unknown[];\n };\n create(context: RuleContext): RuleListener;\n};\n\ntype ArkEslintPlugin = {\n rules: Record<string, ArkRule>;\n configs?: Record<string, unknown>;\n};\n\ntype LayerConfig = {\n name: string;\n patterns?: string[];\n exclude?: string[];\n forbiddenGlobals?: string[];\n};\n\ntype EdgeRule = { from: string; to: string; allowed?: boolean };\n\ntype ArkConfig = {\n layers?: LayerConfig[];\n rules?: EdgeRule[];\n};\n\n// ── Config I/O (editor-only; matching primitives come from ark-layer-match.mjs) ──\n\nexport function findConfigPath(startFile: string): string | null {\n if (!startFile || startFile === '<input>' || startFile.startsWith('stdin')) return null;\n let dir = path.dirname(path.resolve(startFile));\n for (;;) {\n const candidate = path.join(dir, 'ark.config.json');\n if (fs.existsSync(candidate)) return candidate;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nconst _configCache = new Map<string, ArkConfig | null>();\n\nexport function loadArkConfig(configPath: string): ArkConfig | null {\n if (_configCache.has(configPath)) return _configCache.get(configPath) ?? null;\n try {\n const raw = JSON.parse(fs.readFileSync(configPath, 'utf8')) as ArkConfig;\n _configCache.set(configPath, raw);\n return raw;\n } catch {\n _configCache.set(configPath, null);\n return null;\n }\n}\n\n/** Resolve relative import specifier to an absolute path candidate (TS-oriented). */\nexport function resolveRelativeImport(fromFile: string, specifier: string): string | null {\n if (!specifier.startsWith('.')) return null;\n const base = path.resolve(path.dirname(fromFile), specifier);\n const candidates = [\n base,\n `${base}.ts`,\n `${base}.tsx`,\n `${base}.mts`,\n `${base}.cts`,\n `${base}.js`,\n `${base}.jsx`,\n path.join(base, 'index.ts'),\n path.join(base, 'index.tsx'),\n path.join(base, 'index.js'),\n ];\n for (const c of candidates) {\n try {\n if (fs.existsSync(c) && fs.statSync(c).isFile()) return c;\n } catch {\n /* continue */\n }\n }\n // Prefer .ts for layer matching when the target is not on disk yet (editor typing).\n return `${base}.ts`;\n}\n\n// ── AST helpers ────────────────────────────────────────────────────────────\n\nfunction stringValue(node: AstNode | undefined): string | undefined {\n return typeof node?.value === 'string' ? node.value : undefined;\n}\n\nfunction propertyName(node: AstNode | undefined): string | undefined {\n return node?.name ?? stringValue(node);\n}\n\nfunction calleePropertyName(node: AstNode): string | undefined {\n return propertyName(node.callee?.property);\n}\n\nfunction objectProperty(node: AstNode | undefined, name: string): AstNode | undefined {\n return node?.properties?.find((property) => propertyName(property.key) === name);\n}\n\nfunction objectHasProperty(node: AstNode | undefined, name: string): boolean {\n return objectProperty(node, name) !== undefined;\n}\n\nfunction objectHasMetadataSource(node: AstNode | undefined): boolean {\n const metadata = objectProperty(node, 'metadata')?.value as AstNode | undefined;\n return objectHasProperty(metadata, 'source');\n}\n\nfunction looksLikeIntent(value: string): boolean {\n return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\\.[A-Za-z0-9_.]+$/.test(\n value\n );\n}\n\nfunction isPublishCall(node: AstNode): boolean {\n return calleePropertyName(node) === 'publish';\n}\n\n/** Heuristic fallback when no ark.config.json (pre-contract projects). */\nfunction isDomainFileHeuristic(filename: string): boolean {\n const normalized = filename.split('\\\\').join('/').toLowerCase();\n return normalized.includes('/domain/') || normalized.endsWith('/domain.ts');\n}\n\nfunction isInfraImportHeuristic(specifier: string): boolean {\n const normalized = specifier.toLowerCase();\n return [\n 'adapter',\n 'adapters',\n 'infrastructure',\n 'persistence',\n 'repository',\n 'repositories',\n 'integration',\n 'database',\n 'db',\n ].some((token) => normalized.includes(token));\n}\n\nconst DEFAULT_FORBIDDEN_GLOBALS = ['fetch', 'process', 'Date.now', 'Math.random'];\n\n// ── Rules ──────────────────────────────────────────────────────────────────\n\n/**\n * Config-driven layer import boundary (primary editor gate).\n * Replaces path-token domain/infra heuristics when ark.config.json is present.\n * Rule id kept as `no-domain-infra-imports` for recommended-config / upgrade stability.\n */\nexport const noDomainInfraImports: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check). Falls back to domain→infra path heuristics when no config is found.',\n },\n messages: {\n forbiddenImport:\n 'Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}',\n forbiddenImportHeuristic:\n 'Domain code must not import infrastructure, adapters, repositories, or database modules.',\n },\n schema: [],\n },\n create(context) {\n const filename = lintedFilename(context);\n const configPath = findConfigPath(filename);\n const config = configPath ? loadArkConfig(configPath) : null;\n const root = configPath ? path.dirname(configPath) : null;\n\n const check = (node: AstNode) => {\n const source = stringValue(node.source);\n if (!source) return;\n\n if (config && root && filename) {\n const absFile = path.isAbsolute(filename) ? filename : path.resolve(filename);\n const relFile = path.relative(root, absFile).split(path.sep).join('/');\n const fromLayer = layerForRelativePath(relFile, config.layers);\n if (!fromLayer) return;\n\n const targetAbs = resolveRelativeImport(absFile, source);\n if (!targetAbs) return; // package import — CI resolves via TS; editor skips non-relative\n\n const relTarget = path.relative(root, targetAbs).split(path.sep).join('/');\n // Outside project or up-and-out: skip\n if (relTarget.startsWith('..')) return;\n\n const toLayer = layerForRelativePath(relTarget, config.layers);\n if (!toLayer) return;\n if (\n isEdgeDenied(config.rules, fromLayer, toLayer, {\n fromPath: relFile,\n toPath: relTarget,\n layers: config.layers,\n })\n ) {\n context.report({\n node,\n messageId: 'forbiddenImport',\n data: { fromLayer, toLayer, specifier: source },\n });\n }\n return;\n }\n\n // No contract: legacy heuristic so bare domain folders still get a signal.\n if (isDomainFileHeuristic(filename) && isInfraImportHeuristic(source)) {\n context.report({ node, messageId: 'forbiddenImportHeuristic' });\n }\n };\n\n return {\n ImportDeclaration: check,\n ExportNamedDeclaration: check,\n ExportAllDeclaration: check,\n };\n },\n};\n\nexport const noRawEventPublish: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings.',\n },\n messages: {\n rawPublish:\n 'Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const firstValue = stringValue(firstArg);\n if ((firstValue && looksLikeIntent(firstValue)) || objectHasProperty(firstArg, 'intent')) {\n context.report({ node, messageId: 'rawPublish' });\n }\n },\n };\n },\n};\n\nexport const requirePublishSource: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require event bus publish calls to include source metadata.',\n },\n messages: {\n missingSource: 'Strict Ark publish calls must include metadata.source.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const metadataArg = node.arguments?.[2];\n if (objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, 'source')) {\n return;\n }\n context.report({ node, messageId: 'missingSource' });\n },\n };\n },\n};\n\nexport const noForbiddenGlobals: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow ambient globals from the layer’s forbiddenGlobals in ark.config.json (same purity surface as arkgate-check). Option `globals` overrides. Without config, defaults apply only on domain-like paths.',\n },\n messages: {\n forbiddenGlobal:\n 'Ambient global \"{{name}}\" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',\n forbiddenGlobalDefault:\n 'Ambient global \"{{name}}\" is forbidden here; inject the capability through a port instead.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n globals: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n ],\n },\n create(context) {\n const filename = lintedFilename(context);\n const option = context.options?.[0] as { globals?: string[] } | undefined;\n const configPath = findConfigPath(filename);\n const config = configPath ? loadArkConfig(configPath) : null;\n const root = configPath ? path.dirname(configPath) : null;\n\n let globals: Set<string> | null = null;\n let layerName = 'this layer';\n\n if (option?.globals) {\n globals = new Set(option.globals);\n } else if (config && root && filename) {\n const absFile = path.isAbsolute(filename) ? filename : path.resolve(filename);\n const relFile = path.relative(root, absFile).split(path.sep).join('/');\n const layer = config.layers?.find(\n (l) => l.name === layerForRelativePath(relFile, config.layers)\n );\n if (layer?.forbiddenGlobals?.length) {\n globals = new Set(layer.forbiddenGlobals);\n layerName = layer.name;\n } else {\n // Layer has no purity list — do not invent defaults (matches CI).\n globals = null;\n }\n } else if (isDomainFileHeuristic(filename)) {\n globals = new Set(DEFAULT_FORBIDDEN_GLOBALS);\n }\n\n if (!globals) {\n return {} as RuleListener;\n }\n\n const report = (node: AstNode, name: string) =>\n context.report({\n node,\n messageId: config ? 'forbiddenGlobal' : 'forbiddenGlobalDefault',\n data: { name, layer: layerName },\n });\n\n return {\n MemberExpression(node) {\n const base = node.object?.type === 'Identifier' ? node.object.name : undefined;\n if (!base) return;\n const dotted = `${base}.${propertyName(node.property) ?? ''}`;\n if (globals!.has(dotted)) report(node, dotted);\n else if (globals!.has(base)) report(node, base);\n },\n CallExpression(node) {\n const callee = node.callee?.type === 'Identifier' ? node.callee.name : undefined;\n if (callee && globals!.has(callee)) report(node, callee);\n },\n NewExpression(node) {\n const callee = node.callee?.type === 'Identifier' ? node.callee.name : undefined;\n if (callee && globals!.has(callee)) report(node, callee);\n },\n };\n },\n};\n\nconst rules = {\n 'no-domain-infra-imports': noDomainInfraImports,\n 'no-raw-event-publish': noRawEventPublish,\n 'require-publish-source': requirePublishSource,\n 'no-forbidden-globals': noForbiddenGlobals,\n};\n\nconst plugin: ArkEslintPlugin = { rules };\n\nplugin.configs = {\n recommended: {\n plugins: { ark: plugin },\n rules: {\n 'ark/no-domain-infra-imports': 'error',\n 'ark/no-raw-event-publish': 'error',\n 'ark/require-publish-source': 'error',\n 'ark/no-forbidden-globals': 'error',\n },\n },\n};\n\nexport { plugin };\nexport default plugin;\n","/**\n * Pure layer-glob matching for ark.config.json.\n *\n * **Canonical algorithm** for CLI, ESLint, and library consumers.\n * The CLI load path uses the generated `bin/ark-layer-match.mjs`\n * (`npm run generate:layer-match` / `npm run check:layer-match`).\n * Behavioral parity tests remain a safety net.\n */\n\nexport type LayerConfig = {\n name: string;\n patterns?: string[];\n exclude?: string[];\n forbiddenGlobals?: string[];\n};\n\n/**\n * Layer-to-layer dependency rule from ark.config.json.\n *\n * - Classic: `{ from, to, allowed: false }` denies **cross-layer** edges only.\n * Same-layer is always allowed without peerIsolation (historical short-circuit).\n * - `peerIsolation: true` + `allowed: false`: deny only when slice ids differ\n * (same **or** cross layer). Same-slice → allow. Needs fromPath/toPath; missing\n * paths/slices → fail-open.\n */\nexport type EdgeRule = {\n from: string;\n to: string;\n allowed?: boolean;\n /**\n * When true with `allowed: false`: deny only when importer and importee resolve\n * to different slice ids (parent/name, e.g. features/auth). Works same-layer\n * and cross-layer. See findDeniedEdgeRule.\n */\n peerIsolation?: boolean;\n /**\n * Path segment names that own the slice id as the *next* segment\n * (e.g. `[\"features\"]` → `src/features/auth/...` has slice `auth`).\n * When omitted, inferred from the layer's glob patterns (segment before `**`/`*`).\n */\n sliceFolders?: string[];\n /** Optional override message for scanners / write-gate. */\n message?: string;\n};\n\n/** Options for path-aware edge checks (peer isolation). */\nexport type EdgeCheckOptions = {\n /** Repo-relative path of the importing file. */\n fromPath?: string;\n /** Repo-relative path of the imported module. */\n toPath?: string;\n /** Layer configs — used to infer sliceFolders when the rule omits them. */\n layers?: LayerConfig[];\n};\n\nconst regexpCache = new Map<string, RegExp>();\n\nfunction escapeLiteral(ch: string): string {\n return /[.*+?^${}()|[\\]\\\\]/.test(ch) ? `\\\\${ch}` : ch;\n}\n\n/**\n * Normalize path separators to `/` without destroying glob escape sequences.\n * `src\\domain\\x` → `src/domain/x` (Windows paths); `src/\\{legacy\\}/**` keeps `\\{` / `\\}`.\n * A plain `pattern.split('\\\\').join('/')` would eat those escapes.\n */\nfunction normalizeGlobSeparators(pattern: string): string {\n let out = '';\n for (let i = 0; i < pattern.length; i += 1) {\n const c = pattern[i];\n if (c === '\\\\' && i + 1 < pattern.length) {\n const next = pattern[i + 1];\n // Keep escapes for glob metacharacters (and escaped backslash).\n if ('*?{}[],'.includes(next) || next === '\\\\') {\n out += '\\\\' + next;\n i += 1;\n continue;\n }\n // Otherwise treat `\\` as a path separator (Windows).\n out += '/';\n continue;\n }\n out += c;\n }\n return out;\n}\n\nfunction bracesBalanced(glob: string): boolean {\n let depth = 0;\n for (let i = 0; i < glob.length; i += 1) {\n const c = glob[i];\n if (c === '\\\\') {\n i += 1;\n continue;\n }\n if (c === '{') depth += 1;\n else if (c === '}') {\n depth -= 1;\n if (depth < 0) return false;\n }\n }\n return depth === 0;\n}\n\nexport function globToRegExp(pattern: string): RegExp {\n const cached = regexpCache.get(pattern);\n if (cached) return cached;\n\n const glob = normalizeGlobSeparators(pattern);\n const useBraces = bracesBalanced(glob);\n let out = '';\n let braceDepth = 0;\n for (let i = 0; i < glob.length; i += 1) {\n const c = glob[i];\n if (c === '\\\\' && i + 1 < glob.length) {\n out += escapeLiteral(glob[i + 1]);\n i += 1;\n } else if (c === '*') {\n if (glob[i + 1] === '*') {\n if (glob[i + 2] === '/') {\n out += '(?:.*/)?';\n i += 2;\n } else {\n out += '.*';\n i += 1;\n }\n } else {\n out += '[^/]*';\n }\n } else if (c === '?') {\n out += '[^/]';\n } else if (c === '{' && useBraces) {\n out += '(?:';\n braceDepth += 1;\n } else if (c === '}' && useBraces && braceDepth > 0) {\n out += ')';\n braceDepth -= 1;\n } else if (c === ',' && useBraces && braceDepth > 0) {\n out += '|';\n } else {\n out += escapeLiteral(c);\n }\n }\n const re = new RegExp(`^${out}$`);\n regexpCache.set(pattern, re);\n return re;\n}\n\nexport function patternSpecificity(pattern: string): number {\n const glob = normalizeGlobSeparators(String(pattern));\n const beforeWildcard = glob.split('*')[0];\n const literalSegments = beforeWildcard.split('/').filter(Boolean).length;\n const literalLength = glob.replace(/\\*/g, '').length;\n return literalSegments * 10000 + literalLength;\n}\n\nexport function layerForRelativePath(\n relPath: string,\n layers: LayerConfig[] | undefined\n): string | undefined {\n // File paths (not globs): any OS separator → posix relative.\n const rel = String(relPath).split(/[/\\\\]/).join('/');\n let bestName: string | undefined;\n let bestScore = -1;\n for (const layer of layers ?? []) {\n if ((layer.exclude ?? []).some((pattern) => globToRegExp(pattern).test(rel))) {\n continue;\n }\n for (const pattern of layer.patterns ?? []) {\n if (globToRegExp(pattern).test(rel)) {\n const score = patternSpecificity(pattern);\n if (score > bestScore) {\n bestScore = score;\n bestName = layer.name;\n }\n }\n }\n }\n return bestName;\n}\n\n/**\n * Extract the slice id under a known folder name.\n * Includes the parent folder so `features/auth` ≠ `modules/auth`.\n * `src/features/auth/api.ts` + folders `[\"features\"]` → `\"features/auth\"`.\n */\nexport function sliceIdForPath(\n relPath: string,\n sliceFolders: string[] | undefined\n): string | undefined {\n if (!sliceFolders?.length) return undefined;\n const parts = String(relPath)\n .split(/[/\\\\]/)\n .filter(Boolean);\n const folders = new Set(sliceFolders.map((s) => String(s).toLowerCase()));\n for (let i = 0; i < parts.length - 1; i += 1) {\n if (folders.has(parts[i].toLowerCase())) {\n return `${parts[i]}/${parts[i + 1]}`;\n }\n }\n return undefined;\n}\n\n/**\n * Infer slice parent folders from layer globs: the path segment immediately\n * before a `*` or `**` wildcard (e.g. `src/features/**` → `features`).\n */\nexport function inferSliceFoldersFromPatterns(\n patterns: string[] | undefined\n): string[] {\n const out = new Set<string>();\n for (const pattern of patterns ?? []) {\n const glob = normalizeGlobSeparators(String(pattern));\n const parts = glob.split('/').filter(Boolean);\n for (let i = 0; i < parts.length; i += 1) {\n const part = parts[i];\n if ((part === '**' || part === '*') && i > 0) {\n const prev = parts[i - 1];\n if (prev && !prev.includes('*') && !prev.includes('{') && !prev.includes('}')) {\n out.add(prev);\n }\n }\n }\n }\n return [...out];\n}\n\nfunction resolveSliceFolders(\n rule: EdgeRule,\n layerName: string,\n layers: LayerConfig[] | undefined\n): string[] {\n if (Array.isArray(rule.sliceFolders) && rule.sliceFolders.length > 0) {\n return rule.sliceFolders.filter((s) => typeof s === 'string' && s.length > 0);\n }\n const layer = (layers ?? []).find((l) => l.name === layerName);\n return inferSliceFoldersFromPatterns(layer?.patterns);\n}\n\n/**\n * Find the first denying rule for a layer edge.\n *\n * Semantics (locked):\n * - Classic (`allowed: false`, no peerIsolation): deny cross-layer edges only.\n * Same-layer is always allowed (historical short-circuit).\n * - `peerIsolation: true` + `allowed: false`: deny only when importer and importee\n * resolve to **different** slice ids (same or cross layer). Same-slice → allow.\n * Missing paths or unclassifiable slices → fail-open (do not deny).\n */\nexport function findDeniedEdgeRule(\n rules: EdgeRule[] | undefined,\n from: string,\n to: string,\n options?: EdgeCheckOptions\n): EdgeRule | undefined {\n for (const rule of rules ?? []) {\n if (rule.from !== from || rule.to !== to) continue;\n if (rule.allowed !== false) continue;\n\n if (rule.peerIsolation) {\n const fromPath = options?.fromPath;\n const toPath = options?.toPath;\n if (!fromPath || !toPath) continue;\n\n const folders = resolveSliceFolders(rule, from, options?.layers);\n if (folders.length === 0) continue;\n\n const fromSlice = sliceIdForPath(fromPath, folders);\n const toSlice = sliceIdForPath(toPath, folders);\n if (!fromSlice || !toSlice) continue;\n if (fromSlice !== toSlice) return rule;\n continue; // same slice: this peerIsolation rule does not deny\n }\n\n // Classic deny — same-layer always allowed without peerIsolation\n if (from === to) continue;\n return rule;\n }\n return undefined;\n}\n\nexport function isEdgeDenied(\n rules: EdgeRule[] | undefined,\n from: string,\n to: string,\n options?: EdgeCheckOptions\n): boolean {\n return findDeniedEdgeRule(rules, from, to, options) !== undefined;\n}\n\n/** Codegen globs skipped by default scan (emitted into the CLI derived matcher). */\nexport const DEFAULT_GENERATED_FILE_GLOBS = [\n '**/*.gen.ts',\n '**/*.gen.tsx',\n '**/*.generated.ts',\n '**/*.generated.tsx',\n];\n\nexport type ScanExcludeConfig = {\n exclude?: string[];\n excludeGenerated?: boolean;\n};\n\nexport function scanExcludePatterns(config?: ScanExcludeConfig | null): string[] {\n const custom = Array.isArray(config?.exclude)\n ? config!.exclude!.filter((p) => typeof p === 'string')\n : [];\n const generated =\n config?.excludeGenerated === false ? [] : DEFAULT_GENERATED_FILE_GLOBS;\n return [...generated, ...custom];\n}\n\nexport function isScanExcludedRelative(\n relPath: string,\n config?: ScanExcludeConfig | null\n): boolean {\n const rel = String(relPath).split(/[/\\\\]/).join('/');\n return scanExcludePatterns(config).some((pattern) => globToRegExp(pattern).test(rel));\n}\n"],"mappings":"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","names":["rules","path","fs"]}
|
package/dist/eslint/index.d.cts
CHANGED
|
@@ -12,15 +12,47 @@ type LayerConfig$1 = {
|
|
|
12
12
|
exclude?: string[];
|
|
13
13
|
forbiddenGlobals?: string[];
|
|
14
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Layer-to-layer dependency rule from ark.config.json.
|
|
17
|
+
*
|
|
18
|
+
* - Classic: `{ from, to, allowed: false }` denies **cross-layer** edges only.
|
|
19
|
+
* Same-layer is always allowed without peerIsolation (historical short-circuit).
|
|
20
|
+
* - `peerIsolation: true` + `allowed: false`: deny only when slice ids differ
|
|
21
|
+
* (same **or** cross layer). Same-slice → allow. Needs fromPath/toPath; missing
|
|
22
|
+
* paths/slices → fail-open.
|
|
23
|
+
*/
|
|
15
24
|
type EdgeRule$1 = {
|
|
16
25
|
from: string;
|
|
17
26
|
to: string;
|
|
18
27
|
allowed?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* When true with `allowed: false`: deny only when importer and importee resolve
|
|
30
|
+
* to different slice ids (parent/name, e.g. features/auth). Works same-layer
|
|
31
|
+
* and cross-layer. See findDeniedEdgeRule.
|
|
32
|
+
*/
|
|
33
|
+
peerIsolation?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Path segment names that own the slice id as the *next* segment
|
|
36
|
+
* (e.g. `["features"]` → `src/features/auth/...` has slice `auth`).
|
|
37
|
+
* When omitted, inferred from the layer's glob patterns (segment before `**`/`*`).
|
|
38
|
+
*/
|
|
39
|
+
sliceFolders?: string[];
|
|
40
|
+
/** Optional override message for scanners / write-gate. */
|
|
41
|
+
message?: string;
|
|
42
|
+
};
|
|
43
|
+
/** Options for path-aware edge checks (peer isolation). */
|
|
44
|
+
type EdgeCheckOptions = {
|
|
45
|
+
/** Repo-relative path of the importing file. */
|
|
46
|
+
fromPath?: string;
|
|
47
|
+
/** Repo-relative path of the imported module. */
|
|
48
|
+
toPath?: string;
|
|
49
|
+
/** Layer configs — used to infer sliceFolders when the rule omits them. */
|
|
50
|
+
layers?: LayerConfig$1[];
|
|
19
51
|
};
|
|
20
52
|
declare function globToRegExp(pattern: string): RegExp;
|
|
21
53
|
declare function patternSpecificity(pattern: string): number;
|
|
22
54
|
declare function layerForRelativePath(relPath: string, layers: LayerConfig$1[] | undefined): string | undefined;
|
|
23
|
-
declare function isEdgeDenied(rules: EdgeRule$1[] | undefined, from: string, to: string): boolean;
|
|
55
|
+
declare function isEdgeDenied(rules: EdgeRule$1[] | undefined, from: string, to: string, options?: EdgeCheckOptions): boolean;
|
|
24
56
|
|
|
25
57
|
type RuleContext = {
|
|
26
58
|
report(descriptor: Record<string, unknown>): void;
|