arkgate 2.6.0 → 2.6.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 +61 -0
- package/README.md +90 -67
- package/bin/ark-check.mjs +264 -51
- package/bin/ark-layer-match.mjs +29 -0
- package/bin/ark-mcp.mjs +102 -5
- package/bin/ark-shared.mjs +295 -6
- package/bin/ark.mjs +44 -34
- package/bin/lib/agent-gates.mjs +448 -15
- package/bin/lib/doctor-plan.mjs +11 -4
- package/bin/lib/presets.mjs +75 -4
- package/dist/eslint/index.cjs.map +1 -1
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +1 -1
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.js +1 -1
- package/dist/nestjs/index.js.map +1 -1
- package/docs/agent-guide.md +63 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/skills/ark-adopt.md +43 -87
- package/templates/skills/ark-autopilot.md +39 -77
- package/templates/skills/ark-contract.md +43 -84
- package/templates/skills/ark-coverage.md +62 -83
- package/templates/skills/ark-fix.md +45 -90
- package/templates/skills/ark-loop.md +44 -66
package/bin/lib/presets.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createElevenLayerConfig,
|
|
7
7
|
DEFAULT_DOMAIN_FORBIDDEN_GLOBALS,
|
|
8
8
|
DEFAULT_INTENT_PREFIXES,
|
|
9
|
+
resolveIncludeRoots,
|
|
9
10
|
} from '../ark-shared.mjs';
|
|
10
11
|
|
|
11
12
|
export function denyUpward(names) {
|
|
@@ -177,16 +178,25 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
177
178
|
// anywhere in the tree (`**/domain/**` hits packages/x/domain AND apps/y/src/domain),
|
|
178
179
|
// so one profile governs every package. include defaults to the detected workspace
|
|
179
180
|
// roots (falls back to packages+apps). Naming varies by repo — adjust and re-check.
|
|
180
|
-
monorepo: (includeDirs, root) =>
|
|
181
|
-
|
|
181
|
+
monorepo: (includeDirs, root) => {
|
|
182
|
+
let include =
|
|
183
|
+
includeDirs && includeDirs.length > 0 ? [...includeDirs] : [];
|
|
184
|
+
if (root) {
|
|
185
|
+
const resolved = resolveIncludeRoots(root);
|
|
186
|
+
if (resolved.length > 0) include = resolved;
|
|
187
|
+
}
|
|
188
|
+
if (include.length === 0) include = ['packages', 'apps'];
|
|
189
|
+
return presetWithOverlays(
|
|
182
190
|
{
|
|
183
|
-
include
|
|
191
|
+
include,
|
|
184
192
|
layers: [
|
|
185
193
|
{
|
|
186
194
|
name: 'DomainModel',
|
|
187
195
|
description:
|
|
188
196
|
'Pure business rules and entities, in any package. No I/O, no framework, no ambient globals.',
|
|
189
|
-
|
|
197
|
+
// Domain by intentional folders only — NOT bare **/types.ts (that mis-classifies
|
|
198
|
+
// application bags like frontend/src/core/**/types.ts as Domain and creates false edges).
|
|
199
|
+
patterns: ['**/domain/**', '**/entities/**', '**/cinematic/types.ts'],
|
|
190
200
|
forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS,
|
|
191
201
|
optional: true,
|
|
192
202
|
},
|
|
@@ -206,6 +216,8 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
206
216
|
'**/controllers/**',
|
|
207
217
|
'**/http/**',
|
|
208
218
|
'**/routes/**',
|
|
219
|
+
'**/hooks/**',
|
|
220
|
+
'**/lib/**',
|
|
209
221
|
],
|
|
210
222
|
optional: true,
|
|
211
223
|
},
|
|
@@ -231,6 +243,65 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
231
243
|
],
|
|
232
244
|
},
|
|
233
245
|
root
|
|
246
|
+
);
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* UI / Vite / Remotion-style surface: presentation-heavy trees with hooks, lib, routes,
|
|
251
|
+
* components. Use when the TS surface is mostly UI (no deep domain folders yet).
|
|
252
|
+
*/
|
|
253
|
+
'ui-surface': (_workspaces, root) =>
|
|
254
|
+
presetWithOverlays(
|
|
255
|
+
{
|
|
256
|
+
include: (() => {
|
|
257
|
+
if (!root) return ['src'];
|
|
258
|
+
try {
|
|
259
|
+
const roots = resolveIncludeRoots(root);
|
|
260
|
+
return roots.length > 0 ? roots : ['src'];
|
|
261
|
+
} catch {
|
|
262
|
+
return ['src'];
|
|
263
|
+
}
|
|
264
|
+
})(),
|
|
265
|
+
layers: [
|
|
266
|
+
{
|
|
267
|
+
name: 'DomainModel',
|
|
268
|
+
description: 'Shared types and pure view-models (optional on UI-first trees).',
|
|
269
|
+
// Avoid bare **/types.ts — see monorepo DomainModel note (false Domain on core/**/types.ts).
|
|
270
|
+
patterns: ['**/domain/**', '**/cinematic/types.ts'],
|
|
271
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
272
|
+
forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS,
|
|
273
|
+
optional: true,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'PresentationAdapters',
|
|
277
|
+
description: 'UI, routes, hooks, components, compositions.',
|
|
278
|
+
patterns: [
|
|
279
|
+
'**/src/**',
|
|
280
|
+
'**/components/**',
|
|
281
|
+
'**/hooks/**',
|
|
282
|
+
'**/lib/**',
|
|
283
|
+
'**/routes/**',
|
|
284
|
+
'**/app/**',
|
|
285
|
+
'**/pages/**',
|
|
286
|
+
],
|
|
287
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
288
|
+
optional: true,
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: 'PersistenceAdapters',
|
|
292
|
+
description: 'Client data access and external API adapters (when present).',
|
|
293
|
+
patterns: ['**/infrastructure/**', '**/adapters/**', '**/repositories/**'],
|
|
294
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
295
|
+
optional: true,
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
rules: [
|
|
299
|
+
{ from: 'DomainModel', to: 'PresentationAdapters', allowed: false },
|
|
300
|
+
{ from: 'DomainModel', to: 'PersistenceAdapters', allowed: false },
|
|
301
|
+
{ from: 'PresentationAdapters', to: 'PersistenceAdapters', allowed: false },
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
root
|
|
234
305
|
),
|
|
235
306
|
};
|
|
236
307
|
|
|
@@ -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 `bin/ark-layer-match.mjs` (bundled) so\n * CLI and editor share one implementation — 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 * Single TypeScript source of truth for the library (eslint / kernel consumers).\n * CLI re-exports the identical algorithm from `bin/ark-layer-match.mjs` — keep in\n * lockstep via tests/unit/static-check/layerMatchParity.test.ts.\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\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 = pattern.split('\\\\').join('/');\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 = String(pattern).split('\\\\').join('/');\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 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"],"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 `bin/ark-layer-match.mjs` (bundled) so\n * CLI and editor share one implementation — 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 * Single TypeScript source of truth for the library (eslint / kernel consumers).\n * CLI re-exports the identical algorithm from `bin/ark-layer-match.mjs` — keep in\n * lockstep via tests/unit/static-check/layerMatchParity.test.ts.\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\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 = pattern.split('\\\\').join('/');\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 = String(pattern).split('\\\\').join('/');\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 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 — keep in lockstep with bin/ark-layer-match.mjs. */\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.js.map
CHANGED
|
@@ -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 `bin/ark-layer-match.mjs` (bundled) so\n * CLI and editor share one implementation — 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 * Single TypeScript source of truth for the library (eslint / kernel consumers).\n * CLI re-exports the identical algorithm from `bin/ark-layer-match.mjs` — keep in\n * lockstep via tests/unit/static-check/layerMatchParity.test.ts.\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\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 = pattern.split('\\\\').join('/');\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 = String(pattern).split('\\\\').join('/');\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 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"],"mappings":"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","names":["rules"]}
|
|
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 `bin/ark-layer-match.mjs` (bundled) so\n * CLI and editor share one implementation — 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 * Single TypeScript source of truth for the library (eslint / kernel consumers).\n * CLI re-exports the identical algorithm from `bin/ark-layer-match.mjs` — keep in\n * lockstep via tests/unit/static-check/layerMatchParity.test.ts.\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\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 = pattern.split('\\\\').join('/');\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 = String(pattern).split('\\\\').join('/');\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 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 — keep in lockstep with bin/ark-layer-match.mjs. */\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"]}
|