ark-runtime-kernel 1.3.0 → 1.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.
package/bin/ark-mcp.mjs CHANGED
@@ -21,6 +21,11 @@
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';
@@ -39,10 +44,12 @@ function parseArgs(argv) {
39
44
  configExplicit: false,
40
45
  manifest: undefined,
41
46
  hook: false,
47
+ sessionContext: false,
42
48
  };
43
49
  for (let i = 2; i < argv.length; i += 1) {
44
50
  const a = argv[i];
45
51
  if (a === '--hook') args.hook = true;
52
+ else if (a === '--session-context') args.sessionContext = true;
46
53
  else if (a === '--root') args.root = path.resolve(argv[++i]);
47
54
  else if (a === '--config') {
48
55
  args.config = argv[++i];
@@ -168,7 +175,27 @@ function runHook(gate, config, args) {
168
175
  const result = gate.validate(source, { layer, filePath });
169
176
  if (result.valid) return;
170
177
 
171
- 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(
172
199
  (violation) =>
173
200
  `- [${violation.ruleId}] ${violation.message}${violation.line ? ` (line ${violation.line})` : ''}`
174
201
  );
@@ -182,12 +209,65 @@ function runHook(gate, config, args) {
182
209
  process.exitCode = 2;
183
210
  }
184
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
+
185
257
  async function main() {
186
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
+
187
268
  const ark = await loadArk();
188
269
  const ts = await loadOptionalTypeScript();
189
270
 
190
- const configPath = resolveInRoot(args.root, args.config);
191
271
  const config =
192
272
  (configPath ? readJson(configPath, { required: args.configExplicit }) : undefined) ?? {
193
273
  include: ['src'],
@@ -248,11 +328,28 @@ async function main() {
248
328
  });
249
329
  }
250
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
+
251
347
  const gate = ark.createAICodeGate({
252
348
  architectureProfile: profile,
253
349
  intents,
254
350
  enforceIntentAllowlist: intents.length > 0,
255
351
  typescript: ts,
352
+ forbiddenGlobals,
256
353
  });
257
354
 
258
355
  if (args.hook) {
@@ -260,6 +357,11 @@ async function main() {
260
357
  return;
261
358
  }
262
359
 
360
+ if (args.sessionContext) {
361
+ printSessionContext(config, profile, forbiddenGlobals, args);
362
+ return;
363
+ }
364
+
263
365
  const SERVER_INFO = { name: 'ark-runtime-kernel', version: ark.version };
264
366
  const DEFAULT_PROTOCOL = '2024-11-05';
265
367
 
@@ -342,6 +444,7 @@ async function main() {
342
444
  name: profile.name,
343
445
  layers: profile.layers,
344
446
  rules: profile.rules,
447
+ ...(Object.keys(forbiddenGlobals).length > 0 ? { forbiddenGlobals } : {}),
345
448
  ...(suggestions.length > 0
346
449
  ? {
347
450
  suggestedLayers: suggestions,
@@ -69,6 +69,13 @@ 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,11 +89,55 @@ export function createElevenLayerConfig(options = {}) {
82
89
  ),
83
90
  intentPrefixes: entry.prefixes,
84
91
  optional,
92
+ ...(entry.layer === 'DomainModel'
93
+ ? { forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS }
94
+ : {}),
85
95
  })),
86
96
  rules: DEFAULT_RULES,
87
97
  };
88
98
  }
89
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
+
90
141
  const _regexpCache = new Map();
91
142
 
92
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"]}
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  // src/version.ts
4
- var version = "1.3.0";
4
+ var version = "1.5.0";
5
5
 
6
6
  // src/kernel/intent/IntentRegistry.ts
7
7
  var IntentRegistry = class {
@@ -2101,6 +2101,33 @@ function tsPublishSourceLiteral(ts, node) {
2101
2101
  const rawMetadata = tsObjectPropertyValue(ts, firstArg, "metadata");
2102
2102
  return tsStringLiteralText(ts, tsObjectPropertyValue(ts, rawMetadata, "source")) ?? tsStringLiteralText(ts, tsObjectPropertyValue(ts, secondArg, "source")) ?? tsStringLiteralText(ts, tsObjectPropertyValue(ts, thirdArg, "source"));
2103
2103
  }
2104
+ function analyzeForbiddenGlobals(ts, source, filePath, layer, forbidden) {
2105
+ const entries = new Set(forbidden);
2106
+ if (entries.size === 0) return [];
2107
+ const sourceFile = ts.createSourceFile("generated.ts", source, ts.ScriptTarget.Latest, true);
2108
+ const violations = [];
2109
+ const flag = (name, node) => violations.push(
2110
+ violation("FORBIDDEN_GLOBAL", `${layer} must not use the ambient global "${name}".`, {
2111
+ line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
2112
+ filePath,
2113
+ target: name,
2114
+ fromLayer: layer,
2115
+ suggestion: "Inject the capability through a port (e.g. a Clock, IdGenerator, or HttpPort) instead of reaching for the ambient global."
2116
+ })
2117
+ );
2118
+ const visit = (node) => {
2119
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
2120
+ const dotted = `${node.expression.text}.${node.name.text}`;
2121
+ if (entries.has(dotted)) flag(dotted, node);
2122
+ else if (entries.has(node.expression.text)) flag(node.expression.text, node);
2123
+ } else if ((ts.isCallExpression(node) || ts.isNewExpression(node)) && node.expression && ts.isIdentifier(node.expression) && entries.has(node.expression.text)) {
2124
+ flag(node.expression.text, node);
2125
+ }
2126
+ ts.forEachChild(node, visit);
2127
+ };
2128
+ visit(sourceFile);
2129
+ return violations;
2130
+ }
2104
2131
  function analyzePublishAst(ts, source, context, profile) {
2105
2132
  const sourceFile = ts.createSourceFile(
2106
2133
  "generated.ts",
@@ -2299,6 +2326,26 @@ function createAICodeGate(options = {}) {
2299
2326
  }
2300
2327
  }
2301
2328
  }
2329
+ if (options.typescript && contextLayer && options.forbiddenGlobals?.[contextLayer]?.length) {
2330
+ try {
2331
+ violations.push(
2332
+ ...analyzeForbiddenGlobals(
2333
+ options.typescript,
2334
+ source,
2335
+ filePath,
2336
+ contextLayer,
2337
+ options.forbiddenGlobals[contextLayer]
2338
+ )
2339
+ );
2340
+ } catch (err) {
2341
+ violations.push(
2342
+ violation(
2343
+ "AST_ANALYZER_ERROR",
2344
+ `TypeScript AST analyzer failed: ${err instanceof Error ? err.message : String(err)}`
2345
+ )
2346
+ );
2347
+ }
2348
+ }
2302
2349
  if (options.typescript) {
2303
2350
  try {
2304
2351
  violations.push(