arkgate 2.12.0 → 3.0.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 (88) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/README.md +90 -51
  3. package/bin/ark-check.mjs +156 -39
  4. package/bin/ark-mcp.mjs +119 -6
  5. package/bin/ark-shared.mjs +216 -129
  6. package/bin/ark.mjs +134 -34
  7. package/bin/lib/adapter-contract.mjs +93 -0
  8. package/bin/lib/agent-gates.mjs +13 -0
  9. package/bin/lib/analysis-engine.mjs +1171 -0
  10. package/bin/lib/architecture-scan.mjs +84 -127
  11. package/bin/lib/ci-and-commands.mjs +40 -3
  12. package/bin/lib/codex-home.mjs +7 -0
  13. package/bin/lib/config-contract.mjs +331 -0
  14. package/bin/lib/config-warnings.mjs +7 -205
  15. package/bin/lib/doctor-plan.mjs +43 -16
  16. package/bin/lib/enforcement-profiles.mjs +97 -0
  17. package/bin/lib/field-install.mjs +67 -10
  18. package/bin/lib/gate-files.mjs +42 -3
  19. package/bin/lib/graph-cycles.mjs +4 -54
  20. package/bin/lib/hook-templates.mjs +33 -1
  21. package/bin/lib/host-support-matrix.mjs +83 -0
  22. package/bin/lib/install-migrate.mjs +99 -30
  23. package/bin/lib/mcp-adoption.mjs +35 -3
  24. package/bin/lib/open-html.mjs +75 -0
  25. package/bin/lib/presets.mjs +45 -4
  26. package/bin/lib/safety-diagnostics.mjs +36 -15
  27. package/bin/lib/scan-files.mjs +12 -1
  28. package/bin/lib/skill-install.mjs +72 -1
  29. package/bin/lib/source-policy.mjs +36 -0
  30. package/bin/lib/start-preview.mjs +271 -0
  31. package/bin/lib/ts-resolve.mjs +13 -3
  32. package/bin/lib/weakest-link.mjs +417 -0
  33. package/bin/lib/write-path-capabilities.mjs +186 -0
  34. package/bin/lib/write-path-detect.mjs +62 -99
  35. package/compat/nestjs.cjs +2 -0
  36. package/compat/nestjs.d.ts +2 -0
  37. package/compat/nestjs.js +1 -0
  38. package/compat/runtime.cjs +2 -0
  39. package/compat/runtime.d.ts +2 -0
  40. package/compat/runtime.js +1 -0
  41. package/dist/configContract-BxSIwVRo.d.cts +259 -0
  42. package/dist/configContract-BxSIwVRo.d.ts +259 -0
  43. package/dist/eslint/index.cjs +500 -61
  44. package/dist/eslint/index.d.cts +36 -20
  45. package/dist/eslint/index.d.ts +36 -20
  46. package/dist/eslint/index.js +500 -61
  47. package/dist/index.cjs +1349 -2741
  48. package/dist/index.d.cts +449 -483
  49. package/dist/index.d.ts +449 -483
  50. package/dist/index.js +1325 -2687
  51. package/docs/agent-guide.md +58 -34
  52. package/docs/ai-gates.md +79 -21
  53. package/docs/configuration.md +97 -0
  54. package/docs/enthusiast/README.md +3 -3
  55. package/docs/enthusiast/how-to-agent-gates.md +7 -3
  56. package/docs/migrate-from-ark-runtime-kernel.md +5 -3
  57. package/docs/package-surface.md +19 -19
  58. package/docs/production-hardening.md +31 -5
  59. package/docs/threat-model.md +65 -0
  60. package/docs/typescript-support.md +30 -3
  61. package/package.json +46 -11
  62. package/schemas/ark.analysis-result.schema.json +91 -0
  63. package/schemas/ark.config.schema.json +750 -0
  64. package/server.json +2 -2
  65. package/templates/hooks/pre-commit-ark +37 -0
  66. package/templates/skills/ark-architect.md +3 -2
  67. package/templates/skills/ark-coverage.md +2 -2
  68. package/templates/skills/ark-runtime.md +8 -5
  69. package/templates/skills/ark-upgrade.md +36 -16
  70. package/tests/fixtures/ts-consumer/ark.config.json +2 -0
  71. package/dist/eslint/index.cjs.map +0 -1
  72. package/dist/eslint/index.js.map +0 -1
  73. package/dist/index.cjs.map +0 -1
  74. package/dist/index.js.map +0 -1
  75. package/dist/nestjs/index.cjs +0 -2498
  76. package/dist/nestjs/index.cjs.map +0 -1
  77. package/dist/nestjs/index.d.cts +0 -22
  78. package/dist/nestjs/index.d.ts +0 -22
  79. package/dist/nestjs/index.js +0 -2474
  80. package/dist/nestjs/index.js.map +0 -1
  81. package/dist/runtime/index.cjs +0 -3352
  82. package/dist/runtime/index.cjs.map +0 -1
  83. package/dist/runtime/index.d.cts +0 -2
  84. package/dist/runtime/index.d.ts +0 -2
  85. package/dist/runtime/index.js +0 -3270
  86. package/dist/runtime/index.js.map +0 -1
  87. package/dist/types-BZ17b9i5.d.cts +0 -1068
  88. package/dist/types-BZ17b9i5.d.ts +0 -1068
@@ -170,6 +170,368 @@ function isEdgeDenied(rules2, from, to, options) {
170
170
  return findDeniedEdgeRule(rules2, from, to, options) !== void 0;
171
171
  }
172
172
 
173
+ // src/domain/configContract.ts
174
+ var ARK_CONFIG_SCHEMA_VERSION = "1.0";
175
+ var ARK_CONFIG_SCHEMA_URL = "https://unpkg.com/arkgate@2/schemas/ark.config.schema.json";
176
+ var DEFAULT_LAYER_NAMES = [
177
+ "DomainModel",
178
+ "ApplicationOrchestration",
179
+ "PersistenceAdapters",
180
+ "IntegrationAdapters",
181
+ "WorkflowSagaEngine",
182
+ "BackgroundJobsScheduling",
183
+ "PresentationAdapters",
184
+ "ReportingReadModels",
185
+ "ExtensibilityMetadata",
186
+ "SecurityAuditObservability",
187
+ "Kernel"
188
+ ];
189
+ var DEFAULT_ALLOWED_FLOWS = /* @__PURE__ */ new Set([
190
+ "PresentationAdapters->ApplicationOrchestration",
191
+ "ApplicationOrchestration->DomainModel",
192
+ "WorkflowSagaEngine->ApplicationOrchestration",
193
+ "WorkflowSagaEngine->DomainModel",
194
+ "BackgroundJobsScheduling->ApplicationOrchestration"
195
+ ]);
196
+ function createDefaultRules() {
197
+ const rules2 = [];
198
+ for (const from of DEFAULT_LAYER_NAMES) {
199
+ for (const to of DEFAULT_LAYER_NAMES) {
200
+ if (from === to || DEFAULT_ALLOWED_FLOWS.has(`${from}->${to}`)) continue;
201
+ rules2.push({ from, to, allowed: false });
202
+ }
203
+ }
204
+ return rules2;
205
+ }
206
+ var DEFAULT_ARK_CONFIG_RULES = createDefaultRules();
207
+ var stringArraySchema = {
208
+ type: "array",
209
+ items: { type: "string", minLength: 1 },
210
+ uniqueItems: true
211
+ };
212
+ var ARK_CONFIG_SCHEMA = {
213
+ $schema: "https://json-schema.org/draft/2020-12/schema",
214
+ $id: ARK_CONFIG_SCHEMA_URL,
215
+ title: "ArkGate architecture contract",
216
+ description: "Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",
217
+ type: "object",
218
+ additionalProperties: false,
219
+ required: ["$schema", "schemaVersion", "include", "layers", "rules"],
220
+ properties: {
221
+ $schema: {
222
+ type: "string",
223
+ minLength: 1,
224
+ default: ARK_CONFIG_SCHEMA_URL,
225
+ description: "Editor-facing URL or local path for this JSON Schema."
226
+ },
227
+ schemaVersion: {
228
+ type: "string",
229
+ const: ARK_CONFIG_SCHEMA_VERSION,
230
+ default: ARK_CONFIG_SCHEMA_VERSION
231
+ },
232
+ name: { type: "string", minLength: 1 },
233
+ include: { ...stringArraySchema, minItems: 1, default: ["src"] },
234
+ exclude: { ...stringArraySchema, default: [] },
235
+ excludeGenerated: { type: "boolean", default: true },
236
+ frameworkOverlay: { type: "string", minLength: 1 },
237
+ layers: {
238
+ type: "array",
239
+ default: [],
240
+ items: { $ref: "#/$defs/layer" }
241
+ },
242
+ rules: {
243
+ type: "array",
244
+ default: DEFAULT_ARK_CONFIG_RULES,
245
+ items: { $ref: "#/$defs/rule" }
246
+ },
247
+ cyclePolicy: {
248
+ type: "string",
249
+ enum: ["strict", "soft", "framework-soft", "off"],
250
+ default: "strict"
251
+ },
252
+ dynamicImportAllowlist: { ...stringArraySchema, default: [] },
253
+ safety: {
254
+ $ref: "#/$defs/safety",
255
+ default: {
256
+ maxTsSuppressions: 0,
257
+ maxAnyCasts: 0,
258
+ allowInMemory: false,
259
+ allowDisabledPeerIsolation: false
260
+ }
261
+ }
262
+ },
263
+ $defs: {
264
+ layer: {
265
+ type: "object",
266
+ additionalProperties: false,
267
+ required: ["name", "patterns"],
268
+ properties: {
269
+ name: { type: "string", minLength: 1 },
270
+ patterns: { ...stringArraySchema, minItems: 1 },
271
+ exclude: stringArraySchema,
272
+ intentPrefixes: stringArraySchema,
273
+ description: { type: "string", minLength: 1 },
274
+ forbiddenGlobals: stringArraySchema,
275
+ mayImportInfrastructure: { type: "boolean" },
276
+ optional: { type: "boolean" }
277
+ }
278
+ },
279
+ rule: {
280
+ type: "object",
281
+ additionalProperties: false,
282
+ required: ["from", "to", "allowed"],
283
+ properties: {
284
+ from: { type: "string", minLength: 1 },
285
+ to: { type: "string", minLength: 1 },
286
+ allowed: { type: "boolean" },
287
+ message: { type: "string", minLength: 1 },
288
+ peerIsolation: { type: "boolean" },
289
+ sliceFolders: { ...stringArraySchema, minItems: 1 }
290
+ }
291
+ },
292
+ safety: {
293
+ type: "object",
294
+ additionalProperties: false,
295
+ properties: {
296
+ maxTsSuppressions: { type: "integer", minimum: 0, default: 0 },
297
+ maxAnyCasts: { type: "integer", minimum: 0, default: 0 },
298
+ allowInMemory: { type: "boolean", default: false },
299
+ allowDisabledPeerIsolation: { type: "boolean", default: false }
300
+ }
301
+ }
302
+ }
303
+ };
304
+ var ArkConfigValidationError = class extends Error {
305
+ issues;
306
+ source;
307
+ constructor(source, issues) {
308
+ super(
309
+ `Invalid ArkGate config (${source}):
310
+ ${issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n")}`
311
+ );
312
+ this.name = "ArkConfigValidationError";
313
+ this.source = source;
314
+ this.issues = issues;
315
+ }
316
+ };
317
+ function isObject(value) {
318
+ return value !== null && typeof value === "object" && !Array.isArray(value);
319
+ }
320
+ function propertyPath(parent, key) {
321
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${parent}.${key}` : `${parent}[${JSON.stringify(key)}]`;
322
+ }
323
+ function valueType(value) {
324
+ if (value === null) return "null";
325
+ if (Array.isArray(value)) return "array";
326
+ return typeof value;
327
+ }
328
+ function resolveSchemaRef(ref, root) {
329
+ const prefix = "#/$defs/";
330
+ if (!ref.startsWith(prefix)) return void 0;
331
+ return root.$defs[ref.slice(prefix.length)];
332
+ }
333
+ function validateNode(value, schema, path2, root, issues) {
334
+ if (schema.$ref) {
335
+ const referenced = resolveSchemaRef(schema.$ref, root);
336
+ if (!referenced) {
337
+ issues.push({ path: path2, message: `schema reference ${schema.$ref} cannot be resolved` });
338
+ return;
339
+ }
340
+ validateNode(value, referenced, path2, root, issues);
341
+ return;
342
+ }
343
+ if (schema.const !== void 0 && !Object.is(value, schema.const)) {
344
+ issues.push({ path: path2, message: `must equal ${JSON.stringify(schema.const)}` });
345
+ return;
346
+ }
347
+ if (schema.enum && !schema.enum.some((candidate) => Object.is(candidate, value))) {
348
+ issues.push({ path: path2, message: `must be one of ${schema.enum.map(String).join(", ")}` });
349
+ return;
350
+ }
351
+ if (schema.type === "object") {
352
+ if (!isObject(value)) {
353
+ issues.push({ path: path2, message: `must be an object; received ${valueType(value)}` });
354
+ return;
355
+ }
356
+ const properties = schema.properties ?? {};
357
+ for (const key of schema.required ?? []) {
358
+ if (value[key] === void 0) {
359
+ issues.push({ path: propertyPath(path2, key), message: "is required" });
360
+ }
361
+ }
362
+ if (schema.additionalProperties === false) {
363
+ for (const key of Object.keys(value)) {
364
+ if (!(key in properties)) {
365
+ issues.push({ path: propertyPath(path2, key), message: "unknown field" });
366
+ }
367
+ }
368
+ }
369
+ for (const [key, childSchema] of Object.entries(properties)) {
370
+ if (value[key] !== void 0) {
371
+ validateNode(value[key], childSchema, propertyPath(path2, key), root, issues);
372
+ }
373
+ }
374
+ return;
375
+ }
376
+ if (schema.type === "array") {
377
+ if (!Array.isArray(value)) {
378
+ issues.push({ path: path2, message: `must be an array; received ${valueType(value)}` });
379
+ return;
380
+ }
381
+ if (schema.minItems !== void 0 && value.length < schema.minItems) {
382
+ issues.push({ path: path2, message: `must contain at least ${schema.minItems} item(s)` });
383
+ }
384
+ if (schema.uniqueItems) {
385
+ const serialized = value.map((entry) => JSON.stringify(entry));
386
+ if (new Set(serialized).size !== serialized.length) {
387
+ issues.push({ path: path2, message: "must not contain duplicate items" });
388
+ }
389
+ }
390
+ if (schema.items) {
391
+ value.forEach(
392
+ (entry, index) => validateNode(entry, schema.items, `${path2}[${index}]`, root, issues)
393
+ );
394
+ }
395
+ return;
396
+ }
397
+ if (schema.type === "string") {
398
+ if (typeof value !== "string") {
399
+ issues.push({ path: path2, message: `must be a string; received ${valueType(value)}` });
400
+ return;
401
+ }
402
+ if (schema.minLength !== void 0 && value.length < schema.minLength) {
403
+ issues.push({ path: path2, message: `must contain at least ${schema.minLength} character(s)` });
404
+ }
405
+ return;
406
+ }
407
+ if (schema.type === "boolean") {
408
+ if (typeof value !== "boolean") {
409
+ issues.push({ path: path2, message: `must be a boolean; received ${valueType(value)}` });
410
+ }
411
+ return;
412
+ }
413
+ if (schema.type === "integer") {
414
+ if (!Number.isInteger(value)) {
415
+ issues.push({ path: path2, message: `must be an integer; received ${valueType(value)}` });
416
+ return;
417
+ }
418
+ if (schema.minimum !== void 0 && value < schema.minimum) {
419
+ issues.push({ path: path2, message: `must be at least ${schema.minimum}` });
420
+ }
421
+ }
422
+ }
423
+ function defaultedConfig(input) {
424
+ return {
425
+ ...input,
426
+ $schema: input.$schema === void 0 ? ARK_CONFIG_SCHEMA_URL : input.$schema,
427
+ schemaVersion: input.schemaVersion === void 0 ? ARK_CONFIG_SCHEMA_VERSION : input.schemaVersion,
428
+ include: input.include === void 0 ? ["src"] : input.include,
429
+ layers: input.layers === void 0 ? [] : input.layers,
430
+ rules: input.rules === void 0 ? DEFAULT_ARK_CONFIG_RULES.map((rule) => ({ ...rule })) : input.rules
431
+ };
432
+ }
433
+ function migrateArkConfig(input, source = "ark.config.json") {
434
+ if (!isObject(input)) {
435
+ throw new ArkConfigValidationError(source, [
436
+ { path: "$", message: `must be an object; received ${valueType(input)}` }
437
+ ]);
438
+ }
439
+ const migratedFrom = input.schemaVersion === void 0 ? "unversioned" : null;
440
+ if (input.schemaVersion !== void 0 && input.schemaVersion !== ARK_CONFIG_SCHEMA_VERSION) {
441
+ throw new ArkConfigValidationError(source, [
442
+ {
443
+ path: "$.schemaVersion",
444
+ message: `unsupported version ${JSON.stringify(input.schemaVersion)}; expected ${ARK_CONFIG_SCHEMA_VERSION}`
445
+ }
446
+ ]);
447
+ }
448
+ return { candidate: defaultedConfig(input), migratedFrom };
449
+ }
450
+ function loadArkConfigContract(input, source = "ark.config.json") {
451
+ const { candidate, migratedFrom } = migrateArkConfig(input, source);
452
+ const issues = [];
453
+ validateNode(
454
+ candidate,
455
+ ARK_CONFIG_SCHEMA,
456
+ "$",
457
+ ARK_CONFIG_SCHEMA,
458
+ issues
459
+ );
460
+ if (issues.length > 0) throw new ArkConfigValidationError(source, issues);
461
+ return { config: candidate, migratedFrom };
462
+ }
463
+ function parseArkConfigJson(json, source = "ark.config.json") {
464
+ let input;
465
+ try {
466
+ input = JSON.parse(json);
467
+ } catch (error) {
468
+ throw new ArkConfigValidationError(source, [
469
+ {
470
+ path: "$",
471
+ message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`
472
+ }
473
+ ]);
474
+ }
475
+ return loadArkConfigContract(input, source);
476
+ }
477
+
478
+ // src/domain/adapterContract.ts
479
+ function text(value) {
480
+ return typeof value === "string" && value.length > 0 ? value : void 0;
481
+ }
482
+ function positiveInteger(value, fallback) {
483
+ return Number.isInteger(value) && Number(value) > 0 ? Number(value) : fallback;
484
+ }
485
+ function toAdapterDiagnostic(violation, fallbackSeverity = "error") {
486
+ const ruleId = text(violation.ruleId) ?? text(violation.code) ?? "ARK_UNKNOWN";
487
+ const severity = violation.severity === "warning" ? "warning" : fallbackSeverity;
488
+ const evidence = {
489
+ ...text(violation.target) ? { target: text(violation.target) } : {},
490
+ ...text(violation.fromLayer) ? { fromLayer: text(violation.fromLayer) } : {},
491
+ ...text(violation.toLayer) ? { toLayer: text(violation.toLayer) } : {},
492
+ ...typeof violation.typeOnly === "boolean" ? { typeOnly: violation.typeOnly } : {}
493
+ };
494
+ return {
495
+ ruleId,
496
+ severity,
497
+ message: text(violation.message) ?? ruleId,
498
+ location: {
499
+ file: text(violation.file) ?? "<unknown>",
500
+ line: positiveInteger(violation.line, 1),
501
+ column: positiveInteger(violation.column, 1)
502
+ },
503
+ evidence
504
+ };
505
+ }
506
+
507
+ // src/domain/sourcePolicy.ts
508
+ var SOURCE_POLICY_MESSAGES = {
509
+ RAW_EVENT_PUBLISH: "Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",
510
+ PUBLISH_MISSING_SOURCE: "Strict Ark publish calls must include metadata.source."
511
+ };
512
+ function looksLikeArkIntent(value) {
513
+ return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(
514
+ value
515
+ );
516
+ }
517
+ function classifyPublishFacts(facts) {
518
+ if (!facts.publishCall) return [];
519
+ const findings = [];
520
+ if (facts.rawIntentName !== void 0 && looksLikeArkIntent(facts.rawIntentName) || facts.objectHasIntent) {
521
+ findings.push({
522
+ ruleId: "RAW_EVENT_PUBLISH",
523
+ message: SOURCE_POLICY_MESSAGES.RAW_EVENT_PUBLISH
524
+ });
525
+ }
526
+ if (facts.arkPublishCandidate && !facts.hasSource) {
527
+ findings.push({
528
+ ruleId: "PUBLISH_MISSING_SOURCE",
529
+ message: SOURCE_POLICY_MESSAGES.PUBLISH_MISSING_SOURCE
530
+ });
531
+ }
532
+ return findings;
533
+ }
534
+
173
535
  // src/eslint/index.ts
174
536
  function lintedFilename(context) {
175
537
  if (typeof context.physicalFilename === "string" && context.physicalFilename.length > 0) {
@@ -187,6 +549,15 @@ function lintedFilename(context) {
187
549
  }
188
550
  return "";
189
551
  }
552
+ function reportAdapterDiagnostic(context, node, messageId, violation, data) {
553
+ const diagnostic = toAdapterDiagnostic({
554
+ ...violation,
555
+ line: violation.line ?? node.loc?.start?.line,
556
+ column: violation.column ?? (typeof node.loc?.start?.column === "number" ? node.loc.start.column + 1 : void 0)
557
+ });
558
+ context.report({ node, messageId, ...data ? { data } : {}, diagnostic });
559
+ return diagnostic;
560
+ }
190
561
  function findConfigPath(startFile) {
191
562
  if (!startFile || startFile === "<input>" || startFile.startsWith("stdin")) return null;
192
563
  let dir = path.dirname(path.resolve(startFile));
@@ -201,14 +572,10 @@ function findConfigPath(startFile) {
201
572
  var _configCache = /* @__PURE__ */ new Map();
202
573
  function loadArkConfig(configPath) {
203
574
  if (_configCache.has(configPath)) return _configCache.get(configPath) ?? null;
204
- try {
205
- const raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
206
- _configCache.set(configPath, raw);
207
- return raw;
208
- } catch {
209
- _configCache.set(configPath, null);
210
- return null;
211
- }
575
+ if (!fs.existsSync(configPath)) return null;
576
+ const config = parseArkConfigJson(fs.readFileSync(configPath, "utf8"), configPath).config;
577
+ _configCache.set(configPath, config);
578
+ return config;
212
579
  }
213
580
  function resolveRelativeImport(fromFile, specifier) {
214
581
  if (!specifier.startsWith(".")) return null;
@@ -239,6 +606,46 @@ function stringValue(node) {
239
606
  function propertyName(node) {
240
607
  return node?.name ?? stringValue(node);
241
608
  }
609
+ function sourceCodeFor(context) {
610
+ return context.sourceCode ?? context.getSourceCode?.();
611
+ }
612
+ function referenceFor(context, node) {
613
+ let scope = sourceCodeFor(context)?.getScope?.(node);
614
+ while (scope) {
615
+ const reference = scope.references?.find((candidate) => candidate.identifier === node);
616
+ if (reference) return reference;
617
+ scope = scope.upper ?? void 0;
618
+ }
619
+ return void 0;
620
+ }
621
+ function isLocallyBound(context, node, name) {
622
+ const reference = referenceFor(context, node);
623
+ if (reference?.resolved) return (reference.resolved.defs?.length ?? 0) > 0;
624
+ let scope = sourceCodeFor(context)?.getScope?.(node);
625
+ while (scope) {
626
+ const variable = scope.set?.get(name);
627
+ if (variable) return (variable.defs?.length ?? 0) > 0;
628
+ scope = scope.upper ?? void 0;
629
+ }
630
+ return false;
631
+ }
632
+ function isValueIdentifierReference(context, node) {
633
+ const reference = referenceFor(context, node);
634
+ if (reference) return reference.isValueReference !== false;
635
+ return node.parent?.type === "VariableDeclarator" && node.parent.init === node;
636
+ }
637
+ function memberExpressionPath(node) {
638
+ if (node?.type === "Identifier" && node.name) {
639
+ return { root: node, segments: [node.name] };
640
+ }
641
+ if (!node) return void 0;
642
+ const memberLike = node.type === "MemberExpression" || Boolean(node.object && node.property);
643
+ if (!memberLike || node.computed === true) return void 0;
644
+ const base = memberExpressionPath(node.object);
645
+ const property = propertyName(node.property);
646
+ if (!base || !property) return void 0;
647
+ return { root: base.root, segments: [...base.segments, property] };
648
+ }
242
649
  function calleePropertyName(node) {
243
650
  return propertyName(node.callee?.property);
244
651
  }
@@ -252,38 +659,14 @@ function objectHasMetadataSource(node) {
252
659
  const metadata = objectProperty(node, "metadata")?.value;
253
660
  return objectHasProperty(metadata, "source");
254
661
  }
255
- function looksLikeIntent(value) {
256
- return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(
257
- value
258
- );
259
- }
260
662
  function isPublishCall(node) {
261
663
  return calleePropertyName(node) === "publish";
262
664
  }
263
- function isDomainFileHeuristic(filename) {
264
- const normalized = filename.split("\\").join("/").toLowerCase();
265
- return normalized.includes("/domain/") || normalized.endsWith("/domain.ts");
266
- }
267
- function isInfraImportHeuristic(specifier) {
268
- const normalized = specifier.toLowerCase();
269
- return [
270
- "adapter",
271
- "adapters",
272
- "infrastructure",
273
- "persistence",
274
- "repository",
275
- "repositories",
276
- "integration",
277
- "database",
278
- "db"
279
- ].some((token) => normalized.includes(token));
280
- }
281
- var DEFAULT_FORBIDDEN_GLOBALS = ["fetch", "process", "Date.now", "Math.random"];
282
665
  var noDomainInfraImports = {
283
666
  meta: {
284
667
  type: "problem",
285
668
  docs: {
286
- 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."
669
+ description: "Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."
287
670
  },
288
671
  messages: {
289
672
  forbiddenImport: "Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",
@@ -315,17 +698,24 @@ var noDomainInfraImports = {
315
698
  toPath: relTarget,
316
699
  layers: config.layers
317
700
  })) {
318
- context.report({
701
+ reportAdapterDiagnostic(
702
+ context,
319
703
  node,
320
- messageId: "forbiddenImport",
321
- data: { fromLayer, toLayer, specifier: source }
322
- });
704
+ "forbiddenImport",
705
+ {
706
+ ruleId: "LAYER_IMPORT_VIOLATION",
707
+ file: relFile,
708
+ fromLayer,
709
+ toLayer,
710
+ target: relTarget,
711
+ ...node.importKind === "type" ? { typeOnly: true } : {},
712
+ message: `${fromLayer} must not import ${toLayer}.`
713
+ },
714
+ { fromLayer, toLayer, specifier: source }
715
+ );
323
716
  }
324
717
  return;
325
718
  }
326
- if (isDomainFileHeuristic(filename) && isInfraImportHeuristic(source)) {
327
- context.report({ node, messageId: "forbiddenImportHeuristic" });
328
- }
329
719
  };
330
720
  return {
331
721
  ImportDeclaration: check,
@@ -348,11 +738,21 @@ var noRawEventPublish = {
348
738
  create(context) {
349
739
  return {
350
740
  CallExpression(node) {
351
- if (!isPublishCall(node)) return;
352
741
  const firstArg = node.arguments?.[0];
353
742
  const firstValue = stringValue(firstArg);
354
- if (firstValue && looksLikeIntent(firstValue) || objectHasProperty(firstArg, "intent")) {
355
- context.report({ node, messageId: "rawPublish" });
743
+ const findings = classifyPublishFacts({
744
+ publishCall: isPublishCall(node),
745
+ rawIntentName: firstValue,
746
+ objectHasIntent: objectHasProperty(firstArg, "intent"),
747
+ arkPublishCandidate: false,
748
+ hasSource: true
749
+ });
750
+ if (findings.some((finding) => finding.ruleId === "RAW_EVENT_PUBLISH")) {
751
+ const finding = findings.find((item) => item.ruleId === "RAW_EVENT_PUBLISH");
752
+ reportAdapterDiagnostic(context, node, "rawPublish", {
753
+ ...finding,
754
+ file: lintedFilename(context)
755
+ });
356
756
  }
357
757
  }
358
758
  };
@@ -372,13 +772,22 @@ var requirePublishSource = {
372
772
  create(context) {
373
773
  return {
374
774
  CallExpression(node) {
375
- if (!isPublishCall(node)) return;
376
775
  const firstArg = node.arguments?.[0];
377
776
  const metadataArg = node.arguments?.[2];
378
- if (objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, "source")) {
379
- return;
777
+ const findings = classifyPublishFacts({
778
+ publishCall: isPublishCall(node),
779
+ rawIntentName: stringValue(firstArg),
780
+ objectHasIntent: objectHasProperty(firstArg, "intent"),
781
+ arkPublishCandidate: true,
782
+ hasSource: objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, "source")
783
+ });
784
+ const finding = findings.find((item) => item.ruleId === "PUBLISH_MISSING_SOURCE");
785
+ if (finding) {
786
+ reportAdapterDiagnostic(context, node, "missingSource", {
787
+ ...finding,
788
+ file: lintedFilename(context)
789
+ });
380
790
  }
381
- context.report({ node, messageId: "missingSource" });
382
791
  }
383
792
  };
384
793
  }
@@ -387,7 +796,7 @@ var noForbiddenGlobals = {
387
796
  meta: {
388
797
  type: "problem",
389
798
  docs: {
390
- 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."
799
+ description: "Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` explicitly overrides."
391
800
  },
392
801
  messages: {
393
802
  forbiddenGlobal: 'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',
@@ -425,32 +834,63 @@ var noForbiddenGlobals = {
425
834
  } else {
426
835
  globals = null;
427
836
  }
428
- } else if (isDomainFileHeuristic(filename)) {
429
- globals = new Set(DEFAULT_FORBIDDEN_GLOBALS);
430
837
  }
431
838
  if (!globals) {
432
839
  return {};
433
840
  }
434
- const report = (node, name) => context.report({
435
- node,
436
- messageId: config ? "forbiddenGlobal" : "forbiddenGlobalDefault",
437
- data: { name, layer: layerName }
438
- });
841
+ const scopeAware = typeof sourceCodeFor(context)?.getScope === "function";
842
+ const report = (node, name) => {
843
+ const absFile = path.isAbsolute(filename) ? filename : path.resolve(filename);
844
+ const reportFile = root ? path.relative(root, absFile).split(path.sep).join("/") : filename;
845
+ reportAdapterDiagnostic(
846
+ context,
847
+ node,
848
+ config ? "forbiddenGlobal" : "forbiddenGlobalDefault",
849
+ {
850
+ ruleId: "FORBIDDEN_GLOBAL",
851
+ file: reportFile,
852
+ fromLayer: layerName,
853
+ target: name,
854
+ message: `${layerName} must not use the ambient global "${name}".`
855
+ },
856
+ { name, layer: layerName }
857
+ );
858
+ };
439
859
  return {
440
860
  MemberExpression(node) {
441
- const base = node.object?.type === "Identifier" ? node.object.name : void 0;
442
- if (!base) return;
443
- const dotted = `${base}.${propertyName(node.property) ?? ""}`;
444
- if (globals.has(dotted)) report(node, dotted);
445
- else if (globals.has(base)) report(node, base);
861
+ if (node.parent?.type === "MemberExpression" && node.parent.object === node) return;
862
+ const path2 = memberExpressionPath(node);
863
+ if (!path2 || isLocallyBound(context, path2.root, path2.segments[0])) return;
864
+ const explicitGlobalThis = path2.segments[0] === "globalThis";
865
+ const normalized = explicitGlobalThis ? path2.segments.slice(1) : path2.segments;
866
+ let match;
867
+ for (let length = normalized.length; length >= (explicitGlobalThis ? 1 : 2); length -= 1) {
868
+ const candidate = normalized.slice(0, length).join(".");
869
+ if (globals.has(candidate)) {
870
+ match = candidate;
871
+ break;
872
+ }
873
+ }
874
+ if (match) report(node, match);
875
+ else if (!scopeAware && globals.has(path2.segments[0])) {
876
+ report(node, path2.segments[0]);
877
+ }
446
878
  },
447
879
  CallExpression(node) {
880
+ if (scopeAware) return;
448
881
  const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
449
882
  if (callee && globals.has(callee)) report(node, callee);
450
883
  },
451
884
  NewExpression(node) {
885
+ if (scopeAware) return;
452
886
  const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
453
887
  if (callee && globals.has(callee)) report(node, callee);
888
+ },
889
+ Identifier(node) {
890
+ if (!scopeAware || !node.name || !globals.has(node.name) || !isValueIdentifierReference(context, node) || isLocallyBound(context, node, node.name)) {
891
+ return;
892
+ }
893
+ report(node, node.name);
454
894
  }
455
895
  };
456
896
  }
@@ -489,4 +929,3 @@ export {
489
929
  requirePublishSource,
490
930
  resolveRelativeImport
491
931
  };
492
- //# sourceMappingURL=index.js.map