ark-runtime-kernel 1.2.0 → 1.4.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.
package/bin/ark-mcp.mjs CHANGED
@@ -21,11 +21,21 @@
21
21
  * stdin, validates the file content a Write/Edit/MultiEdit is about to produce, and exits
22
22
  * 2 with the violations on stderr when the write must be blocked (0 otherwise). This is
23
23
  * the copy-paste integration for agent runtimes whose hooks run shell commands.
24
+ *
25
+ * --session-context runs one-shot and prints a compact contract summary (layers, rule
26
+ * count, forbidden globals, baseline state, check command) to stdout. Bind it to a
27
+ * SessionStart hook so the agent has the architecture in context from the first token,
28
+ * instead of learning it by rejection.
24
29
  */
25
30
  import fs from 'node:fs';
26
31
  import path from 'node:path';
27
32
  import readline from 'node:readline';
28
- import { DEFAULT_INTENT_PREFIXES, DEFAULT_RULES, layerForFile } from './ark-shared.mjs';
33
+ import {
34
+ DEFAULT_INTENT_PREFIXES,
35
+ DEFAULT_LAYER_DIRECTORIES,
36
+ DEFAULT_RULES,
37
+ layerForFile,
38
+ } from './ark-shared.mjs';
29
39
 
30
40
  function parseArgs(argv) {
31
41
  const args = {
@@ -34,10 +44,12 @@ function parseArgs(argv) {
34
44
  configExplicit: false,
35
45
  manifest: undefined,
36
46
  hook: false,
47
+ sessionContext: false,
37
48
  };
38
49
  for (let i = 2; i < argv.length; i += 1) {
39
50
  const a = argv[i];
40
51
  if (a === '--hook') args.hook = true;
52
+ else if (a === '--session-context') args.sessionContext = true;
41
53
  else if (a === '--root') args.root = path.resolve(argv[++i]);
42
54
  else if (a === '--config') {
43
55
  args.config = argv[++i];
@@ -163,7 +175,27 @@ function runHook(gate, config, args) {
163
175
  const result = gate.validate(source, { layer, filePath });
164
176
  if (result.valid) return;
165
177
 
166
- const lines = result.violations.map(
178
+ // Ratchet semantics (same philosophy as ark-check --baseline): an edit is blocked only
179
+ // when it ADDS violations relative to the file's current on-disk state. Otherwise a
180
+ // pre-existing violation — frozen in a baseline or predating Ark adoption — would make
181
+ // every subsequent edit to that file un-writable while CI passes. Keys ignore line
182
+ // numbers (edits shift them) and collapse duplicates, mirroring ark-check's baselineKey.
183
+ const violationKey = (violation) => `${violation.ruleId}|${violation.target ?? violation.message}`;
184
+ let existingKeys = new Set();
185
+ try {
186
+ const current = fs.readFileSync(filePath, 'utf8');
187
+ existingKeys = new Set(
188
+ gate.validate(current, { layer, filePath }).violations.map(violationKey)
189
+ );
190
+ } catch {
191
+ // New file: nothing pre-exists, every violation is new.
192
+ }
193
+ const newViolations = result.violations.filter(
194
+ (violation) => !existingKeys.has(violationKey(violation))
195
+ );
196
+ if (newViolations.length === 0) return;
197
+
198
+ const lines = newViolations.map(
167
199
  (violation) =>
168
200
  `- [${violation.ruleId}] ${violation.message}${violation.line ? ` (line ${violation.line})` : ''}`
169
201
  );
@@ -177,12 +209,65 @@ function runHook(gate, config, args) {
177
209
  process.exitCode = 2;
178
210
  }
179
211
 
212
+ /**
213
+ * One-shot SessionStart context: a compact summary of the contract on stdout so the
214
+ * agent starts the session already knowing the architecture. Advisory — never blocks
215
+ * and never exits non-zero for missing optional inputs (e.g. no baseline file).
216
+ */
217
+ function printSessionContext(config, profile, forbiddenGlobals, args) {
218
+ const lines = ['Ark architecture contract governs this project (ark.config.json is authoritative).'];
219
+
220
+ const configLayers = Array.isArray(config.layers) ? config.layers : [];
221
+ if (configLayers.length > 0) {
222
+ lines.push('Layers:');
223
+ for (const layer of configLayers) {
224
+ const globals = forbiddenGlobals[layer.name];
225
+ const globalsNote = globals ? ` — forbidden globals: ${globals.join(', ')}` : '';
226
+ lines.push(` - ${layer.name}: ${(layer.patterns ?? []).join(', ')}${globalsNote}`);
227
+ }
228
+ } else {
229
+ lines.push(
230
+ `Layers: none configured — the default 11-layer profile applies to intent references.`
231
+ );
232
+ }
233
+
234
+ const denied = (profile.rules ?? []).filter((rule) => !rule.allowed).length;
235
+ lines.push(
236
+ `Rules: ${denied} denied layer edge(s). Full contract: ark://manifest MCP resource.`
237
+ );
238
+
239
+ // Advisory output: a malformed baseline must not abort the summary.
240
+ let baseline;
241
+ try {
242
+ baseline = readJson(path.join(args.root, '.ark-baseline.json'));
243
+ } catch {
244
+ baseline = undefined;
245
+ }
246
+ if (Array.isArray(baseline?.violations)) {
247
+ lines.push(
248
+ `Baseline: ${baseline.violations.length} frozen violation(s) — only NEW violations fail; do not add to them.`
249
+ );
250
+ }
251
+
252
+ lines.push('After edits run: npx ark-check --root . --config ark.config.json --strict-config');
253
+ lines.push('If Ark reports violations, fix the architecture instead of weakening the gate.');
254
+ process.stdout.write(`${lines.join('\n')}\n`);
255
+ }
256
+
180
257
  async function main() {
181
258
  const args = parseArgs(process.argv);
259
+ const configPath = resolveInRoot(args.root, args.config);
260
+
261
+ // SessionStart contract injection is only meaningful in Ark-governed projects. Bail
262
+ // out silently (before loading dist) when there is no config, so the hook is safe
263
+ // even if a user installs it in their GLOBAL settings instead of per-project.
264
+ if (args.sessionContext && !(configPath && fs.existsSync(configPath))) {
265
+ return;
266
+ }
267
+
182
268
  const ark = await loadArk();
183
269
  const ts = await loadOptionalTypeScript();
184
270
 
185
- const configPath = resolveInRoot(args.root, args.config);
186
271
  const config =
187
272
  (configPath ? readJson(configPath, { required: args.configExplicit }) : undefined) ?? {
188
273
  include: ['src'],
@@ -243,11 +328,28 @@ async function main() {
243
328
  });
244
329
  }
245
330
 
331
+ // Layer → forbidden ambient globals, straight from ark.config.json. Enforced by the
332
+ // gate only when the target file's layer is known (same data ark-check enforces in CI).
333
+ const forbiddenGlobals = Object.fromEntries(
334
+ configLayers
335
+ .filter(
336
+ (layer) =>
337
+ layer.name &&
338
+ Array.isArray(layer.forbiddenGlobals) &&
339
+ layer.forbiddenGlobals.some((entry) => typeof entry === 'string')
340
+ )
341
+ .map((layer) => [
342
+ layer.name,
343
+ layer.forbiddenGlobals.filter((entry) => typeof entry === 'string'),
344
+ ])
345
+ );
346
+
246
347
  const gate = ark.createAICodeGate({
247
348
  architectureProfile: profile,
248
349
  intents,
249
350
  enforceIntentAllowlist: intents.length > 0,
250
351
  typescript: ts,
352
+ forbiddenGlobals,
251
353
  });
252
354
 
253
355
  if (args.hook) {
@@ -255,6 +357,11 @@ async function main() {
255
357
  return;
256
358
  }
257
359
 
360
+ if (args.sessionContext) {
361
+ printSessionContext(config, profile, forbiddenGlobals, args);
362
+ return;
363
+ }
364
+
258
365
  const SERVER_INFO = { name: 'ark-runtime-kernel', version: ark.version };
259
366
  const DEFAULT_PROTOCOL = '2024-11-05';
260
367
 
@@ -297,6 +404,31 @@ async function main() {
297
404
  },
298
405
  ];
299
406
 
407
+ // Layers from the 11-layer profile that this project has NOT declared, with their
408
+ // conventional directories: tells the agent where a new kind of code (a saga, a job,
409
+ // a read model, ...) belongs BEFORE it improvises a location the gate can't govern.
410
+ // A default layer is dropped when the project already claims any of its intent
411
+ // prefixes under another name (e.g. a `core` layer owning `Domain.`) — suggesting
412
+ // DomainModel there would tell the agent to create a second layer for the same
413
+ // prefix, making longest-prefix resolution ambiguous.
414
+ function suggestedLayers() {
415
+ const activeNames = new Set(profile.layers.map((layer) => layer.name));
416
+ const claimedPrefixes = new Set(
417
+ profile.layers.flatMap((layer) =>
418
+ (layer.prefixes ?? []).map((p) => (p.endsWith('.') ? p : `${p}.`))
419
+ )
420
+ );
421
+ return DEFAULT_INTENT_PREFIXES.filter(
422
+ (entry) =>
423
+ !activeNames.has(entry.layer) &&
424
+ !entry.prefixes.some((p) => claimedPrefixes.has(p.endsWith('.') ? p : `${p}.`))
425
+ ).map((entry) => ({
426
+ layer: entry.layer,
427
+ intentPrefixes: entry.prefixes,
428
+ conventionalDirectories: DEFAULT_LAYER_DIRECTORIES[entry.layer] ?? [],
429
+ }));
430
+ }
431
+
300
432
  function manifestText() {
301
433
  if (projectManifest) {
302
434
  return JSON.stringify(
@@ -305,12 +437,24 @@ async function main() {
305
437
  2
306
438
  );
307
439
  }
440
+ const suggestions = suggestedLayers();
308
441
  return JSON.stringify(
309
442
  {
310
443
  source: profile === ark.elevenLayerProfile ? 'strictDefaultElevenLayerProfile' : 'project',
311
444
  name: profile.name,
312
445
  layers: profile.layers,
313
446
  rules: profile.rules,
447
+ ...(Object.keys(forbiddenGlobals).length > 0 ? { forbiddenGlobals } : {}),
448
+ ...(suggestions.length > 0
449
+ ? {
450
+ suggestedLayers: suggestions,
451
+ suggestedLayersNote:
452
+ 'Layers from the default 11-layer profile this project has not declared. ' +
453
+ 'When creating a NEW kind of code that fits one of these, place it in a ' +
454
+ 'conventional directory and add the layer to ark.config.json instead of ' +
455
+ 'inventing an ungoverned location.',
456
+ }
457
+ : {}),
314
458
  },
315
459
  null,
316
460
  2
@@ -69,23 +69,75 @@ export const DEFAULT_RULES = createStrictDenyRules(
69
69
  DEFAULT_ALLOWED_FLOWS
70
70
  );
71
71
 
72
+ /**
73
+ * Default ambient globals forbidden in the domain layer: a pure domain does no I/O and is
74
+ * deterministic. `console` is deliberately omitted (too common during adoption); add it per
75
+ * project via the layer's `forbiddenGlobals` in ark.config.json.
76
+ */
77
+ export const DEFAULT_DOMAIN_FORBIDDEN_GLOBALS = ['fetch', 'process', 'Date.now', 'Math.random'];
78
+
72
79
  export function createElevenLayerConfig(options = {}) {
73
80
  const rootDir = options.rootDir ?? 'src';
74
81
  const optional = options.optionalLayers ?? true;
82
+ const prefix = rootDir === '.' ? '' : `${rootDir}/`;
75
83
  return {
76
84
  include: options.include ?? [rootDir],
77
85
  layers: DEFAULT_INTENT_PREFIXES.map((entry) => ({
78
86
  name: entry.layer,
79
87
  patterns: (DEFAULT_LAYER_DIRECTORIES[entry.layer] ?? [entry.layer]).map(
80
- (directory) => `${rootDir}/${directory}/**`
88
+ (directory) => `${prefix}${directory}/**`
81
89
  ),
82
90
  intentPrefixes: entry.prefixes,
83
91
  optional,
92
+ ...(entry.layer === 'DomainModel'
93
+ ? { forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS }
94
+ : {}),
84
95
  })),
85
96
  rules: DEFAULT_RULES,
86
97
  };
87
98
  }
88
99
 
100
+ /**
101
+ * Find uses of forbidden ambient globals in a TypeScript source file.
102
+ *
103
+ * Detection is deliberately positional, not scope-aware (kept in sync with
104
+ * `collectForbiddenGlobalUses` in src/kernel/ai-gate/AICodeGate.ts — the CLIs must not
105
+ * import from dist):
106
+ * - a dotted entry ("Date.now") flags `Date.now` property accesses
107
+ * - a bare entry ("console", "fetch") flags property accesses on it (`console.log`),
108
+ * direct calls (`fetch(...)`), and constructions (`new WebSocket(...)`)
109
+ * Bare identifier mentions in other positions (types, shadowed locals, import names) are
110
+ * NOT flagged, trading a little recall for near-zero false positives without a type checker.
111
+ *
112
+ * Returns [{ name, node }] where `name` is the matched forbidden entry.
113
+ */
114
+ export function collectForbiddenGlobalUses(ts, sourceFile, forbidden) {
115
+ const entries = new Set(forbidden ?? []);
116
+ if (entries.size === 0) return [];
117
+ const uses = [];
118
+
119
+ const visit = (node) => {
120
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
121
+ const dotted = `${node.expression.text}.${node.name.text}`;
122
+ if (entries.has(dotted)) {
123
+ uses.push({ name: dotted, node });
124
+ } else if (entries.has(node.expression.text)) {
125
+ uses.push({ name: node.expression.text, node });
126
+ }
127
+ } else if (
128
+ (ts.isCallExpression(node) || ts.isNewExpression(node)) &&
129
+ node.expression &&
130
+ ts.isIdentifier(node.expression) &&
131
+ entries.has(node.expression.text)
132
+ ) {
133
+ uses.push({ name: node.expression.text, node });
134
+ }
135
+ ts.forEachChild(node, visit);
136
+ };
137
+ visit(sourceFile);
138
+ return uses;
139
+ }
140
+
89
141
  const _regexpCache = new Map();
90
142
 
91
143
  function escapeLiteral(ch) {
@@ -122,10 +122,57 @@ var requirePublishSource = {
122
122
  };
123
123
  }
124
124
  };
125
+ var DEFAULT_FORBIDDEN_GLOBALS = ["fetch", "process", "Date.now", "Math.random"];
126
+ var noForbiddenGlobals = {
127
+ meta: {
128
+ type: "problem",
129
+ docs: {
130
+ description: 'Disallow ambient globals (e.g. fetch, Date.now) in architecture-governed code; scope the rule to layer directories via ESLint "files" patterns.'
131
+ },
132
+ messages: {
133
+ forbiddenGlobal: 'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.'
134
+ },
135
+ schema: [
136
+ {
137
+ type: "object",
138
+ properties: {
139
+ globals: { type: "array", items: { type: "string" } }
140
+ },
141
+ additionalProperties: false
142
+ }
143
+ ]
144
+ },
145
+ create(context) {
146
+ const option = context.options?.[0];
147
+ const globals = new Set(option?.globals ?? DEFAULT_FORBIDDEN_GLOBALS);
148
+ const report = (node, name) => context.report({ node, messageId: "forbiddenGlobal", data: { name } });
149
+ return {
150
+ // Same positional detection as ark-check's FORBIDDEN_GLOBAL: property accesses on a
151
+ // forbidden base (console.log, Date.now), direct calls, and constructions. Bare
152
+ // identifier mentions elsewhere are not flagged (avoids shadowed-local false positives).
153
+ MemberExpression(node) {
154
+ const base = node.object?.type === "Identifier" ? node.object.name : void 0;
155
+ if (!base) return;
156
+ const dotted = `${base}.${propertyName(node.property) ?? ""}`;
157
+ if (globals.has(dotted)) report(node, dotted);
158
+ else if (globals.has(base)) report(node, base);
159
+ },
160
+ CallExpression(node) {
161
+ const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
162
+ if (callee && globals.has(callee)) report(node, callee);
163
+ },
164
+ NewExpression(node) {
165
+ const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
166
+ if (callee && globals.has(callee)) report(node, callee);
167
+ }
168
+ };
169
+ }
170
+ };
125
171
  var rules = {
126
172
  "no-domain-infra-imports": noDomainInfraImports,
127
173
  "no-raw-event-publish": noRawEventPublish,
128
- "require-publish-source": requirePublishSource
174
+ "require-publish-source": requirePublishSource,
175
+ "no-forbidden-globals": noForbiddenGlobals
129
176
  };
130
177
  var plugin = { rules };
131
178
  plugin.configs = {
@@ -142,6 +189,7 @@ var eslint_default = plugin;
142
189
 
143
190
  exports.default = eslint_default;
144
191
  exports.noDomainInfraImports = noDomainInfraImports;
192
+ exports.noForbiddenGlobals = noForbiddenGlobals;
145
193
  exports.noRawEventPublish = noRawEventPublish;
146
194
  exports.plugin = plugin;
147
195
  exports.requirePublishSource = requirePublishSource;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/eslint/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["type RuleContext = {\n report(descriptor: Record<string, unknown>): void;\n getFilename?: () => string;\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 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 rules = {\n 'no-domain-infra-imports': noDomainInfraImports,\n 'no-raw-event-publish': noRawEventPublish,\n 'require-publish-source': requirePublishSource,\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"]}
1
+ {"version":3,"sources":["../../src/eslint/index.ts"],"names":[],"mappings":"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","file":"index.cjs","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"]}
@@ -1,6 +1,7 @@
1
1
  type RuleContext = {
2
2
  report(descriptor: Record<string, unknown>): void;
3
3
  getFilename?: () => string;
4
+ options?: unknown[];
4
5
  };
5
6
  type RuleListener = Record<string, (node: AstNode) => void>;
6
7
  type AstNode = {
@@ -9,6 +10,7 @@ type AstNode = {
9
10
  value?: unknown;
10
11
  source?: AstNode;
11
12
  callee?: AstNode;
13
+ object?: AstNode;
12
14
  property?: AstNode;
13
15
  key?: AstNode;
14
16
  arguments?: AstNode[];
@@ -32,6 +34,7 @@ type ArkEslintPlugin = {
32
34
  declare const noDomainInfraImports: ArkRule;
33
35
  declare const noRawEventPublish: ArkRule;
34
36
  declare const requirePublishSource: ArkRule;
37
+ declare const noForbiddenGlobals: ArkRule;
35
38
  declare const plugin: ArkEslintPlugin;
36
39
 
37
- export { plugin as default, noDomainInfraImports, noRawEventPublish, plugin, requirePublishSource };
40
+ export { plugin as default, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, plugin, requirePublishSource };
@@ -1,6 +1,7 @@
1
1
  type RuleContext = {
2
2
  report(descriptor: Record<string, unknown>): void;
3
3
  getFilename?: () => string;
4
+ options?: unknown[];
4
5
  };
5
6
  type RuleListener = Record<string, (node: AstNode) => void>;
6
7
  type AstNode = {
@@ -9,6 +10,7 @@ type AstNode = {
9
10
  value?: unknown;
10
11
  source?: AstNode;
11
12
  callee?: AstNode;
13
+ object?: AstNode;
12
14
  property?: AstNode;
13
15
  key?: AstNode;
14
16
  arguments?: AstNode[];
@@ -32,6 +34,7 @@ type ArkEslintPlugin = {
32
34
  declare const noDomainInfraImports: ArkRule;
33
35
  declare const noRawEventPublish: ArkRule;
34
36
  declare const requirePublishSource: ArkRule;
37
+ declare const noForbiddenGlobals: ArkRule;
35
38
  declare const plugin: ArkEslintPlugin;
36
39
 
37
- export { plugin as default, noDomainInfraImports, noRawEventPublish, plugin, requirePublishSource };
40
+ export { plugin as default, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, plugin, requirePublishSource };
@@ -118,10 +118,57 @@ var requirePublishSource = {
118
118
  };
119
119
  }
120
120
  };
121
+ var DEFAULT_FORBIDDEN_GLOBALS = ["fetch", "process", "Date.now", "Math.random"];
122
+ var noForbiddenGlobals = {
123
+ meta: {
124
+ type: "problem",
125
+ docs: {
126
+ description: 'Disallow ambient globals (e.g. fetch, Date.now) in architecture-governed code; scope the rule to layer directories via ESLint "files" patterns.'
127
+ },
128
+ messages: {
129
+ forbiddenGlobal: 'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.'
130
+ },
131
+ schema: [
132
+ {
133
+ type: "object",
134
+ properties: {
135
+ globals: { type: "array", items: { type: "string" } }
136
+ },
137
+ additionalProperties: false
138
+ }
139
+ ]
140
+ },
141
+ create(context) {
142
+ const option = context.options?.[0];
143
+ const globals = new Set(option?.globals ?? DEFAULT_FORBIDDEN_GLOBALS);
144
+ const report = (node, name) => context.report({ node, messageId: "forbiddenGlobal", data: { name } });
145
+ return {
146
+ // Same positional detection as ark-check's FORBIDDEN_GLOBAL: property accesses on a
147
+ // forbidden base (console.log, Date.now), direct calls, and constructions. Bare
148
+ // identifier mentions elsewhere are not flagged (avoids shadowed-local false positives).
149
+ MemberExpression(node) {
150
+ const base = node.object?.type === "Identifier" ? node.object.name : void 0;
151
+ if (!base) return;
152
+ const dotted = `${base}.${propertyName(node.property) ?? ""}`;
153
+ if (globals.has(dotted)) report(node, dotted);
154
+ else if (globals.has(base)) report(node, base);
155
+ },
156
+ CallExpression(node) {
157
+ const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
158
+ if (callee && globals.has(callee)) report(node, callee);
159
+ },
160
+ NewExpression(node) {
161
+ const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
162
+ if (callee && globals.has(callee)) report(node, callee);
163
+ }
164
+ };
165
+ }
166
+ };
121
167
  var rules = {
122
168
  "no-domain-infra-imports": noDomainInfraImports,
123
169
  "no-raw-event-publish": noRawEventPublish,
124
- "require-publish-source": requirePublishSource
170
+ "require-publish-source": requirePublishSource,
171
+ "no-forbidden-globals": noForbiddenGlobals
125
172
  };
126
173
  var plugin = { rules };
127
174
  plugin.configs = {
@@ -136,6 +183,6 @@ plugin.configs = {
136
183
  };
137
184
  var eslint_default = plugin;
138
185
 
139
- export { eslint_default as default, noDomainInfraImports, noRawEventPublish, plugin, requirePublishSource };
186
+ export { eslint_default as default, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, plugin, requirePublishSource };
140
187
  //# sourceMappingURL=index.js.map
141
188
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/eslint/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["type RuleContext = {\n report(descriptor: Record<string, unknown>): void;\n getFilename?: () => string;\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 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 rules = {\n 'no-domain-infra-imports': noDomainInfraImports,\n 'no-raw-event-publish': noRawEventPublish,\n 'require-publish-source': requirePublishSource,\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"]}
1
+ {"version":3,"sources":["../../src/eslint/index.ts"],"names":[],"mappings":"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","file":"index.js","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"]}