arkgate 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +28 -17
  3. package/SECURITY.md +9 -8
  4. package/bin/ark-check.mjs +599 -53
  5. package/bin/ark-shared.mjs +53 -0
  6. package/bin/ark.mjs +20 -5
  7. package/dist/eslint/index.cjs +258 -23
  8. package/dist/eslint/index.cjs.map +1 -1
  9. package/dist/eslint/index.d.cts +38 -1
  10. package/dist/eslint/index.d.ts +38 -1
  11. package/dist/eslint/index.js +240 -22
  12. package/dist/eslint/index.js.map +1 -1
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/nestjs/index.cjs +1 -1
  20. package/dist/nestjs/index.cjs.map +1 -1
  21. package/dist/nestjs/index.js +1 -1
  22. package/dist/nestjs/index.js.map +1 -1
  23. package/docs/agent-guide.md +15 -8
  24. package/docs/ai-gates.md +26 -36
  25. package/docs/brownfield-adoption.md +14 -13
  26. package/docs/demos/03-copilot-autopilot.md +5 -3
  27. package/docs/enthusiast/README.md +4 -3
  28. package/docs/enthusiast/how-to-agent-gates.md +14 -6
  29. package/docs/enthusiast/reference-commands.md +23 -8
  30. package/docs/migrate-from-ark-runtime-kernel.md +18 -0
  31. package/docs/typescript-support.md +142 -0
  32. package/package.json +12 -3
  33. package/server.json +2 -2
  34. package/templates/skills/ark-autopilot.md +6 -4
  35. package/templates/skills/ark-explain.md +7 -2
  36. package/templates/skills/ark-fix.md +16 -12
  37. package/templates/skills/ark-loop.md +14 -4
  38. package/templates/skills/ark-upgrade.md +26 -1
  39. package/templates/tests/ark-adoption-gaps.test.ts +68 -0
  40. package/tests/fixtures/ts-consumer/ark.config.json +11 -0
  41. package/tests/fixtures/ts-consumer/src/app/types.ts +1 -0
  42. package/tests/fixtures/ts-consumer/src/domain/bad.ts +1 -0
  43. package/tests/fixtures/ts-consumer/src/domain/ok.ts +1 -0
  44. package/tests/fixtures/ts-consumer/src/domain/user.ts +1 -0
  45. package/tests/fixtures/ts-consumer/tsconfig.json +16 -0
@@ -630,6 +630,59 @@ export function classifyRemediation(violation) {
630
630
  };
631
631
  }
632
632
 
633
+ /**
634
+ * Normalize a required/imported TypeScript module for ark-check's host.
635
+ * TS 5/6 expose `sys` on the root export. Early TS 7 / some ESM interop shapes
636
+ * may nest under `.default` or omit `sys` — those are unusable for resolve/scan
637
+ * and must fall through to a JS-API-compatible TypeScript (Ark's own or 5/6).
638
+ *
639
+ * @param {unknown} mod
640
+ * @returns {object | null} usable typescript namespace, or null
641
+ */
642
+ export function usableTypescript(mod) {
643
+ if (!mod || typeof mod !== 'object') return null;
644
+ // Prefer root; if root has no sys but default does (CJS/ESM interop), use default.
645
+ const candidates = [mod];
646
+ if (mod.default && typeof mod.default === 'object') candidates.push(mod.default);
647
+ for (const ts of candidates) {
648
+ if (
649
+ ts &&
650
+ typeof ts === 'object' &&
651
+ ts.sys &&
652
+ typeof ts.sys.fileExists === 'function' &&
653
+ typeof ts.createSourceFile === 'function' &&
654
+ typeof ts.resolveModuleName === 'function'
655
+ ) {
656
+ return ts;
657
+ }
658
+ }
659
+ return null;
660
+ }
661
+
662
+ /**
663
+ * Human-readable reason a typescript package module is unusable for the gate.
664
+ * @param {unknown} mod
665
+ */
666
+ export function typescriptUsabilityHint(mod) {
667
+ if (!mod) return 'module is null/undefined';
668
+ const ts = mod.default && mod.sys == null ? mod.default : mod;
669
+ if (!ts || typeof ts !== 'object') return 'not an object export';
670
+ // TS 7.0.x main export is only { version, versionMajorMinor }; classic JS host is not there.
671
+ if (
672
+ typeof ts.version === 'string' &&
673
+ !ts.sys &&
674
+ typeof ts.createSourceFile !== 'function' &&
675
+ typeof ts.resolveModuleName !== 'function'
676
+ ) {
677
+ return `version-only export (${ts.version}) — TypeScript 7 main entry no longer ships the classic JS host (sys/AST/resolve); gate falls back to a JS-API TypeScript`;
678
+ }
679
+ if (!ts.sys) return 'missing ts.sys (common with early TypeScript 7 native builds without a full JS host)';
680
+ if (typeof ts.sys.fileExists !== 'function') return 'ts.sys.fileExists is not a function';
681
+ if (typeof ts.createSourceFile !== 'function') return 'missing createSourceFile (AST API)';
682
+ if (typeof ts.resolveModuleName !== 'function') return 'missing resolveModuleName';
683
+ return 'unknown shape incompatibility';
684
+ }
685
+
633
686
  /** The three package managers Ark emits commands for. */
634
687
  const LOCKFILES = { pnpm: 'pnpm-lock.yaml', yarn: 'yarn.lock', npm: 'package-lock.json' };
635
688
 
package/bin/ark.mjs CHANGED
@@ -123,11 +123,12 @@ async function upgrade(args) {
123
123
  let status = runArkCheck(['--root', root, '--install-agent-gates'], { cwd: root });
124
124
  if (status !== 0) return status;
125
125
 
126
- // Codex loads slash-command prompts from ~/.codex/prompts, not the repo — refresh those too
127
- // when a Codex home exists, so nothing is left stale. Non-fatal: a permission error there
128
- // (e.g. a sandbox) shouldn't fail the whole upgrade.
129
- if (fs.existsSync(path.join(os.homedir(), '.codex'))) {
130
- console.log('\n Refreshing Codex home prompts (~/.codex)…');
126
+ // Codex loads slash-command prompts from $CODEX_HOME/prompts, not the repo — refresh those
127
+ // when a Codex home exists. --force rewrites temp/upgrade MCP roots to this project + arkgate-mcp.
128
+ // Non-fatal: a permission error (e.g. sandbox) shouldn't fail the whole upgrade.
129
+ const codexHomeBase = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
130
+ if (fs.existsSync(codexHomeBase)) {
131
+ console.log(`\n Refreshing Codex home (${codexHomeBase})…`);
131
132
  runArkCheck(
132
133
  ['--root', root, '--install-agent-gates', '--skills-only', '--codex-home', '--force'],
133
134
  { cwd: root }
@@ -279,6 +280,10 @@ async function init(args) {
279
280
  if (archetype) {
280
281
  console.log(`Shape: ${archetype}. Plan: ${arkCommand(root, 'ark-check', '--recommend')}`);
281
282
  }
283
+ console.log(
284
+ `Freeze day-one architecture snapshot: ${arkCommand(root, 'ark-check', '--report ark-report.html')} (writes .ark/reports/origin.* once).`
285
+ );
286
+ console.log(`Adoption health: ${arkCommand(root, 'ark-check', '--doctor')}`);
282
287
  return 0;
283
288
  } finally {
284
289
  rl?.close();
@@ -433,7 +438,17 @@ async function start(args) {
433
438
  }
434
439
  console.log(` • Re-run the plan anytime: ${arkCommand(root, 'ark-check', '--plan')}`);
435
440
  console.log(` • Full project check: ${arkCommand(root, 'ark-check', '--root . --config ark.config.json --strict-config')}`);
441
+ console.log(` • Adoption health: ${arkCommand(root, 'ark-check', '--doctor')}`);
436
442
  console.log(` • Update Ark later: ${arkCommand(root, 'ark', 'upgrade')}`);
443
+ if (fs.existsSync(path.join(root, '.ark-baseline.json'))) {
444
+ console.log(
445
+ ' • Baseline file present — keep empty for ratchet-from-clean, or freeze debt with --update-baseline.'
446
+ );
447
+ } else {
448
+ console.log(
449
+ ' • No baseline yet (fine on clean trees). Adopting dirty code? freeze with --update-baseline.'
450
+ );
451
+ }
437
452
 
438
453
  // 6) First architecture report — freezes an origin snapshot under .ark/reports/
439
454
  // so later --report runs can show evolution. Idempotent: origin is written only once.
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,19 +17,191 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/eslint/index.ts
21
31
  var eslint_exports = {};
22
32
  __export(eslint_exports, {
23
33
  default: () => eslint_default,
34
+ findConfigPath: () => findConfigPath,
35
+ globToRegExp: () => globToRegExp,
36
+ isEdgeDenied: () => isEdgeDenied,
37
+ layerForRelativePath: () => layerForRelativePath,
38
+ loadArkConfig: () => loadArkConfig,
24
39
  noDomainInfraImports: () => noDomainInfraImports,
25
40
  noForbiddenGlobals: () => noForbiddenGlobals,
26
41
  noRawEventPublish: () => noRawEventPublish,
42
+ patternSpecificity: () => patternSpecificity,
27
43
  plugin: () => plugin,
28
- requirePublishSource: () => requirePublishSource
44
+ requirePublishSource: () => requirePublishSource,
45
+ resolveRelativeImport: () => resolveRelativeImport
29
46
  });
30
47
  module.exports = __toCommonJS(eslint_exports);
48
+ var import_node_fs = __toESM(require("fs"), 1);
49
+ var import_node_path = __toESM(require("path"), 1);
50
+ function lintedFilename(context) {
51
+ if (typeof context.physicalFilename === "string" && context.physicalFilename.length > 0) {
52
+ return context.physicalFilename;
53
+ }
54
+ if (typeof context.filename === "string" && context.filename.length > 0) {
55
+ return context.filename;
56
+ }
57
+ if (typeof context.getFilename === "function") {
58
+ try {
59
+ const name = context.getFilename();
60
+ if (typeof name === "string" && name.length > 0) return name;
61
+ } catch {
62
+ }
63
+ }
64
+ return "";
65
+ }
66
+ var _regexpCache = /* @__PURE__ */ new Map();
67
+ function bracesBalanced(glob) {
68
+ let depth = 0;
69
+ for (let i = 0; i < glob.length; i += 1) {
70
+ if (glob[i] === "\\" && i + 1 < glob.length) {
71
+ i += 1;
72
+ continue;
73
+ }
74
+ if (glob[i] === "{") depth += 1;
75
+ else if (glob[i] === "}") {
76
+ depth -= 1;
77
+ if (depth < 0) return false;
78
+ }
79
+ }
80
+ return depth === 0;
81
+ }
82
+ function escapeLiteral(c) {
83
+ return c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
84
+ }
85
+ function globToRegExp(pattern) {
86
+ const cached = _regexpCache.get(pattern);
87
+ if (cached) return cached;
88
+ const glob = pattern.split(import_node_path.default.sep).join("/");
89
+ const useBraces = bracesBalanced(glob);
90
+ let out = "";
91
+ let braceDepth = 0;
92
+ for (let i = 0; i < glob.length; i += 1) {
93
+ const c = glob[i];
94
+ if (c === "\\" && i + 1 < glob.length) {
95
+ out += escapeLiteral(glob[i + 1]);
96
+ i += 1;
97
+ } else if (c === "*") {
98
+ if (glob[i + 1] === "*") {
99
+ if (glob[i + 2] === "/") {
100
+ out += "(?:.*/)?";
101
+ i += 2;
102
+ } else {
103
+ out += ".*";
104
+ i += 1;
105
+ }
106
+ } else {
107
+ out += "[^/]*";
108
+ }
109
+ } else if (c === "?") {
110
+ out += "[^/]";
111
+ } else if (c === "{" && useBraces) {
112
+ out += "(?:";
113
+ braceDepth += 1;
114
+ } else if (c === "}" && useBraces && braceDepth > 0) {
115
+ out += ")";
116
+ braceDepth -= 1;
117
+ } else if (c === "," && useBraces && braceDepth > 0) {
118
+ out += "|";
119
+ } else {
120
+ out += escapeLiteral(c);
121
+ }
122
+ }
123
+ const re = new RegExp(`^${out}$`);
124
+ _regexpCache.set(pattern, re);
125
+ return re;
126
+ }
127
+ function patternSpecificity(pattern) {
128
+ const glob = String(pattern).split(import_node_path.default.sep).join("/");
129
+ const beforeWildcard = glob.split("*")[0];
130
+ const literalSegments = beforeWildcard.split("/").filter(Boolean).length;
131
+ const literalLength = glob.replace(/\*/g, "").length;
132
+ return literalSegments * 1e4 + literalLength;
133
+ }
134
+ function layerForRelativePath(relPath, layers) {
135
+ const rel = relPath.split(import_node_path.default.sep).join("/");
136
+ let bestName;
137
+ let bestScore = -1;
138
+ for (const layer of layers ?? []) {
139
+ if ((layer.exclude ?? []).some((pattern) => globToRegExp(pattern).test(rel))) {
140
+ continue;
141
+ }
142
+ for (const pattern of layer.patterns ?? []) {
143
+ if (globToRegExp(pattern).test(rel)) {
144
+ const score = patternSpecificity(pattern);
145
+ if (score > bestScore) {
146
+ bestScore = score;
147
+ bestName = layer.name;
148
+ }
149
+ }
150
+ }
151
+ }
152
+ return bestName;
153
+ }
154
+ function isEdgeDenied(rules2, from, to) {
155
+ if (from === to) return false;
156
+ const hit = (rules2 ?? []).find((r) => r.from === from && r.to === to);
157
+ return hit?.allowed === false;
158
+ }
159
+ function findConfigPath(startFile) {
160
+ if (!startFile || startFile === "<input>" || startFile.startsWith("stdin")) return null;
161
+ let dir = import_node_path.default.dirname(import_node_path.default.resolve(startFile));
162
+ for (; ; ) {
163
+ const candidate = import_node_path.default.join(dir, "ark.config.json");
164
+ if (import_node_fs.default.existsSync(candidate)) return candidate;
165
+ const parent = import_node_path.default.dirname(dir);
166
+ if (parent === dir) return null;
167
+ dir = parent;
168
+ }
169
+ }
170
+ var _configCache = /* @__PURE__ */ new Map();
171
+ function loadArkConfig(configPath) {
172
+ if (_configCache.has(configPath)) return _configCache.get(configPath) ?? null;
173
+ try {
174
+ const raw = JSON.parse(import_node_fs.default.readFileSync(configPath, "utf8"));
175
+ _configCache.set(configPath, raw);
176
+ return raw;
177
+ } catch {
178
+ _configCache.set(configPath, null);
179
+ return null;
180
+ }
181
+ }
182
+ function resolveRelativeImport(fromFile, specifier) {
183
+ if (!specifier.startsWith(".")) return null;
184
+ const base = import_node_path.default.resolve(import_node_path.default.dirname(fromFile), specifier);
185
+ const candidates = [
186
+ base,
187
+ `${base}.ts`,
188
+ `${base}.tsx`,
189
+ `${base}.mts`,
190
+ `${base}.cts`,
191
+ `${base}.js`,
192
+ `${base}.jsx`,
193
+ import_node_path.default.join(base, "index.ts"),
194
+ import_node_path.default.join(base, "index.tsx"),
195
+ import_node_path.default.join(base, "index.js")
196
+ ];
197
+ for (const c of candidates) {
198
+ try {
199
+ if (import_node_fs.default.existsSync(c) && import_node_fs.default.statSync(c).isFile()) return c;
200
+ } catch {
201
+ }
202
+ }
203
+ return `${base}.ts`;
204
+ }
31
205
  function stringValue(node) {
32
206
  return typeof node?.value === "string" ? node.value : void 0;
33
207
  }
@@ -48,14 +222,18 @@ function objectHasMetadataSource(node) {
48
222
  return objectHasProperty(metadata, "source");
49
223
  }
50
224
  function looksLikeIntent(value) {
51
- return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(value);
225
+ return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(
226
+ value
227
+ );
228
+ }
229
+ function isPublishCall(node) {
230
+ return calleePropertyName(node) === "publish";
52
231
  }
53
- function isDomainFile(context) {
54
- const filename = context.getFilename?.() ?? "";
232
+ function isDomainFileHeuristic(filename) {
55
233
  const normalized = filename.split("\\").join("/").toLowerCase();
56
234
  return normalized.includes("/domain/") || normalized.endsWith("/domain.ts");
57
235
  }
58
- function isInfraImport(specifier) {
236
+ function isInfraImportHeuristic(specifier) {
59
237
  const normalized = specifier.toLowerCase();
60
238
  return [
61
239
  "adapter",
@@ -69,26 +247,49 @@ function isInfraImport(specifier) {
69
247
  "db"
70
248
  ].some((token) => normalized.includes(token));
71
249
  }
72
- function isPublishCall(node) {
73
- return calleePropertyName(node) === "publish";
74
- }
250
+ var DEFAULT_FORBIDDEN_GLOBALS = ["fetch", "process", "Date.now", "Math.random"];
75
251
  var noDomainInfraImports = {
76
252
  meta: {
77
253
  type: "problem",
78
254
  docs: {
79
- description: "Disallow importing infrastructure or adapters from domain files."
255
+ description: "Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check). Falls back to domain\u2192infra path heuristics when no config is found."
80
256
  },
81
257
  messages: {
82
- forbiddenImport: "Domain code must not import infrastructure, adapters, repositories, or database modules."
258
+ forbiddenImport: "Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",
259
+ forbiddenImportHeuristic: "Domain code must not import infrastructure, adapters, repositories, or database modules."
83
260
  },
84
261
  schema: []
85
262
  },
86
263
  create(context) {
264
+ const filename = lintedFilename(context);
265
+ const configPath = findConfigPath(filename);
266
+ const config = configPath ? loadArkConfig(configPath) : null;
267
+ const root = configPath ? import_node_path.default.dirname(configPath) : null;
87
268
  const check = (node) => {
88
- if (!isDomainFile(context)) return;
89
269
  const source = stringValue(node.source);
90
- if (source && isInfraImport(source)) {
91
- context.report({ node, messageId: "forbiddenImport" });
270
+ if (!source) return;
271
+ if (config && root && filename) {
272
+ const absFile = import_node_path.default.isAbsolute(filename) ? filename : import_node_path.default.resolve(filename);
273
+ const relFile = import_node_path.default.relative(root, absFile).split(import_node_path.default.sep).join("/");
274
+ const fromLayer = layerForRelativePath(relFile, config.layers);
275
+ if (!fromLayer) return;
276
+ const targetAbs = resolveRelativeImport(absFile, source);
277
+ if (!targetAbs) return;
278
+ const relTarget = import_node_path.default.relative(root, targetAbs).split(import_node_path.default.sep).join("/");
279
+ if (relTarget.startsWith("..")) return;
280
+ const toLayer = layerForRelativePath(relTarget, config.layers);
281
+ if (!toLayer) return;
282
+ if (isEdgeDenied(config.rules, fromLayer, toLayer)) {
283
+ context.report({
284
+ node,
285
+ messageId: "forbiddenImport",
286
+ data: { fromLayer, toLayer, specifier: source }
287
+ });
288
+ }
289
+ return;
290
+ }
291
+ if (isDomainFileHeuristic(filename) && isInfraImportHeuristic(source)) {
292
+ context.report({ node, messageId: "forbiddenImportHeuristic" });
92
293
  }
93
294
  };
94
295
  return {
@@ -147,15 +348,15 @@ var requirePublishSource = {
147
348
  };
148
349
  }
149
350
  };
150
- var DEFAULT_FORBIDDEN_GLOBALS = ["fetch", "process", "Date.now", "Math.random"];
151
351
  var noForbiddenGlobals = {
152
352
  meta: {
153
353
  type: "problem",
154
354
  docs: {
155
- description: 'Disallow ambient globals (e.g. fetch, Date.now) in architecture-governed code; scope the rule to layer directories via ESLint "files" patterns.'
355
+ description: "Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as arkgate-check). Option `globals` overrides. Without config, defaults apply only on domain-like paths."
156
356
  },
157
357
  messages: {
158
- forbiddenGlobal: 'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.'
358
+ forbiddenGlobal: 'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',
359
+ forbiddenGlobalDefault: 'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.'
159
360
  },
160
361
  schema: [
161
362
  {
@@ -168,13 +369,39 @@ var noForbiddenGlobals = {
168
369
  ]
169
370
  },
170
371
  create(context) {
372
+ const filename = lintedFilename(context);
171
373
  const option = context.options?.[0];
172
- const globals = new Set(option?.globals ?? DEFAULT_FORBIDDEN_GLOBALS);
173
- const report = (node, name) => context.report({ node, messageId: "forbiddenGlobal", data: { name } });
374
+ const configPath = findConfigPath(filename);
375
+ const config = configPath ? loadArkConfig(configPath) : null;
376
+ const root = configPath ? import_node_path.default.dirname(configPath) : null;
377
+ let globals = null;
378
+ let layerName = "this layer";
379
+ if (option?.globals) {
380
+ globals = new Set(option.globals);
381
+ } else if (config && root && filename) {
382
+ const absFile = import_node_path.default.isAbsolute(filename) ? filename : import_node_path.default.resolve(filename);
383
+ const relFile = import_node_path.default.relative(root, absFile).split(import_node_path.default.sep).join("/");
384
+ const layer = config.layers?.find(
385
+ (l) => l.name === layerForRelativePath(relFile, config.layers)
386
+ );
387
+ if (layer?.forbiddenGlobals?.length) {
388
+ globals = new Set(layer.forbiddenGlobals);
389
+ layerName = layer.name;
390
+ } else {
391
+ globals = null;
392
+ }
393
+ } else if (isDomainFileHeuristic(filename)) {
394
+ globals = new Set(DEFAULT_FORBIDDEN_GLOBALS);
395
+ }
396
+ if (!globals) {
397
+ return {};
398
+ }
399
+ const report = (node, name) => context.report({
400
+ node,
401
+ messageId: config ? "forbiddenGlobal" : "forbiddenGlobalDefault",
402
+ data: { name, layer: layerName }
403
+ });
174
404
  return {
175
- // Same positional detection as ark-check's FORBIDDEN_GLOBAL: property accesses on a
176
- // forbidden base (console.log, Date.now), direct calls, and constructions. Bare
177
- // identifier mentions elsewhere are not flagged (avoids shadowed-local false positives).
178
405
  MemberExpression(node) {
179
406
  const base = node.object?.type === "Identifier" ? node.object.name : void 0;
180
407
  if (!base) return;
@@ -206,17 +433,25 @@ plugin.configs = {
206
433
  rules: {
207
434
  "ark/no-domain-infra-imports": "error",
208
435
  "ark/no-raw-event-publish": "error",
209
- "ark/require-publish-source": "error"
436
+ "ark/require-publish-source": "error",
437
+ "ark/no-forbidden-globals": "error"
210
438
  }
211
439
  }
212
440
  };
213
441
  var eslint_default = plugin;
214
442
  // Annotate the CommonJS export names for ESM import in node:
215
443
  0 && (module.exports = {
444
+ findConfigPath,
445
+ globToRegExp,
446
+ isEdgeDenied,
447
+ layerForRelativePath,
448
+ loadArkConfig,
216
449
  noDomainInfraImports,
217
450
  noForbiddenGlobals,
218
451
  noRawEventPublish,
452
+ patternSpecificity,
219
453
  plugin,
220
- requirePublishSource
454
+ requirePublishSource,
455
+ resolveRelativeImport
221
456
  });
222
457
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/eslint/index.ts"],"sourcesContent":["type RuleContext = {\n report(descriptor: Record<string, unknown>): void;\n getFilename?: () => string;\n options?: unknown[];\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};\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\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(value);\n}\n\nfunction isDomainFile(context: RuleContext): boolean {\n const filename = context.getFilename?.() ?? '';\n const normalized = filename.split('\\\\').join('/').toLowerCase();\n return normalized.includes('/domain/') || normalized.endsWith('/domain.ts');\n}\n\nfunction isInfraImport(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\nfunction isPublishCall(node: AstNode): boolean {\n return calleePropertyName(node) === 'publish';\n}\n\nexport const noDomainInfraImports: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow importing infrastructure or adapters from domain files.',\n },\n messages: {\n forbiddenImport: 'Domain code must not import infrastructure, adapters, repositories, or database modules.',\n },\n schema: [],\n },\n create(context) {\n const check = (node: AstNode) => {\n if (!isDomainFile(context)) return;\n const source = stringValue(node.source);\n if (source && isInfraImport(source)) {\n context.report({ node, messageId: 'forbiddenImport' });\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: 'Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings.',\n },\n messages: {\n rawPublish: '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 (\n firstValue && looksLikeIntent(firstValue) ||\n objectHasProperty(firstArg, 'intent')\n ) {\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\nconst DEFAULT_FORBIDDEN_GLOBALS = ['fetch', 'process', 'Date.now', 'Math.random'];\n\nexport const noForbiddenGlobals: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow ambient globals (e.g. fetch, Date.now) in architecture-governed code; scope the rule to layer directories via ESLint \"files\" patterns.',\n },\n messages: {\n forbiddenGlobal: '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 option = context.options?.[0] as { globals?: string[] } | undefined;\n const globals = new Set(option?.globals ?? DEFAULT_FORBIDDEN_GLOBALS);\n const report = (node: AstNode, name: string) =>\n context.report({ node, messageId: 'forbiddenGlobal', data: { name } });\n\n return {\n // Same positional detection as ark-check's FORBIDDEN_GLOBAL: property accesses on a\n // forbidden base (console.log, Date.now), direct calls, and constructions. Bare\n // identifier mentions elsewhere are not flagged (avoids shadowed-local false positives).\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 },\n },\n};\n\nexport { plugin };\nexport default plugin;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/eslint/index.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. Tooling layer: pure Node + local helpers only (no Kernel imports).\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\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// ── Pure helpers (mirror bin/ark-shared.mjs layer matching; no CLI imports) ──\n\nconst _regexpCache = new Map<string, RegExp>();\n\nfunction bracesBalanced(glob: string): boolean {\n let depth = 0;\n for (let i = 0; i < glob.length; i += 1) {\n if (glob[i] === '\\\\' && i + 1 < glob.length) {\n i += 1;\n continue;\n }\n if (glob[i] === '{') depth += 1;\n else if (glob[i] === '}') {\n depth -= 1;\n if (depth < 0) return false;\n }\n }\n return depth === 0;\n}\n\nfunction escapeLiteral(c: string): string {\n return c.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/** Same glob → RegExp semantics as ark-check / ark-shared.mjs. */\nexport function globToRegExp(pattern: string): RegExp {\n const cached = _regexpCache.get(pattern);\n if (cached) return cached;\n const glob = pattern.split(path.sep).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(path.sep).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\n/** Same file→layer resolution as ark-check (most-specific pattern wins; exclude honored). */\nexport function layerForRelativePath(relPath: string, layers: LayerConfig[] | undefined): string | undefined {\n const rel = relPath.split(path.sep).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(rules: EdgeRule[] | undefined, from: string, to: string): 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\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"],"mappings":"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","names":["path","rules","fs"]}
@@ -1,5 +1,10 @@
1
1
  type RuleContext = {
2
2
  report(descriptor: Record<string, unknown>): void;
3
+ /** ESLint 9+ / 10: preferred path on the context object. */
4
+ filename?: string;
5
+ /** ESLint 8-style physical path when linting with processors / virtual files. */
6
+ physicalFilename?: string;
7
+ /** ESLint ≤8 API — still present on some hosts; removed in ESLint 10. */
3
8
  getFilename?: () => string;
4
9
  options?: unknown[];
5
10
  };
@@ -15,6 +20,8 @@ type AstNode = {
15
20
  key?: AstNode;
16
21
  arguments?: AstNode[];
17
22
  properties?: AstNode[];
23
+ importKind?: string;
24
+ specifiers?: AstNode[];
18
25
  };
19
26
  type ArkRule = {
20
27
  meta: {
@@ -31,6 +38,36 @@ type ArkEslintPlugin = {
31
38
  rules: Record<string, ArkRule>;
32
39
  configs?: Record<string, unknown>;
33
40
  };
41
+ type LayerConfig = {
42
+ name: string;
43
+ patterns?: string[];
44
+ exclude?: string[];
45
+ forbiddenGlobals?: string[];
46
+ };
47
+ type EdgeRule = {
48
+ from: string;
49
+ to: string;
50
+ allowed?: boolean;
51
+ };
52
+ type ArkConfig = {
53
+ layers?: LayerConfig[];
54
+ rules?: EdgeRule[];
55
+ };
56
+ /** Same glob → RegExp semantics as ark-check / ark-shared.mjs. */
57
+ declare function globToRegExp(pattern: string): RegExp;
58
+ declare function patternSpecificity(pattern: string): number;
59
+ /** Same file→layer resolution as ark-check (most-specific pattern wins; exclude honored). */
60
+ declare function layerForRelativePath(relPath: string, layers: LayerConfig[] | undefined): string | undefined;
61
+ declare function isEdgeDenied(rules: EdgeRule[] | undefined, from: string, to: string): boolean;
62
+ declare function findConfigPath(startFile: string): string | null;
63
+ declare function loadArkConfig(configPath: string): ArkConfig | null;
64
+ /** Resolve relative import specifier to an absolute path candidate (TS-oriented). */
65
+ declare function resolveRelativeImport(fromFile: string, specifier: string): string | null;
66
+ /**
67
+ * Config-driven layer import boundary (primary editor gate).
68
+ * Replaces path-token domain/infra heuristics when ark.config.json is present.
69
+ * Rule id kept as `no-domain-infra-imports` for recommended-config / upgrade stability.
70
+ */
34
71
  declare const noDomainInfraImports: ArkRule;
35
72
  declare const noRawEventPublish: ArkRule;
36
73
  declare const requirePublishSource: ArkRule;
@@ -39,4 +76,4 @@ declare const plugin: ArkEslintPlugin;
39
76
 
40
77
  // @ts-ignore
41
78
  export = plugin;
42
- export { noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, plugin, requirePublishSource };
79
+ export { findConfigPath, globToRegExp, isEdgeDenied, layerForRelativePath, loadArkConfig, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, patternSpecificity, plugin, requirePublishSource, resolveRelativeImport };