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
@@ -216,6 +216,368 @@ function isEdgeDenied(rules2, from, to, options) {
216
216
  return findDeniedEdgeRule(rules2, from, to, options) !== void 0;
217
217
  }
218
218
 
219
+ // src/domain/configContract.ts
220
+ var ARK_CONFIG_SCHEMA_VERSION = "1.0";
221
+ var ARK_CONFIG_SCHEMA_URL = "https://unpkg.com/arkgate@2/schemas/ark.config.schema.json";
222
+ var DEFAULT_LAYER_NAMES = [
223
+ "DomainModel",
224
+ "ApplicationOrchestration",
225
+ "PersistenceAdapters",
226
+ "IntegrationAdapters",
227
+ "WorkflowSagaEngine",
228
+ "BackgroundJobsScheduling",
229
+ "PresentationAdapters",
230
+ "ReportingReadModels",
231
+ "ExtensibilityMetadata",
232
+ "SecurityAuditObservability",
233
+ "Kernel"
234
+ ];
235
+ var DEFAULT_ALLOWED_FLOWS = /* @__PURE__ */ new Set([
236
+ "PresentationAdapters->ApplicationOrchestration",
237
+ "ApplicationOrchestration->DomainModel",
238
+ "WorkflowSagaEngine->ApplicationOrchestration",
239
+ "WorkflowSagaEngine->DomainModel",
240
+ "BackgroundJobsScheduling->ApplicationOrchestration"
241
+ ]);
242
+ function createDefaultRules() {
243
+ const rules2 = [];
244
+ for (const from of DEFAULT_LAYER_NAMES) {
245
+ for (const to of DEFAULT_LAYER_NAMES) {
246
+ if (from === to || DEFAULT_ALLOWED_FLOWS.has(`${from}->${to}`)) continue;
247
+ rules2.push({ from, to, allowed: false });
248
+ }
249
+ }
250
+ return rules2;
251
+ }
252
+ var DEFAULT_ARK_CONFIG_RULES = createDefaultRules();
253
+ var stringArraySchema = {
254
+ type: "array",
255
+ items: { type: "string", minLength: 1 },
256
+ uniqueItems: true
257
+ };
258
+ var ARK_CONFIG_SCHEMA = {
259
+ $schema: "https://json-schema.org/draft/2020-12/schema",
260
+ $id: ARK_CONFIG_SCHEMA_URL,
261
+ title: "ArkGate architecture contract",
262
+ description: "Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",
263
+ type: "object",
264
+ additionalProperties: false,
265
+ required: ["$schema", "schemaVersion", "include", "layers", "rules"],
266
+ properties: {
267
+ $schema: {
268
+ type: "string",
269
+ minLength: 1,
270
+ default: ARK_CONFIG_SCHEMA_URL,
271
+ description: "Editor-facing URL or local path for this JSON Schema."
272
+ },
273
+ schemaVersion: {
274
+ type: "string",
275
+ const: ARK_CONFIG_SCHEMA_VERSION,
276
+ default: ARK_CONFIG_SCHEMA_VERSION
277
+ },
278
+ name: { type: "string", minLength: 1 },
279
+ include: { ...stringArraySchema, minItems: 1, default: ["src"] },
280
+ exclude: { ...stringArraySchema, default: [] },
281
+ excludeGenerated: { type: "boolean", default: true },
282
+ frameworkOverlay: { type: "string", minLength: 1 },
283
+ layers: {
284
+ type: "array",
285
+ default: [],
286
+ items: { $ref: "#/$defs/layer" }
287
+ },
288
+ rules: {
289
+ type: "array",
290
+ default: DEFAULT_ARK_CONFIG_RULES,
291
+ items: { $ref: "#/$defs/rule" }
292
+ },
293
+ cyclePolicy: {
294
+ type: "string",
295
+ enum: ["strict", "soft", "framework-soft", "off"],
296
+ default: "strict"
297
+ },
298
+ dynamicImportAllowlist: { ...stringArraySchema, default: [] },
299
+ safety: {
300
+ $ref: "#/$defs/safety",
301
+ default: {
302
+ maxTsSuppressions: 0,
303
+ maxAnyCasts: 0,
304
+ allowInMemory: false,
305
+ allowDisabledPeerIsolation: false
306
+ }
307
+ }
308
+ },
309
+ $defs: {
310
+ layer: {
311
+ type: "object",
312
+ additionalProperties: false,
313
+ required: ["name", "patterns"],
314
+ properties: {
315
+ name: { type: "string", minLength: 1 },
316
+ patterns: { ...stringArraySchema, minItems: 1 },
317
+ exclude: stringArraySchema,
318
+ intentPrefixes: stringArraySchema,
319
+ description: { type: "string", minLength: 1 },
320
+ forbiddenGlobals: stringArraySchema,
321
+ mayImportInfrastructure: { type: "boolean" },
322
+ optional: { type: "boolean" }
323
+ }
324
+ },
325
+ rule: {
326
+ type: "object",
327
+ additionalProperties: false,
328
+ required: ["from", "to", "allowed"],
329
+ properties: {
330
+ from: { type: "string", minLength: 1 },
331
+ to: { type: "string", minLength: 1 },
332
+ allowed: { type: "boolean" },
333
+ message: { type: "string", minLength: 1 },
334
+ peerIsolation: { type: "boolean" },
335
+ sliceFolders: { ...stringArraySchema, minItems: 1 }
336
+ }
337
+ },
338
+ safety: {
339
+ type: "object",
340
+ additionalProperties: false,
341
+ properties: {
342
+ maxTsSuppressions: { type: "integer", minimum: 0, default: 0 },
343
+ maxAnyCasts: { type: "integer", minimum: 0, default: 0 },
344
+ allowInMemory: { type: "boolean", default: false },
345
+ allowDisabledPeerIsolation: { type: "boolean", default: false }
346
+ }
347
+ }
348
+ }
349
+ };
350
+ var ArkConfigValidationError = class extends Error {
351
+ issues;
352
+ source;
353
+ constructor(source, issues) {
354
+ super(
355
+ `Invalid ArkGate config (${source}):
356
+ ${issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n")}`
357
+ );
358
+ this.name = "ArkConfigValidationError";
359
+ this.source = source;
360
+ this.issues = issues;
361
+ }
362
+ };
363
+ function isObject(value) {
364
+ return value !== null && typeof value === "object" && !Array.isArray(value);
365
+ }
366
+ function propertyPath(parent, key) {
367
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${parent}.${key}` : `${parent}[${JSON.stringify(key)}]`;
368
+ }
369
+ function valueType(value) {
370
+ if (value === null) return "null";
371
+ if (Array.isArray(value)) return "array";
372
+ return typeof value;
373
+ }
374
+ function resolveSchemaRef(ref, root) {
375
+ const prefix = "#/$defs/";
376
+ if (!ref.startsWith(prefix)) return void 0;
377
+ return root.$defs[ref.slice(prefix.length)];
378
+ }
379
+ function validateNode(value, schema, path2, root, issues) {
380
+ if (schema.$ref) {
381
+ const referenced = resolveSchemaRef(schema.$ref, root);
382
+ if (!referenced) {
383
+ issues.push({ path: path2, message: `schema reference ${schema.$ref} cannot be resolved` });
384
+ return;
385
+ }
386
+ validateNode(value, referenced, path2, root, issues);
387
+ return;
388
+ }
389
+ if (schema.const !== void 0 && !Object.is(value, schema.const)) {
390
+ issues.push({ path: path2, message: `must equal ${JSON.stringify(schema.const)}` });
391
+ return;
392
+ }
393
+ if (schema.enum && !schema.enum.some((candidate) => Object.is(candidate, value))) {
394
+ issues.push({ path: path2, message: `must be one of ${schema.enum.map(String).join(", ")}` });
395
+ return;
396
+ }
397
+ if (schema.type === "object") {
398
+ if (!isObject(value)) {
399
+ issues.push({ path: path2, message: `must be an object; received ${valueType(value)}` });
400
+ return;
401
+ }
402
+ const properties = schema.properties ?? {};
403
+ for (const key of schema.required ?? []) {
404
+ if (value[key] === void 0) {
405
+ issues.push({ path: propertyPath(path2, key), message: "is required" });
406
+ }
407
+ }
408
+ if (schema.additionalProperties === false) {
409
+ for (const key of Object.keys(value)) {
410
+ if (!(key in properties)) {
411
+ issues.push({ path: propertyPath(path2, key), message: "unknown field" });
412
+ }
413
+ }
414
+ }
415
+ for (const [key, childSchema] of Object.entries(properties)) {
416
+ if (value[key] !== void 0) {
417
+ validateNode(value[key], childSchema, propertyPath(path2, key), root, issues);
418
+ }
419
+ }
420
+ return;
421
+ }
422
+ if (schema.type === "array") {
423
+ if (!Array.isArray(value)) {
424
+ issues.push({ path: path2, message: `must be an array; received ${valueType(value)}` });
425
+ return;
426
+ }
427
+ if (schema.minItems !== void 0 && value.length < schema.minItems) {
428
+ issues.push({ path: path2, message: `must contain at least ${schema.minItems} item(s)` });
429
+ }
430
+ if (schema.uniqueItems) {
431
+ const serialized = value.map((entry) => JSON.stringify(entry));
432
+ if (new Set(serialized).size !== serialized.length) {
433
+ issues.push({ path: path2, message: "must not contain duplicate items" });
434
+ }
435
+ }
436
+ if (schema.items) {
437
+ value.forEach(
438
+ (entry, index) => validateNode(entry, schema.items, `${path2}[${index}]`, root, issues)
439
+ );
440
+ }
441
+ return;
442
+ }
443
+ if (schema.type === "string") {
444
+ if (typeof value !== "string") {
445
+ issues.push({ path: path2, message: `must be a string; received ${valueType(value)}` });
446
+ return;
447
+ }
448
+ if (schema.minLength !== void 0 && value.length < schema.minLength) {
449
+ issues.push({ path: path2, message: `must contain at least ${schema.minLength} character(s)` });
450
+ }
451
+ return;
452
+ }
453
+ if (schema.type === "boolean") {
454
+ if (typeof value !== "boolean") {
455
+ issues.push({ path: path2, message: `must be a boolean; received ${valueType(value)}` });
456
+ }
457
+ return;
458
+ }
459
+ if (schema.type === "integer") {
460
+ if (!Number.isInteger(value)) {
461
+ issues.push({ path: path2, message: `must be an integer; received ${valueType(value)}` });
462
+ return;
463
+ }
464
+ if (schema.minimum !== void 0 && value < schema.minimum) {
465
+ issues.push({ path: path2, message: `must be at least ${schema.minimum}` });
466
+ }
467
+ }
468
+ }
469
+ function defaultedConfig(input) {
470
+ return {
471
+ ...input,
472
+ $schema: input.$schema === void 0 ? ARK_CONFIG_SCHEMA_URL : input.$schema,
473
+ schemaVersion: input.schemaVersion === void 0 ? ARK_CONFIG_SCHEMA_VERSION : input.schemaVersion,
474
+ include: input.include === void 0 ? ["src"] : input.include,
475
+ layers: input.layers === void 0 ? [] : input.layers,
476
+ rules: input.rules === void 0 ? DEFAULT_ARK_CONFIG_RULES.map((rule) => ({ ...rule })) : input.rules
477
+ };
478
+ }
479
+ function migrateArkConfig(input, source = "ark.config.json") {
480
+ if (!isObject(input)) {
481
+ throw new ArkConfigValidationError(source, [
482
+ { path: "$", message: `must be an object; received ${valueType(input)}` }
483
+ ]);
484
+ }
485
+ const migratedFrom = input.schemaVersion === void 0 ? "unversioned" : null;
486
+ if (input.schemaVersion !== void 0 && input.schemaVersion !== ARK_CONFIG_SCHEMA_VERSION) {
487
+ throw new ArkConfigValidationError(source, [
488
+ {
489
+ path: "$.schemaVersion",
490
+ message: `unsupported version ${JSON.stringify(input.schemaVersion)}; expected ${ARK_CONFIG_SCHEMA_VERSION}`
491
+ }
492
+ ]);
493
+ }
494
+ return { candidate: defaultedConfig(input), migratedFrom };
495
+ }
496
+ function loadArkConfigContract(input, source = "ark.config.json") {
497
+ const { candidate, migratedFrom } = migrateArkConfig(input, source);
498
+ const issues = [];
499
+ validateNode(
500
+ candidate,
501
+ ARK_CONFIG_SCHEMA,
502
+ "$",
503
+ ARK_CONFIG_SCHEMA,
504
+ issues
505
+ );
506
+ if (issues.length > 0) throw new ArkConfigValidationError(source, issues);
507
+ return { config: candidate, migratedFrom };
508
+ }
509
+ function parseArkConfigJson(json, source = "ark.config.json") {
510
+ let input;
511
+ try {
512
+ input = JSON.parse(json);
513
+ } catch (error) {
514
+ throw new ArkConfigValidationError(source, [
515
+ {
516
+ path: "$",
517
+ message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`
518
+ }
519
+ ]);
520
+ }
521
+ return loadArkConfigContract(input, source);
522
+ }
523
+
524
+ // src/domain/adapterContract.ts
525
+ function text(value) {
526
+ return typeof value === "string" && value.length > 0 ? value : void 0;
527
+ }
528
+ function positiveInteger(value, fallback) {
529
+ return Number.isInteger(value) && Number(value) > 0 ? Number(value) : fallback;
530
+ }
531
+ function toAdapterDiagnostic(violation, fallbackSeverity = "error") {
532
+ const ruleId = text(violation.ruleId) ?? text(violation.code) ?? "ARK_UNKNOWN";
533
+ const severity = violation.severity === "warning" ? "warning" : fallbackSeverity;
534
+ const evidence = {
535
+ ...text(violation.target) ? { target: text(violation.target) } : {},
536
+ ...text(violation.fromLayer) ? { fromLayer: text(violation.fromLayer) } : {},
537
+ ...text(violation.toLayer) ? { toLayer: text(violation.toLayer) } : {},
538
+ ...typeof violation.typeOnly === "boolean" ? { typeOnly: violation.typeOnly } : {}
539
+ };
540
+ return {
541
+ ruleId,
542
+ severity,
543
+ message: text(violation.message) ?? ruleId,
544
+ location: {
545
+ file: text(violation.file) ?? "<unknown>",
546
+ line: positiveInteger(violation.line, 1),
547
+ column: positiveInteger(violation.column, 1)
548
+ },
549
+ evidence
550
+ };
551
+ }
552
+
553
+ // src/domain/sourcePolicy.ts
554
+ var SOURCE_POLICY_MESSAGES = {
555
+ RAW_EVENT_PUBLISH: "Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",
556
+ PUBLISH_MISSING_SOURCE: "Strict Ark publish calls must include metadata.source."
557
+ };
558
+ function looksLikeArkIntent(value) {
559
+ return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(
560
+ value
561
+ );
562
+ }
563
+ function classifyPublishFacts(facts) {
564
+ if (!facts.publishCall) return [];
565
+ const findings = [];
566
+ if (facts.rawIntentName !== void 0 && looksLikeArkIntent(facts.rawIntentName) || facts.objectHasIntent) {
567
+ findings.push({
568
+ ruleId: "RAW_EVENT_PUBLISH",
569
+ message: SOURCE_POLICY_MESSAGES.RAW_EVENT_PUBLISH
570
+ });
571
+ }
572
+ if (facts.arkPublishCandidate && !facts.hasSource) {
573
+ findings.push({
574
+ ruleId: "PUBLISH_MISSING_SOURCE",
575
+ message: SOURCE_POLICY_MESSAGES.PUBLISH_MISSING_SOURCE
576
+ });
577
+ }
578
+ return findings;
579
+ }
580
+
219
581
  // src/eslint/index.ts
220
582
  function lintedFilename(context) {
221
583
  if (typeof context.physicalFilename === "string" && context.physicalFilename.length > 0) {
@@ -233,6 +595,15 @@ function lintedFilename(context) {
233
595
  }
234
596
  return "";
235
597
  }
598
+ function reportAdapterDiagnostic(context, node, messageId, violation, data) {
599
+ const diagnostic = toAdapterDiagnostic({
600
+ ...violation,
601
+ line: violation.line ?? node.loc?.start?.line,
602
+ column: violation.column ?? (typeof node.loc?.start?.column === "number" ? node.loc.start.column + 1 : void 0)
603
+ });
604
+ context.report({ node, messageId, ...data ? { data } : {}, diagnostic });
605
+ return diagnostic;
606
+ }
236
607
  function findConfigPath(startFile) {
237
608
  if (!startFile || startFile === "<input>" || startFile.startsWith("stdin")) return null;
238
609
  let dir = import_node_path.default.dirname(import_node_path.default.resolve(startFile));
@@ -247,14 +618,10 @@ function findConfigPath(startFile) {
247
618
  var _configCache = /* @__PURE__ */ new Map();
248
619
  function loadArkConfig(configPath) {
249
620
  if (_configCache.has(configPath)) return _configCache.get(configPath) ?? null;
250
- try {
251
- const raw = JSON.parse(import_node_fs.default.readFileSync(configPath, "utf8"));
252
- _configCache.set(configPath, raw);
253
- return raw;
254
- } catch {
255
- _configCache.set(configPath, null);
256
- return null;
257
- }
621
+ if (!import_node_fs.default.existsSync(configPath)) return null;
622
+ const config = parseArkConfigJson(import_node_fs.default.readFileSync(configPath, "utf8"), configPath).config;
623
+ _configCache.set(configPath, config);
624
+ return config;
258
625
  }
259
626
  function resolveRelativeImport(fromFile, specifier) {
260
627
  if (!specifier.startsWith(".")) return null;
@@ -285,6 +652,46 @@ function stringValue(node) {
285
652
  function propertyName(node) {
286
653
  return node?.name ?? stringValue(node);
287
654
  }
655
+ function sourceCodeFor(context) {
656
+ return context.sourceCode ?? context.getSourceCode?.();
657
+ }
658
+ function referenceFor(context, node) {
659
+ let scope = sourceCodeFor(context)?.getScope?.(node);
660
+ while (scope) {
661
+ const reference = scope.references?.find((candidate) => candidate.identifier === node);
662
+ if (reference) return reference;
663
+ scope = scope.upper ?? void 0;
664
+ }
665
+ return void 0;
666
+ }
667
+ function isLocallyBound(context, node, name) {
668
+ const reference = referenceFor(context, node);
669
+ if (reference?.resolved) return (reference.resolved.defs?.length ?? 0) > 0;
670
+ let scope = sourceCodeFor(context)?.getScope?.(node);
671
+ while (scope) {
672
+ const variable = scope.set?.get(name);
673
+ if (variable) return (variable.defs?.length ?? 0) > 0;
674
+ scope = scope.upper ?? void 0;
675
+ }
676
+ return false;
677
+ }
678
+ function isValueIdentifierReference(context, node) {
679
+ const reference = referenceFor(context, node);
680
+ if (reference) return reference.isValueReference !== false;
681
+ return node.parent?.type === "VariableDeclarator" && node.parent.init === node;
682
+ }
683
+ function memberExpressionPath(node) {
684
+ if (node?.type === "Identifier" && node.name) {
685
+ return { root: node, segments: [node.name] };
686
+ }
687
+ if (!node) return void 0;
688
+ const memberLike = node.type === "MemberExpression" || Boolean(node.object && node.property);
689
+ if (!memberLike || node.computed === true) return void 0;
690
+ const base = memberExpressionPath(node.object);
691
+ const property = propertyName(node.property);
692
+ if (!base || !property) return void 0;
693
+ return { root: base.root, segments: [...base.segments, property] };
694
+ }
288
695
  function calleePropertyName(node) {
289
696
  return propertyName(node.callee?.property);
290
697
  }
@@ -298,38 +705,14 @@ function objectHasMetadataSource(node) {
298
705
  const metadata = objectProperty(node, "metadata")?.value;
299
706
  return objectHasProperty(metadata, "source");
300
707
  }
301
- function looksLikeIntent(value) {
302
- return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(
303
- value
304
- );
305
- }
306
708
  function isPublishCall(node) {
307
709
  return calleePropertyName(node) === "publish";
308
710
  }
309
- function isDomainFileHeuristic(filename) {
310
- const normalized = filename.split("\\").join("/").toLowerCase();
311
- return normalized.includes("/domain/") || normalized.endsWith("/domain.ts");
312
- }
313
- function isInfraImportHeuristic(specifier) {
314
- const normalized = specifier.toLowerCase();
315
- return [
316
- "adapter",
317
- "adapters",
318
- "infrastructure",
319
- "persistence",
320
- "repository",
321
- "repositories",
322
- "integration",
323
- "database",
324
- "db"
325
- ].some((token) => normalized.includes(token));
326
- }
327
- var DEFAULT_FORBIDDEN_GLOBALS = ["fetch", "process", "Date.now", "Math.random"];
328
711
  var noDomainInfraImports = {
329
712
  meta: {
330
713
  type: "problem",
331
714
  docs: {
332
- 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."
715
+ description: "Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."
333
716
  },
334
717
  messages: {
335
718
  forbiddenImport: "Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",
@@ -361,17 +744,24 @@ var noDomainInfraImports = {
361
744
  toPath: relTarget,
362
745
  layers: config.layers
363
746
  })) {
364
- context.report({
747
+ reportAdapterDiagnostic(
748
+ context,
365
749
  node,
366
- messageId: "forbiddenImport",
367
- data: { fromLayer, toLayer, specifier: source }
368
- });
750
+ "forbiddenImport",
751
+ {
752
+ ruleId: "LAYER_IMPORT_VIOLATION",
753
+ file: relFile,
754
+ fromLayer,
755
+ toLayer,
756
+ target: relTarget,
757
+ ...node.importKind === "type" ? { typeOnly: true } : {},
758
+ message: `${fromLayer} must not import ${toLayer}.`
759
+ },
760
+ { fromLayer, toLayer, specifier: source }
761
+ );
369
762
  }
370
763
  return;
371
764
  }
372
- if (isDomainFileHeuristic(filename) && isInfraImportHeuristic(source)) {
373
- context.report({ node, messageId: "forbiddenImportHeuristic" });
374
- }
375
765
  };
376
766
  return {
377
767
  ImportDeclaration: check,
@@ -394,11 +784,21 @@ var noRawEventPublish = {
394
784
  create(context) {
395
785
  return {
396
786
  CallExpression(node) {
397
- if (!isPublishCall(node)) return;
398
787
  const firstArg = node.arguments?.[0];
399
788
  const firstValue = stringValue(firstArg);
400
- if (firstValue && looksLikeIntent(firstValue) || objectHasProperty(firstArg, "intent")) {
401
- context.report({ node, messageId: "rawPublish" });
789
+ const findings = classifyPublishFacts({
790
+ publishCall: isPublishCall(node),
791
+ rawIntentName: firstValue,
792
+ objectHasIntent: objectHasProperty(firstArg, "intent"),
793
+ arkPublishCandidate: false,
794
+ hasSource: true
795
+ });
796
+ if (findings.some((finding) => finding.ruleId === "RAW_EVENT_PUBLISH")) {
797
+ const finding = findings.find((item) => item.ruleId === "RAW_EVENT_PUBLISH");
798
+ reportAdapterDiagnostic(context, node, "rawPublish", {
799
+ ...finding,
800
+ file: lintedFilename(context)
801
+ });
402
802
  }
403
803
  }
404
804
  };
@@ -418,13 +818,22 @@ var requirePublishSource = {
418
818
  create(context) {
419
819
  return {
420
820
  CallExpression(node) {
421
- if (!isPublishCall(node)) return;
422
821
  const firstArg = node.arguments?.[0];
423
822
  const metadataArg = node.arguments?.[2];
424
- if (objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, "source")) {
425
- return;
823
+ const findings = classifyPublishFacts({
824
+ publishCall: isPublishCall(node),
825
+ rawIntentName: stringValue(firstArg),
826
+ objectHasIntent: objectHasProperty(firstArg, "intent"),
827
+ arkPublishCandidate: true,
828
+ hasSource: objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, "source")
829
+ });
830
+ const finding = findings.find((item) => item.ruleId === "PUBLISH_MISSING_SOURCE");
831
+ if (finding) {
832
+ reportAdapterDiagnostic(context, node, "missingSource", {
833
+ ...finding,
834
+ file: lintedFilename(context)
835
+ });
426
836
  }
427
- context.report({ node, messageId: "missingSource" });
428
837
  }
429
838
  };
430
839
  }
@@ -433,7 +842,7 @@ var noForbiddenGlobals = {
433
842
  meta: {
434
843
  type: "problem",
435
844
  docs: {
436
- 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."
845
+ description: "Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` explicitly overrides."
437
846
  },
438
847
  messages: {
439
848
  forbiddenGlobal: 'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',
@@ -471,32 +880,63 @@ var noForbiddenGlobals = {
471
880
  } else {
472
881
  globals = null;
473
882
  }
474
- } else if (isDomainFileHeuristic(filename)) {
475
- globals = new Set(DEFAULT_FORBIDDEN_GLOBALS);
476
883
  }
477
884
  if (!globals) {
478
885
  return {};
479
886
  }
480
- const report = (node, name) => context.report({
481
- node,
482
- messageId: config ? "forbiddenGlobal" : "forbiddenGlobalDefault",
483
- data: { name, layer: layerName }
484
- });
887
+ const scopeAware = typeof sourceCodeFor(context)?.getScope === "function";
888
+ const report = (node, name) => {
889
+ const absFile = import_node_path.default.isAbsolute(filename) ? filename : import_node_path.default.resolve(filename);
890
+ const reportFile = root ? import_node_path.default.relative(root, absFile).split(import_node_path.default.sep).join("/") : filename;
891
+ reportAdapterDiagnostic(
892
+ context,
893
+ node,
894
+ config ? "forbiddenGlobal" : "forbiddenGlobalDefault",
895
+ {
896
+ ruleId: "FORBIDDEN_GLOBAL",
897
+ file: reportFile,
898
+ fromLayer: layerName,
899
+ target: name,
900
+ message: `${layerName} must not use the ambient global "${name}".`
901
+ },
902
+ { name, layer: layerName }
903
+ );
904
+ };
485
905
  return {
486
906
  MemberExpression(node) {
487
- const base = node.object?.type === "Identifier" ? node.object.name : void 0;
488
- if (!base) return;
489
- const dotted = `${base}.${propertyName(node.property) ?? ""}`;
490
- if (globals.has(dotted)) report(node, dotted);
491
- else if (globals.has(base)) report(node, base);
907
+ if (node.parent?.type === "MemberExpression" && node.parent.object === node) return;
908
+ const path2 = memberExpressionPath(node);
909
+ if (!path2 || isLocallyBound(context, path2.root, path2.segments[0])) return;
910
+ const explicitGlobalThis = path2.segments[0] === "globalThis";
911
+ const normalized = explicitGlobalThis ? path2.segments.slice(1) : path2.segments;
912
+ let match;
913
+ for (let length = normalized.length; length >= (explicitGlobalThis ? 1 : 2); length -= 1) {
914
+ const candidate = normalized.slice(0, length).join(".");
915
+ if (globals.has(candidate)) {
916
+ match = candidate;
917
+ break;
918
+ }
919
+ }
920
+ if (match) report(node, match);
921
+ else if (!scopeAware && globals.has(path2.segments[0])) {
922
+ report(node, path2.segments[0]);
923
+ }
492
924
  },
493
925
  CallExpression(node) {
926
+ if (scopeAware) return;
494
927
  const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
495
928
  if (callee && globals.has(callee)) report(node, callee);
496
929
  },
497
930
  NewExpression(node) {
931
+ if (scopeAware) return;
498
932
  const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
499
933
  if (callee && globals.has(callee)) report(node, callee);
934
+ },
935
+ Identifier(node) {
936
+ if (!scopeAware || !node.name || !globals.has(node.name) || !isValueIdentifierReference(context, node) || isLocallyBound(context, node, node.name)) {
937
+ return;
938
+ }
939
+ report(node, node.name);
500
940
  }
501
941
  };
502
942
  }
@@ -535,4 +975,3 @@ var eslint_default = plugin;
535
975
  requirePublishSource,
536
976
  resolveRelativeImport
537
977
  });
538
- //# sourceMappingURL=index.cjs.map